0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

Merge branch 'main' into feat/fonts

This commit is contained in:
Florian Lefebvre 2025-02-27 11:08:05 +01:00
commit c515886407
160 changed files with 1875 additions and 1502 deletions

View file

@ -0,0 +1,23 @@
---
'astro': minor
---
Exposes extra APIs for scripting and testing.
### Config helpers
Two new helper functions exported from `astro/config`:
- `mergeConfig()` allows users to merge partially defined Astro configurations on top of a base config while following the merge rules of `updateConfig()` available for integrations.
- `validateConfig()` allows users to validate that a given value is a valid Astro configuration and fills in default values as necessary.
These helpers are particularly useful for integration authors and for developers writing scripts that need to manipulate Astro configurations programmatically.
### Programmatic build
The `build` API now receives a second optional `BuildOptions` argument where users can specify:
- `devOutput` (default `false`): output a development-based build similar to code transformed in `astro dev`.
- `teardownCompiler` (default `true`): teardown the compiler WASM instance after build.
These options provide more control when running Astro builds programmatically, especially for testing scenarios or custom build pipelines.

View file

@ -0,0 +1,23 @@
---
'astro': patch
---
Adds experimental responsive image support in Markdown
Previously, the `experimental.responsiveImages` feature could only provide responsive images when using the `<Image />` and `<Picture />` components.
Now, images written with the `![]()` Markdown syntax in Markdown and MDX files will generate responsive images by default when using this experimental feature.
To try this experimental feature, set `experimental.responsiveImages` to true in your `astro.config.mjs` file:
```js
{
experimental: {
responsiveImages: true,
},
}
```
Learn more about using this feature in the [experimental responsive images feature reference](https://docs.astro.build/en/reference/experimental-flags/responsive-images/).
For a complete overview, and to give feedback on this experimental API, see the [Responsive Images RFC](https://github.com/withastro/roadmap/blob/responsive-images/proposals/0053-responsive-images.md).

View file

@ -0,0 +1,30 @@
---
'astro': minor
---
Adds a new configuration option `server.allowedHosts` and CLI option `--allowed-hosts`.
Now you can specify the hostnames that the dev and preview servers are allowed to respond to. This is useful for allowing additional subdomains, or running the dev server in a web container.
`allowedHosts` checks the Host header on HTTP requests from browsers and if it doesn't match, it will reject the request to prevent CSRF and XSS attacks.
```shell
astro dev --allowed-hosts=foo.bar.example.com,bar.example.com
```
```shell
astro preview --allowed-hosts=foo.bar.example.com,bar.example.com
```
```js
// astro.config.mjs
import {defineConfig} from "astro/config";
export default defineConfig({
server: {
allowedHosts: ['foo.bar.example.com', 'bar.example.com']
}
})
```
This feature is the same as [Vite's `server.allowHosts` configuration](https://vite.dev/config/server-options.html#server-allowedhosts).

View file

@ -1,5 +0,0 @@
---
'@astrojs/vue': patch
---
Fixes a case where the compiler could not be resolved automatically

View file

@ -0,0 +1,5 @@
---
'@astrojs/db': patch
---
Expose `ilike` function from `drizzle-orm`

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes an issue where a form field named "attributes" shadows the form.attributes property.

View file

@ -0,0 +1,7 @@
---
'@astrojs/internal-helpers': minor
---
Adds remote URL filtering utilities
This adds logic to filter remote URLs so that it can be used by both `astro` and `@astrojs/markdown-remark`.

View file

@ -0,0 +1,11 @@
---
'astro': minor
---
Adds the ability to process and optimize remote images in Markdown files
Previously, Astro only allowed local images to be optimized when included using `![]()` syntax in plain Markdown files. Astro's image service could only display remote images without any processing.
Now, Astro's image service can also optimize remote images written in standard Markdown syntax. This allows you to enjoy the benefits of Astro's image processing when your images are stored externally, for example in a CMS or digital asset manager.
No additional configuration is required to use this feature! Any existing remote images written in Markdown will now automatically be optimized. To opt-out of this processing, write your images in Markdown using the HTML `<img>` tag instead. Note that images located in your `public/` folder are still never processed.

View file

@ -0,0 +1,7 @@
---
'astro': patch
---
Fixes incorrect config update when calling `updateConfig` from `astro:build:setup` hook.
The function previously called a custom update config function made for merging an Astro config. Now it calls the appropriate `mergeConfig()` utility exported by Vite that updates functional options correctly.

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes an issue where the dev server was applying second decoding of the URL of the incoming request, causing issues for certain URLs.

View file

@ -0,0 +1,11 @@
---
'@astrojs/mdx': minor
---
Adds the ability to process and optimize remote images in Markdown syntax in MDX files.
Previously, Astro only allowed local images to be optimized when included using `![]()` syntax. Astro's image service could only display remote images without any processing.
Now, Astro's image service can also optimize remote images written in standard Markdown syntax. This allows you to enjoy the benefits of Astro's image processing when your images are stored externally, for example in a CMS or digital asset manager.
No additional configuration is required to use this feature! Any existing remote images written in Markdown will now automatically be optimized. To opt-out of this processing, write your images in Markdown using the JSX `<img/>` tag instead. Note that images located in your `public/` folder are still never processed.

View file

@ -0,0 +1,27 @@
---
'@astrojs/vercel': minor
---
Adds support for regular expressions in ISR exclude list
Previously, excluding a page from ISR required explicitly listing it in `isr.exclude`. As websites grew larger, maintaining this list became increasingly difficult, especially for multiple API routes and pages that needed server-side rendering.
To address this, ISR exclusions now support regular expressions, allowing for more flexible and scalable configurations.
```js
// astro.config.mjs
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel({
isr: {
exclude: [
'/preview', // Excludes a specific route (e.g., pages/preview.astro)
'/auth/[page]', // Excludes a dynamic route (e.g., pages/auth/[page].astro)
/^\/api\/.+/, // Excludes all routes starting with /api/
]
}
})
});
```

View file

@ -0,0 +1,11 @@
---
'@astrojs/markdown-remark': minor
---
Adds remote image optimization in Markdown
Previously, an internal remark plugin only looked for images in `![]()` syntax that referred to a relative file path. This meant that only local images stored in `src/` were passed through to an internal rehype plugin that would transform them for later processing by Astro's image service.
Now, the plugins recognize and transform both local and remote images using this syntax. Only [authorized remote images specified in your config](https://docs.astro.build/en/guides/images/#authorizing-remote-images) are transformed; remote images from other sources will not be processed.
While not configurable at this time, this process outputs two separate metadata fields (`localImagePaths` and `remoteImagePaths`) which allow for the possibility of controlling the behavior of each type of image separately in the future.

View file

@ -1,6 +1,6 @@
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { globby as glob } from 'globby';
import { glob } from 'tinyglobby';
import { setOutput } from './utils.mjs';
const { GITHUB_REF = 'main' } = process.env;
@ -85,6 +85,8 @@ async function generatePackageMap() {
const packageRoot = new URL('../../packages/', import.meta.url);
const packages = await glob(['*/package.json', '*/*/package.json'], {
cwd: fileURLToPath(packageRoot),
expandDirectories: false,
ignore: ['**/node_modules/**'],
});
await Promise.all(
packages.map(async (pkg) => {

View file

@ -1,26 +0,0 @@
name: Add continuous release label
on:
issue_comment:
types: [created]
permissions:
pull-requests: write
jobs:
label:
if: ${{ github.repository_owner == 'withastro' && startsWith(github.event.comment.body, '!preview') }}
runs-on: ubuntu-latest
steps:
- name: Check if user has write access
uses: lannonbr/repo-permission-check-action@2.0.2
with:
permission: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
gh issue edit ${{ github.event.issue.number }} --add-label "pr: preview" --repo ${{ github.repository }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -24,8 +24,7 @@ env:
jobs:
preview:
if: |
${{ github.repository_owner == 'withastro' && contains(github.event.pull_request.labels.*.name, 'pr: preview') }}
if: ${{ github.repository_owner == 'withastro' && github.event.label.name == 'pr preview' }}
runs-on: ubuntu-latest
permissions:
contents: read
@ -54,28 +53,12 @@ jobs:
- name: Build Packages
run: pnpm run build
- name: Remove Preview Label
uses: actions-ecosystem/action-remove-labels@v1
with:
labels: "pr: preview"
# - name: Changesets status
# run: pnpm changeset status --output=changesets.json
#
# - name: Retrieve packages to publish
# uses: actions/github-script@v7
# id: packages
# with:
# script: |
# const fs = require('fs');
# let packages = JSON.parse(fs.readFileSync('changesets.json', 'utf8'));
# const releases = packages.releases
# .filter(p => {
# return p.changesets.length > 0;
# })
# .map(p => p.name);
# if (releases.length > 0) {
# return releases.join(' ');
# }
# return ""
# result-encoding: string
- name: Publish packages
run: |
pnpx pkg-pr-new publish --pnpm --compact --no-template 'packages/astro' 'packages/integrations/node' 'packages/integrations/cloudflare' 'packages/integrations/netlify' 'packages/integrations/vercel'
pnpm dlx pkg-pr-new publish --pnpm --compact --no-template 'packages/astro' 'packages/integrations/node' 'packages/integrations/cloudflare' 'packages/integrations/netlify' 'packages/integrations/vercel'

View file

@ -1,206 +0,0 @@
name: Create a Snapshot Release
on:
workflow_dispatch:
issue_comment:
types: [created]
defaults:
run:
shell: bash
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
FORCE_COLOR: 1
jobs:
snapshot-release:
name: Create a snapshot release of a pull request
if: ${{ github.repository_owner == 'withastro' && github.event.issue.pull_request && (contains(github.event.comment.body, '!preview') || contains(github.event.comment.body, '/preview') || contains(github.event.comment.body, '!snapshot') || contains(github.event.comment.body, '/snapshot')) }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
steps:
- name: Check if user has write access
uses: lannonbr/repo-permission-check-action@2.0.2
with:
permission: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract the snapshot name from comment body
id: getSnapshotName
uses: actions/github-script@v7
with:
script: |
const { body } = context.payload.comment;
const PREVIEW_RE = /^[!\/](?:preview|snapshot)\s+(\S*)\s*$/gim;
const [_, name] = PREVIEW_RE.exec(body) ?? [];
if (name) return name;
const error = 'Invalid command. Expected: "/preview <snapshot-name>"'
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: error,
})
core.setFailed(error)
result-encoding: string
- name: resolve pr refs
id: refs
uses: eficode/resolve-pr-refs@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
ref: ${{ steps.refs.outputs.head_ref }}
fetch-depth: 0
- name: Extract base branch from .changeset/config.json
id: getBaseBranch
run: |
baseBranch=$(jq -r '.baseBranch' .changeset/config.json)
echo "baseBranch=${baseBranch}" >> $GITHUB_OUTPUT
- run: git fetch origin ${{ steps.getBaseBranch.outputs.baseBranch }}:${{ steps.getBaseBranch.outputs.baseBranch }}
- name: Setup PNPM
uses: pnpm/action-setup@v3
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build Packages
run: pnpm run build
- name: Bump Package Versions
id: changesets
run: |
pnpm exec changeset status --output status.output.json 2>&1
# Snapshots don't work in pre mode. See https://github.com/changesets/changesets/issues/1195
pnpm exec changeset pre exit || true
pnpm exec changeset version --snapshot ${{ steps.getSnapshotName.outputs.result }}
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "status<<$EOF" >> $GITHUB_OUTPUT
echo "$(cat status.output.json)" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
env:
# Needs access to run the script
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Disable color
FORCE_COLOR: 0
NO_COLOR: 1
- name: Publish Release
id: publish
run: |
set -o pipefail
GITHUB_ACTIONS=0 pnpm run build 2>&1 | tee build.output.txt
BUILD_EXIT_CODE=$?
if [ $BUILD_EXIT_CODE -ne 0 ]; then
echo "::error::Build failed. See output above."
# Store the build output for the notification step before exiting
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "build<<$EOF" >> $GITHUB_OUTPUT
echo "$(cat build.output.txt)" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
exit 1
fi
pnpm exec changeset publish --tag experimental--${{ steps.getSnapshotName.outputs.result }} 2>&1 | tee publish.output.txt
PUBLISH_EXIT_CODE=$?
if [ $PUBLISH_EXIT_CODE -ne 0 ]; then
echo "::error::Publish failed. See output above."
fi
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "build<<$EOF" >> $GITHUB_OUTPUT
echo "$(cat build.output.txt)" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
echo "publish<<$EOF" >> $GITHUB_OUTPUT
echo "$(cat publish.output.txt)" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
# Exit with error if publish failed
exit $PUBLISH_EXIT_CODE
env:
# Needs access to publish to npm
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Disable color
FORCE_COLOR: 0
NO_COLOR: 1
- name: Pull Request Notification
uses: actions/github-script@v7
if: always()
env:
TAG: ${{ steps.getSnapshotName.outputs.result }}
STATUS_DATA: ${{ steps.changesets.outputs.status }}
BUILD_LOG: ${{ steps.publish.outputs.build }}
PUBLISH_LOG: ${{ steps.publish.outputs.publish }}
JOB_STATUS: ${{ job.status }}
with:
script: |
let changeset = { releases: [] };
try {
changeset = JSON.parse(process.env.STATUS_DATA);
} catch (e) {}
let message = '';
if (process.env.JOB_STATUS === 'success') {
message = 'Snapshots have been released for the following packages:';
for (const release of changeset.releases) {
if (release.type === 'none') continue;
message += `\n- \`${release.name}@experimental--${process.env.TAG}\``;
}
} else {
message = '❌ Snapshot release failed.';
}
function details(title, body, failed = false) {
if (!body) return;
message += '\n';
const icon = failed ? '❌ ' : '';
message += `<details><summary><strong>${icon}${title}</strong></summary>`;
message += '\n\n```\n';
message += body;
message += '\n```\n\n</details>';
}
// Show build log first if it exists
if (process.env.BUILD_LOG) {
details('Build Log', process.env.BUILD_LOG, process.env.JOB_STATUS !== 'success');
}
// Only show publish log if it exists (might not if build failed)
if (process.env.PUBLISH_LOG) {
details('Publish Log', process.env.PUBLISH_LOG, process.env.JOB_STATUS !== 'success');
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message,
})

View file

@ -283,6 +283,12 @@ Assigning labels isn't always easy and many times the distinction between the di
- In case the number of reactions of an issue grows, the number of users affected grows, or a discussion uncovers some insights that weren't clear before, it's OK to change the priority of the issue. The maintainer **should** provide an explanation when assigning a different label.
As with any other contribution, triaging is voluntary and best-efforts. We welcome and appreciate all the help you're happy to give (including reading this!) and nothing more. If you are not confident about an issue, you are welcome to leave an issue untriaged for someone who would have more context, or to bring it to their attention.
### Preview releases
You can trigger a preview release **from a PR** anytime by using the label `pr preview`. Add this label, and a workflow will trigger, which at the end will add a comment with the instructions of how to install the preview release.
If you're in need to trigger multiple preview releases from the same PR, remove the label and add it again.
## Code Structure
Server-side rendering (SSR) can be complicated. The Astro package (`packages/astro`) is structured in a way to help think about the different systems.

View file

@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"@benchmark/timer": "workspace:*",
"@benchmark/adapter": "workspace:*",
"astro": "workspace:*",

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -13,6 +13,6 @@
"@astrojs/mdx": "^4.0.8",
"@astrojs/rss": "^4.0.11",
"@astrojs/sitemap": "^3.2.1",
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^5.3.0"
"astro": "^5.3.1"
},
"peerDependencies": {
"astro": "^4.0.0 || ^5.0.0"

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/react": "^4.2.0",
"astro": "^5.3.0",
"astro": "^5.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vitest": "^3.0.5"

View file

@ -13,6 +13,6 @@
"@astrojs/alpinejs": "^0.4.3",
"@types/alpinejs": "^3.13.11",
"alpinejs": "^3.14.8",
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -14,10 +14,10 @@
"@astrojs/react": "^4.2.0",
"@astrojs/solid-js": "^5.0.4",
"@astrojs/svelte": "^7.0.4",
"@astrojs/vue": "^5.0.6",
"@astrojs/vue": "^5.0.7",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"astro": "^5.3.0",
"astro": "^5.3.1",
"preact": "^10.25.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",

View file

@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/preact": "^4.0.4",
"@preact/signals": "^2.0.1",
"astro": "^5.3.0",
"astro": "^5.3.1",
"preact": "^10.25.4"
}
}

View file

@ -13,7 +13,7 @@
"@astrojs/react": "^4.2.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"astro": "^5.3.0",
"astro": "^5.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}

View file

@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/solid-js": "^5.0.4",
"astro": "^5.3.0",
"astro": "^5.3.1",
"solid-js": "^1.9.4"
}
}

View file

@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/svelte": "^7.0.4",
"astro": "^5.3.0",
"astro": "^5.3.1",
"svelte": "^5.19.7"
}
}

View file

@ -10,8 +10,8 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/vue": "^5.0.6",
"astro": "^5.3.0",
"@astrojs/vue": "^5.0.7",
"astro": "^5.3.1",
"vue": "^3.5.13"
}
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^9.1.0",
"astro": "^5.3.0"
"@astrojs/node": "^9.1.1",
"astro": "^5.3.1"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^5.3.0"
"astro": "^5.3.1"
},
"peerDependencies": {
"astro": "^4.0.0"

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -11,9 +11,9 @@
"server": "node dist/server/entry.mjs"
},
"dependencies": {
"@astrojs/node": "^9.1.0",
"@astrojs/node": "^9.1.1",
"@astrojs/svelte": "^7.0.4",
"astro": "^5.3.0",
"astro": "^5.3.1",
"svelte": "^5.19.7"
}
}

View file

@ -9,7 +9,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^5.3.0",
"astro": "^5.3.1",
"sass": "^1.83.4",
"sharp": "^0.33.3"
}

View file

@ -16,6 +16,6 @@
},
"devDependencies": {
"@types/node": "^18.17.8",
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -11,6 +11,6 @@
},
"dependencies": {
"@astrojs/markdoc": "^0.12.9",
"astro": "^5.3.0"
"astro": "^5.3.1"
}
}

View file

@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/mdx": "^4.0.8",
"@astrojs/preact": "^4.0.4",
"astro": "^5.3.0",
"astro": "^5.3.1",
"preact": "^10.25.4"
}
}

View file

@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/preact": "^4.0.4",
"@nanostores/preact": "^0.5.2",
"astro": "^5.3.0",
"astro": "^5.3.1",
"nanostores": "^0.11.3",
"preact": "^10.25.4"
}

View file

@ -13,7 +13,7 @@
"@astrojs/mdx": "^4.0.8",
"@tailwindcss/vite": "^4.0.3",
"@types/canvas-confetti": "^1.9.0",
"astro": "^5.3.0",
"astro": "^5.3.1",
"canvas-confetti": "^1.9.3",
"tailwindcss": "^4.0.3"
}

View file

@ -11,7 +11,7 @@
"test": "vitest"
},
"dependencies": {
"astro": "^5.3.0",
"astro": "^5.3.1",
"vitest": "^3.0.5"
}
}

View file

@ -62,11 +62,11 @@
"esbuild": "^0.24.2",
"eslint": "^9.19.0",
"eslint-plugin-regexp": "^2.7.0",
"globby": "^14.0.2",
"only-allow": "^1.2.1",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.14.1",
"publint": "^0.3.2",
"tinyglobby": "^0.2.12",
"turbo": "^2.4.0",
"typescript": "~5.7.3",
"typescript-eslint": "^8.23.0"

View file

@ -1,5 +1,23 @@
# astro
## 5.3.1
### Patch Changes
- [#13233](https://github.com/withastro/astro/pull/13233) [`32fafeb`](https://github.com/withastro/astro/commit/32fafeb874cc4b6312eb50d54d9f0ca6b83aedbc) Thanks [@joshmkennedy](https://github.com/joshmkennedy)! - Ensures consistent behaviour of `Astro.rewrite`/`ctx.rewrite` when using `base` and `trailingSlash` options.
- [#13003](https://github.com/withastro/astro/pull/13003) [`ea79054`](https://github.com/withastro/astro/commit/ea790542e186b0d2d2e828cb3ebd23bde4d04879) Thanks [@chaegumi](https://github.com/chaegumi)! - Fixes a bug that caused the `vite.base` value to be ignored when running `astro dev`
- [#13299](https://github.com/withastro/astro/pull/13299) [`2e1321e`](https://github.com/withastro/astro/commit/2e1321e9d5b27da3e86bc4021e4136661a8055aa) Thanks [@bluwy](https://github.com/bluwy)! - Uses `tinyglobby` for globbing files
- [#13233](https://github.com/withastro/astro/pull/13233) [`32fafeb`](https://github.com/withastro/astro/commit/32fafeb874cc4b6312eb50d54d9f0ca6b83aedbc) Thanks [@joshmkennedy](https://github.com/joshmkennedy)! - Ensures that `Astro.url`/`ctx.url` is correctly updated with the `base` path after rewrites.
This change fixes an issue where `Astro.url`/`ctx.url` did not include the configured base path after Astro.rewrite was called. Now, the base path is correctly reflected in Astro.url.
Previously, any rewrites performed through `Astro.rewrite`/`ctx.rewrite` failed to append the base path to `Astro.url`/`ctx.rewrite`, which could lead to incorrect URL handling in downstream logic. By fixing this, we ensure that all routes remain consistent and predictable after a rewrite.
If you were relying on the work around of including the base path in astro.rewrite you can now remove it from the path.
## 5.3.0
### Minor Changes

View file

@ -1,7 +1,6 @@
---
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
import type { UnresolvedImageTransform } from '../dist/assets/types';
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes.js';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
import type { HTMLAttributes } from '../types';
@ -46,14 +45,7 @@ if (import.meta.env.DEV) {
additionalAttributes['data-image-component'] = 'true';
}
const { class: className, ...attributes } = useResponsive
? applyResponsiveAttributes({
layout,
image,
props,
additionalAttributes,
})
: { ...additionalAttributes, ...image.attributes };
const { class: className, ...attributes } = { ...additionalAttributes, ...image.attributes };
---
{/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}

View file

@ -1,8 +1,7 @@
---
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
import * as mime from 'mrmime';
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes';
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind.js';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
import type {
GetImageResult,
@ -101,18 +100,14 @@ if (fallbackImage.srcSet.values.length > 0) {
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
}
const { class: className, ...attributes } = useResponsive
? applyResponsiveAttributes({
layout,
image: fallbackImage,
props,
additionalAttributes: imgAdditionalAttributes,
})
: { ...imgAdditionalAttributes, ...fallbackImage.attributes };
if (import.meta.env.DEV) {
imgAdditionalAttributes['data-image-component'] = 'true';
}
const { class: className, ...attributes } = {
...imgAdditionalAttributes,
...fallbackImage.attributes,
};
---
<picture {...pictureAttributes}>

View file

@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/db": "workspace:*",
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",

View file

@ -12,7 +12,7 @@
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/db": "workspace:*",
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
"@types/react": "npm:types-react",
"@types/react-dom": "npm:types-react-dom",

View file

@ -4,6 +4,6 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "^9.0.2"
"@astrojs/node": "workspace:*"
}
}

View file

@ -7,6 +7,6 @@
},
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "^9.0.2"
"@astrojs/node": "workspace:*"
}
}

View file

@ -9,7 +9,7 @@
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}

View file

@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
"@astrojs/solid-js": "workspace:*",
"@astrojs/svelte": "workspace:*",

View file

@ -12,5 +12,6 @@ export const prerender = false;
<input type="hidden" name="name" value="Testing" />
{postShowThrow ? <input type="hidden" name="throw" value="true" /> : ''}
<input type="submit" value="Submit" id="submit" />
<input type="text" name="attributes" />
</form>
</Layout>

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "5.3.0",
"version": "5.3.1",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -144,7 +144,6 @@
"es-module-lexer": "^1.6.0",
"esbuild": "^0.24.2",
"estree-walker": "^3.0.3",
"fast-glob": "^3.3.3",
"flattie": "^1.1.1",
"github-slugger": "^2.0.0",
"html-escaper": "3.0.3",
@ -153,17 +152,18 @@
"kleur": "^4.1.5",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"micromatch": "^4.0.8",
"mrmime": "^2.0.0",
"neotraverse": "^0.6.18",
"p-limit": "^6.2.0",
"p-queue": "^8.1.0",
"picomatch": "^4.0.2",
"preferred-pm": "^4.1.1",
"prompts": "^2.4.2",
"rehype": "^13.0.2",
"semver": "^7.7.1",
"shiki": "^1.29.2",
"tinyexec": "^0.3.2",
"tinyglobby": "^0.2.12",
"tsconfck": "^3.1.4",
"ultrahtml": "^1.5.3",
"unifont": "^0.1.7",
@ -196,7 +196,7 @@
"@types/html-escaper": "3.0.4",
"@types/http-cache-semantics": "^4.0.4",
"@types/js-yaml": "^4.0.9",
"@types/micromatch": "^4.0.9",
"@types/picomatch": "^3.0.2",
"@types/prompts": "^2.4.9",
"@types/semver": "^7.5.8",
"@types/yargs-parser": "^21.0.3",

View file

@ -1,11 +1,11 @@
// @ts-expect-error
import { imageConfig } from 'astro:assets';
import { isRemotePath } from '@astrojs/internal-helpers/path';
import { isRemoteAllowed } from '@astrojs/internal-helpers/remote';
import * as mime from 'mrmime';
import type { APIRoute } from '../../types/public/common.js';
import { getConfiguredImageService } from '../internal.js';
import { etag } from '../utils/etag.js';
import { isRemoteAllowed } from '../utils/remotePattern.js';
async function loadRemoteImage(src: URL, headers: Headers) {
try {

View file

@ -6,11 +6,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
// @ts-expect-error
import { assetsDir, imageConfig, outDir } from 'astro:assets';
import { isRemotePath, removeQueryString } from '@astrojs/internal-helpers/path';
import { isRemoteAllowed } from '@astrojs/internal-helpers/remote';
import * as mime from 'mrmime';
import type { APIRoute } from '../../types/public/common.js';
import { getConfiguredImageService } from '../internal.js';
import { etag } from '../utils/etag.js';
import { isRemoteAllowed } from '../utils/remotePattern.js';
function replaceFileSystemReferences(src: string) {
return os.platform().includes('win32') ? src.replace(/^\/@fs\//, '') : src.replace(/^\/@fs/, '');

View file

@ -16,6 +16,7 @@ import {
type UnresolvedImageTransform,
isImageMetadata,
} from './types.js';
import { addCSSVarsToStyle, cssFitValues } from './utils/imageAttributes.js';
import { isESMImportedImage, isRemoteImage, resolveSrc } from './utils/imageKind.js';
import { inferRemoteSize } from './utils/remoteProbe.js';
@ -151,6 +152,19 @@ export async function getImage(
}
delete resolvedOptions.priority;
delete resolvedOptions.densities;
if (layout !== 'none') {
resolvedOptions.style = addCSSVarsToStyle(
{
w: String(resolvedOptions.width),
h: String(resolvedOptions.height),
fit: cssFitValues.includes(resolvedOptions.fit ?? '') && resolvedOptions.fit,
pos: resolvedOptions.position,
},
resolvedOptions.style,
);
resolvedOptions['data-astro-image'] = layout;
}
}
const validatedOptions = service.validateOptions

View file

@ -1,3 +1,4 @@
import { isRemoteAllowed } from '@astrojs/internal-helpers/remote';
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
import { isRemotePath, joinPaths } from '../../core/path.js';
import type { AstroConfig } from '../../types/public/config.js';
@ -9,7 +10,6 @@ import type {
UnresolvedSrcSetValue,
} from '../types.js';
import { isESMImportedImage, isRemoteImage } from '../utils/imageKind.js';
import { isRemoteAllowed } from '../utils/remotePattern.js';
export type ImageService = LocalImageService | ExternalImageService;

View file

@ -1,5 +1,6 @@
import { toStyleString } from '../../runtime/server/render/util.js';
import type { GetImageResult, ImageLayout, LocalImageProps, RemoteImageProps } from '../types.js';
export const cssFitValues = ['fill', 'contain', 'cover', 'scale-down'];
export function addCSSVarsToStyle(
vars: Record<string, string | false | undefined>,
@ -17,32 +18,3 @@ export function addCSSVarsToStyle(
return `${cssVars} ${style}`;
}
const cssFitValues = ['fill', 'contain', 'cover', 'scale-down'];
export function applyResponsiveAttributes<
T extends LocalImageProps<unknown> | RemoteImageProps<unknown>,
>({
layout,
image,
props,
additionalAttributes,
}: {
layout: Exclude<ImageLayout, 'none'>;
image: GetImageResult;
additionalAttributes: Record<string, any>;
props: T;
}) {
const attributes = { ...additionalAttributes, ...image.attributes };
attributes.style = addCSSVarsToStyle(
{
w: image.attributes.width ?? props.width ?? image.options.width,
h: image.attributes.height ?? props.height ?? image.options.height,
fit: cssFitValues.includes(props.fit ?? '') && props.fit,
pos: props.position,
},
attributes.style,
);
attributes['data-astro-image'] = layout;
return attributes;
}

View file

@ -2,15 +2,6 @@ export { emitESMImage } from './node/emitAsset.js';
export { isESMImportedImage, isRemoteImage } from './imageKind.js';
export { imageMetadata } from './metadata.js';
export { getOrigQueryParams } from './queryParams.js';
export {
isRemoteAllowed,
matchHostname,
matchPathname,
matchPattern,
matchPort,
matchProtocol,
type RemotePattern,
} from './remotePattern.js';
export { hashTransform, propsToFilename } from './transformToPath.js';
export { inferRemoteSize } from './remoteProbe.js';
export { makeSvgComponent } from './svg.js';

View file

@ -20,6 +20,10 @@ export async function dev({ flags }: DevOptions) {
['--host <custom-address>', `Expose on a network IP address at <custom-address>`],
['--open', 'Automatically open the app in the browser on server start'],
['--force', 'Clear the content layer cache, forcing a full rebuild.'],
[
'--allowed-hosts',
'Specify a comma-separated list of allowed hosts or allow any hostname.',
],
['--help (-h)', 'See all available flags.'],
],
},

View file

@ -25,6 +25,12 @@ export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig {
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
open:
typeof flags.open === 'string' || typeof flags.open === 'boolean' ? flags.open : undefined,
allowedHosts:
typeof flags.allowedHosts === 'string'
? flags.allowedHosts.split(',')
: typeof flags.allowedHosts === 'boolean' && flags.allowedHosts === true
? flags.allowedHosts
: [],
},
};
}

View file

@ -18,6 +18,10 @@ export async function preview({ flags }: PreviewOptions) {
['--host', `Listen on all addresses, including LAN and public addresses.`],
['--host <custom-address>', `Expose on a network IP address at <custom-address>`],
['--open', 'Automatically open the app in the browser on server start'],
[
'--allowed-hosts',
'Specify a comma-separated list of allowed hosts or allow any hostname.',
],
['--help (-h)', 'See all available flags.'],
],
},

View file

@ -5,6 +5,8 @@ import type { ImageServiceConfig } from '../types/public/index.js';
export { defineConfig, getViteConfig } from './index.js';
export { envField } from '../env/config.js';
export { mergeConfig } from '../core/config/merge.js';
export { validateConfig } from '../core/config/validate.js';
export { fontProviders, defineFontProvider } from '../assets/fonts/providers/index.js';
/**

View file

@ -157,6 +157,7 @@ function createManifest(
clientDirectives: manifest?.clientDirectives ?? getDefaultClientDirectives(),
renderers: renderers ?? manifest?.renderers ?? [],
base: manifest?.base ?? ASTRO_CONFIG_DEFAULTS.base,
userAssetsBase: manifest?.userAssetsBase ?? '',
componentMetadata: manifest?.componentMetadata ?? new Map(),
inlinedScripts: manifest?.inlinedScripts ?? new Map(),
i18n: manifest?.i18n,
@ -234,6 +235,7 @@ type AstroContainerManifest = Pick<
| 'renderers'
| 'assetsPrefix'
| 'base'
| 'userAssetsBase'
| 'routes'
| 'assets'
| 'entryModules'

View file

@ -1,10 +1,10 @@
import { promises as fs, existsSync } from 'node:fs';
import { relative } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import fastGlob from 'fast-glob';
import { bold, green } from 'kleur/colors';
import micromatch from 'micromatch';
import pLimit from 'p-limit';
import picomatch from 'picomatch';
import { glob as tinyglobby } from 'tinyglobby';
import type { ContentEntryRenderFunction, ContentEntryType } from '../../types/public/content.js';
import type { RenderedContent } from '../data-store.js';
import { getContentEntryIdAndSlug, posixRelative } from '../utils.js';
@ -236,8 +236,9 @@ export function glob(globOptions: GlobOptions): Loader {
logger.warn(`The base directory "${fileURLToPath(baseDir)}" does not exist.`);
}
const files = await fastGlob(globOptions.pattern, {
const files = await tinyglobby(globOptions.pattern, {
cwd: fileURLToPath(baseDir),
expandDirectories: false,
});
if (exists && files.length === 0) {
@ -321,7 +322,7 @@ export function glob(globOptions: GlobOptions): Loader {
watcher.add(filePath);
const matchesGlob = (entry: string) =>
!entry.startsWith('../') && micromatch.isMatch(entry, globOptions.pattern);
!entry.startsWith('../') && picomatch.isMatch(entry, globOptions.pattern);
const basePath = fileURLToPath(baseDir);

View file

@ -414,13 +414,23 @@ async function updateImageReferencesInBody(html: string, fileName: string) {
for (const [_full, imagePath] of html.matchAll(CONTENT_LAYER_IMAGE_REGEX)) {
try {
const decodedImagePath = JSON.parse(imagePath.replaceAll('&#x22;', '"'));
const id = imageSrcToImportId(decodedImagePath.src, fileName);
const imported = imageAssetMap.get(id);
if (!id || imageObjects.has(id) || !imported) {
continue;
let image: GetImageResult;
if (URL.canParse(decodedImagePath.src)) {
// Remote image, pass through without resolving import
// We know we should resolve this remote image because either:
// 1. It was collected with the remark-collect-images plugin, which respects the astro image configuration,
// 2. OR it was manually injected by another plugin, and we should respect that.
image = await getImage(decodedImagePath);
} else {
const id = imageSrcToImportId(decodedImagePath.src, fileName);
const imported = imageAssetMap.get(id);
if (!id || imageObjects.has(id) || !imported) {
continue;
}
image = await getImage({ ...decodedImagePath, src: imported });
}
const image: GetImageResult = await getImage({ ...decodedImagePath, src: imported });
imageObjects.set(imagePath, image);
} catch {
throw new Error(`Failed to parse image reference: ${imagePath}`);

View file

@ -1,8 +1,8 @@
import type fsMod from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import glob from 'fast-glob';
import { bold, cyan } from 'kleur/colors';
import { glob } from 'tinyglobby';
import { type ViteDevServer, normalizePath } from 'vite';
import { type ZodSchema, z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
@ -94,21 +94,16 @@ export async function createContentTypesGenerator({
}
const globResult = await glob('**', {
cwd: fileURLToPath(contentPaths.contentDir),
fs: {
readdir: fs.readdir.bind(fs),
readdirSync: fs.readdirSync.bind(fs),
},
onlyFiles: false,
objectMode: true,
absolute: true,
});
for (const entry of globResult) {
const fullPath = path.join(fileURLToPath(contentPaths.contentDir), entry.path);
for (const fullPath of globResult) {
const entryURL = pathToFileURL(fullPath);
if (entryURL.href.startsWith(contentPaths.config.url.href)) continue;
if (entry.dirent.isFile()) {
const stat = fs.statSync(fullPath);
if (stat.isFile()) {
events.push({ name: 'add', entry: entryURL });
} else if (entry.dirent.isDirectory()) {
} else if (stat.isDirectory()) {
events.push({ name: 'addDir', entry: entryURL });
}
}

View file

@ -2,8 +2,8 @@ import nodeFs from 'node:fs';
import { extname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { dataToEsm } from '@rollup/pluginutils';
import glob from 'fast-glob';
import pLimit from 'p-limit';
import { glob } from 'tinyglobby';
import type { Plugin, ViteDevServer } from 'vite';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { rootRelativePath } from '../core/viteUtils.js';
@ -280,7 +280,7 @@ export async function generateLookupMap({
{
absolute: true,
cwd: fileURLToPath(root),
fs,
expandDirectories: false,
},
);

View file

@ -177,7 +177,7 @@ export class App {
if (!pathname) {
pathname = prependForwardSlash(this.removeBase(url.pathname));
}
let routeData = matchRoute(pathname, this.#manifestData);
let routeData = matchRoute(decodeURI(pathname), this.#manifestData);
// missing routes fall-through, pre rendered are handled by static layer
if (!routeData || routeData.prerender) return undefined;

View file

@ -48,6 +48,13 @@ export type SSRManifest = {
routes: RouteInfo[];
site?: string;
base: string;
/**
* The base of the assets generated **by the user**. For example, scripts created by the user falls under this category.
*
* The value of this field comes from `vite.base`. We aren't usually this tight to vite in our code base, so probably
* this should be refactored somehow.
*/
userAssetsBase: string | undefined;
trailingSlash: AstroConfig['trailingSlash'];
buildFormat: NonNullable<AstroConfig['build']>['format'];
compressHTML: boolean;

View file

@ -360,7 +360,7 @@ async function getPathsForRoute(
// NOTE: The same URL may match multiple routes in the manifest.
// Routing priority needs to be verified here for any duplicate
// paths to ensure routing priority rules are enforced in the final build.
const matchedRoute = matchRoute(staticPath, options.routesList);
const matchedRoute = matchRoute(decodeURI(staticPath), options.routesList);
return matchedRoute === route;
});
@ -630,6 +630,7 @@ function createBuildManifest(
compressHTML: settings.config.compressHTML,
renderers,
base: settings.config.base,
userAssetsBase: settings.config?.vite?.base,
assetsPrefix: settings.config.build.assetsPrefix,
site: settings.config.site,
componentMetadata: internals.componentMetadata,

View file

@ -45,7 +45,9 @@ export interface BuildOptions {
* Teardown the compiler WASM instance after build. This can improve performance when
* building once, but may cause a performance hit if building multiple times in a row.
*
* @internal only used for testing
* When building multiple projects in the same execution (e.g. during tests), disabling
* this option can greatly improve performance at the cost of some extra memory usage.
*
* @default true
*/
teardownCompiler?: boolean;

View file

@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url';
import glob from 'fast-glob';
import type { OutputChunk } from 'rollup';
import { glob } from 'tinyglobby';
import type { Plugin as VitePlugin } from 'vite';
import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js';
import { normalizeTheLocale } from '../../../i18n/index.js';
@ -287,6 +287,7 @@ function buildManifest(
routes,
site: settings.config.site,
base: settings.config.base,
userAssetsBase: settings.config?.vite?.base,
trailingSlash: settings.config.trailingSlash,
compressHTML: settings.config.compressHTML,
assetsPrefix: settings.config.build.assetsPrefix,

View file

@ -2,8 +2,8 @@ import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { teardown } from '@astrojs/compiler';
import glob from 'fast-glob';
import { bgGreen, black, green } from 'kleur/colors';
import { glob } from 'tinyglobby';
import * as vite from 'vite';
import { type BuildInternals, createBuildInternals } from '../../core/build/internal.js';
import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js';
@ -247,8 +247,8 @@ async function clientBuild(
// Nothing to do if there is no client-side JS.
if (!input.size) {
// If SSR, copy public over
if (ssr) {
await copyFiles(settings.config.publicDir, out, true);
if (ssr && fs.existsSync(settings.config.publicDir)) {
await fs.promises.cp(settings.config.publicDir, out, { recursive: true, force: true });
}
return null;
@ -384,31 +384,12 @@ async function cleanServerOutput(
.map((fileName) => fs.promises.rm(new URL(fileName, out))),
);
// Copy assets before cleaning directory if outside root
await copyFiles(out, opts.settings.config.outDir, true);
await fs.promises.cp(out, opts.settings.config.outDir, { recursive: true, force: true });
await fs.promises.rm(out, { recursive: true });
return;
}
}
export async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles = false) {
const files = await glob('**/*', {
cwd: fileURLToPath(fromFolder),
dot: includeDotfiles,
});
if (files.length === 0) return;
return await Promise.all(
files.map(async function copyFile(filename) {
const from = new URL(filename, fromFolder);
const to = new URL(filename, toFolder);
const lastFolder = new URL('./', to);
return fs.promises.mkdir(lastFolder, { recursive: true }).then(async function fsCopyFile() {
const p = await fs.promises.copyFile(from, to, fs.constants.COPYFILE_FICLONE);
return p;
});
}),
);
}
async function ssrMoveAssets(opts: StaticBuildOptions) {
opts.logger.info('build', 'Rearranging server assets...');
const serverRoot =

View file

@ -1,4 +1,6 @@
import { mergeConfig as mergeViteConfig } from 'vite';
import type { DeepPartial } from '../../type-utils.js';
import type { AstroConfig, AstroInlineConfig } from '../../types/public/index.js';
import { arraify, isObject, isURL } from '../util.js';
function mergeConfigRecursively(
@ -64,10 +66,9 @@ function mergeConfigRecursively(
return merged;
}
export function mergeConfig(
defaults: Record<string, any>,
overrides: Record<string, any>,
isRoot = true,
): Record<string, any> {
return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.');
export function mergeConfig<C extends AstroConfig | AstroInlineConfig>(
defaults: C,
overrides: DeepPartial<C>,
): C {
return mergeConfigRecursively(defaults, overrides, '') as C;
}

View file

@ -81,6 +81,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
host: false,
port: 4321,
open: false,
allowedHosts: [],
},
integrations: [],
markdown: markdownConfigDefaults,
@ -218,6 +219,10 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.server.host),
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
headers: z.custom<OutgoingHttpHeaders>().optional(),
allowedHosts: z
.union([z.array(z.string()), z.literal(true)])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.allowedHosts),
})
.default({}),
),
@ -801,6 +806,10 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) {
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
headers: z.custom<OutgoingHttpHeaders>().optional(),
streaming: z.boolean().optional().default(true),
allowedHosts: z
.union([z.array(z.string()), z.literal(true)])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.allowedHosts),
})
.optional()
.default({}),

View file

@ -1,6 +1,6 @@
import nodeFs from 'node:fs';
import { fileURLToPath } from 'node:url';
import glob from 'fast-glob';
import { convertPathToPattern } from 'tinyglobby';
import * as vite from 'vite';
import { crawlFrameworkPkgs } from 'vitefu';
import { vitePluginActions, vitePluginUserActions } from '../actions/plugins.js';
@ -124,7 +124,7 @@ export async function createVite(
},
});
const srcDirPattern = glob.convertPathToPattern(fileURLToPath(settings.config.srcDir));
const srcDirPattern = convertPathToPattern(fileURLToPath(settings.config.srcDir));
const envLoader = createEnvLoader(mode, settings.config);
// Start with the Vite configuration that Astro core needs

View file

@ -56,7 +56,7 @@ export async function createContainer({
const {
base,
server: { host, headers, open: serverOpen },
server: { host, headers, open: serverOpen, allowedHosts },
} = settings.config;
// serverOpen = true, isRestart = false
@ -92,7 +92,7 @@ export async function createContainer({
const mode = inlineConfig?.mode ?? 'development';
const viteConfig = await createVite(
{
server: { host, headers, open },
server: { host, headers, open, allowedHosts },
optimizeDeps: {
include: rendererClientEntries,
},

View file

@ -1,21 +1,12 @@
// This is the main entrypoint when importing the `astro` package.
import type { AstroInlineConfig } from '../types/public/config.js';
import { default as _build } from './build/index.js';
import { default as _sync } from './sync/index.js';
export { default as build } from './build/index.js';
export { default as dev } from './dev/index.js';
export { default as preview } from './preview/index.js';
/**
* Builds your site for deployment. By default, this will generate static files and place them in a dist/ directory.
* If SSR is enabled, this will generate the necessary server files to serve your site.
*
* @experimental The JavaScript API is experimental
*/
// Wrap `_build` to prevent exposing the second internal options parameter
export const build = (inlineConfig: AstroInlineConfig) => _build(inlineConfig);
/**
* Generates TypeScript types for all Astro modules. This sets up a `src/env.d.ts` file for type inferencing,
* and defines the `astro:content` module for the Content Collections API.

View file

@ -36,6 +36,7 @@ export default async function createStaticPreviewServer(
port: settings.config.server.port,
headers: settings.config.server.headers,
open: settings.config.server.open,
allowedHosts: settings.config.server.allowedHosts,
},
plugins: [vitePluginAstroPreview(settings)],
});

View file

@ -394,6 +394,7 @@ export class RenderContext {
// calling the render() function will populate the object with scripts, styles, etc.
const result: SSRResult = {
base: manifest.base,
userAssetsBase: manifest.userAssetsBase,
cancelled: false,
clientDirectives,
inlinedScripts,

View file

@ -5,18 +5,17 @@ import { SERVER_ISLAND_BASE_PREFIX, SERVER_ISLAND_COMPONENT } from '../server-is
/** Find matching route from pathname */
export function matchRoute(pathname: string, manifest: RoutesList): RouteData | undefined {
const decodedPathname = decodeURI(pathname);
return manifest.routes.find((route) => {
return (
route.pattern.test(decodedPathname) ||
route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(decodedPathname))
route.pattern.test(pathname) ||
route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname))
);
});
}
/** Finds all matching routes from pathname */
export function matchAllRoutes(pathname: string, manifest: RoutesList): RouteData[] {
return manifest.routes.filter((route) => route.pattern.test(decodeURI(pathname)));
return manifest.routes.filter((route) => route.pattern.test(pathname));
}
const ROUTE404_RE = /^\/404\/?$/;

View file

@ -5,7 +5,12 @@ import { shouldAppendForwardSlash } from '../build/util.js';
import { originPathnameSymbol } from '../constants.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { appendForwardSlash, removeTrailingForwardSlash } from '../path.js';
import {
appendForwardSlash,
joinPaths,
prependForwardSlash,
removeTrailingForwardSlash,
} from '../path.js';
import { createRequest } from '../request.js';
import { DEFAULT_404_ROUTE } from './astro-designed-error-pages.js';
@ -45,14 +50,31 @@ export function findRouteToRewrite({
} else {
newUrl = new URL(payload, new URL(request.url).origin);
}
let pathname = newUrl.pathname;
const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);
if (base !== '/' && newUrl.pathname.startsWith(base)) {
pathname = shouldAppendForwardSlash(trailingSlash, buildFormat)
pathname = shouldAppendSlash
? appendForwardSlash(newUrl.pathname)
: removeTrailingForwardSlash(newUrl.pathname);
pathname = pathname.slice(base.length);
}
if (!pathname.startsWith('/') && shouldAppendSlash && newUrl.pathname.endsWith('/')) {
// when base is in the rewrite call and trailingSlash is 'always' this is needed or it will 404.
pathname = prependForwardSlash(pathname);
}
if (pathname === '/' && base !== '/' && !shouldAppendSlash) {
// when rewriting to index and trailingSlash is 'never' this is needed or it will 404
// in this case the pattern will look for '/^$/' so '/' will never match
pathname = '';
}
newUrl.pathname = joinPaths(...[base, pathname].filter(Boolean));
const decodedPathname = decodeURI(pathname);
let foundRoute;
for (const route of routes) {

View file

@ -3,6 +3,7 @@ import type { AddressInfo } from 'node:net';
import { fileURLToPath } from 'node:url';
import { bold } from 'kleur/colors';
import type { InlineConfig, ViteDevServer } from 'vite';
import { mergeConfig as mergeViteConfig } from 'vite';
import astroIntegrationActionsRouteHandler from '../actions/integration.js';
import { isActionsFilePresent } from '../actions/utils.js';
import { CONTENT_LAYER_TYPE } from '../content/consts.js';
@ -197,7 +198,7 @@ export async function runHookConfigSetup({
updatedSettings.scripts.push({ stage, content });
},
updateConfig: (newConfig) => {
updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig;
updatedConfig = mergeConfig(updatedConfig, newConfig);
return { ...updatedConfig };
},
injectRoute: (injectRoute) => {
@ -511,7 +512,7 @@ export async function runHookBuildSetup({
pages,
target,
updateConfig: (newConfig) => {
updatedConfig = mergeConfig(updatedConfig, newConfig);
updatedConfig = mergeViteConfig(updatedConfig, newConfig);
return { ...updatedConfig };
},
logger: getLogger(integration, logger),

View file

@ -27,6 +27,10 @@ export function renderAllHeadContent(result: SSRResult) {
const scripts = Array.from(result.scripts)
.filter(uniqueElements)
.map((script) => {
if (result.userAssetsBase) {
script.props.src =
(result.base === '/' ? '' : result.base) + result.userAssetsBase + script.props.src;
}
return renderElement('script', script, false);
});
const links = Array.from(result.links)

View file

@ -20,5 +20,7 @@ export async function renderScript(result: SSRResult, id: string) {
}
const resolved = await result.resolve(id);
return markHTMLString(`<script type="module" src="${resolved}"></script>`);
return markHTMLString(
`<script type="module" src="${result.userAssetsBase ? (result.base === '/' ? '' : result.base) + result.userAssetsBase : ''}${resolved}"></script>`,
);
}

View file

@ -398,7 +398,9 @@ async function transition(
// Note: getNamedItem can return null in real life, even if TypeScript doesn't think so, hence
// the ?.
init.body =
form?.attributes.getNamedItem('enctype')?.value === 'application/x-www-form-urlencoded'
from !== undefined &&
Reflect.get(HTMLFormElement.prototype, 'attributes', form).getNamedItem('enctype')
?.value === 'application/x-www-form-urlencoded'
? new URLSearchParams(preparationEvent.formData as any)
: preparationEvent.formData;
}

View file

@ -1,4 +1,5 @@
import type { OutgoingHttpHeaders } from 'node:http';
import type { RemotePattern } from '@astrojs/internal-helpers/remote';
import type {
RehypePlugins,
RemarkPlugins,
@ -8,7 +9,6 @@ import type {
import type { BuiltinDriverName, BuiltinDriverOptions, Driver, Storage } from 'unstorage';
import type { UserConfig as OriginalViteUserConfig, SSROptions as ViteSSROptions } from 'vite';
import type { ImageFit, ImageLayout } from '../../assets/types.js';
import type { RemotePattern } from '../../assets/utils/remotePattern.js';
import type { SvgRenderMode } from '../../assets/utils/svg.js';
import type { AssetsPrefix } from '../../core/app/types.js';
import type { AstroConfigType } from '../../core/config/schema.js';
@ -72,6 +72,26 @@ export type ServerConfig = {
*/
port?: number;
/**
* @name server.allowedHosts
* @type {string[] | true}
* @default `[]`
* @version 5.4.0
* @description
*
* A list of hostnames that Astro is allowed to respond to. When the value is set to `true`, any
* hostname is allowed.
*
* ```js
* {
* server: {
* allowedHosts: ['staging.example.com', 'qa.example.com']
* }
* }
* ```
*/
allowedHosts?: string[] | true;
/**
* @name server.headers
* @typeraw {OutgoingHttpHeaders}

View file

@ -14,6 +14,7 @@ export type * from './manifest.js';
export type { AstroIntegrationLogger } from '../../core/logger/core.js';
export type { ToolbarServerHelpers } from '../../runtime/client/dev-toolbar/helpers.js';
export type { RemotePattern } from '@astrojs/internal-helpers/remote';
export type {
MarkdownHeading,
RehypePlugins,
@ -35,7 +36,6 @@ export type {
ImageTransform,
UnresolvedImageTransform,
} from '../../assets/types.js';
export type { RemotePattern } from '../../assets/utils/remotePattern.js';
export type { AssetsPrefix, SSRManifest } from '../../core/app/types.js';
export type {
AstroCookieGetOptions,

View file

@ -214,6 +214,7 @@ export interface SSRResult {
*/
cancelled: boolean;
base: string;
userAssetsBase: string | undefined;
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
links: Set<SSRElement>;

View file

@ -190,6 +190,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
clientDirectives: settings.clientDirectives,
renderers: [],
base: settings.config.base,
userAssetsBase: settings.config?.vite?.base,
assetsPrefix: settings.config.build.assetsPrefix,
site: settings.config.site,
componentMetadata: new Map(),

View file

@ -18,7 +18,10 @@ export const markdownContentEntryType: ContentEntryType = {
handlePropagation: true,
async getRenderFunction(config) {
const processor = await createMarkdownProcessor(config.markdown);
const processor = await createMarkdownProcessor({
image: config.image,
...config.markdown,
});
return async function renderToString(entry) {
// Process markdown even if it's empty as remark/rehype plugins may add content or frontmatter dynamically
const result = await processor.render(entry.body ?? '', {
@ -28,7 +31,10 @@ export const markdownContentEntryType: ContentEntryType = {
});
return {
html: result.code,
metadata: result.metadata,
metadata: {
...result.metadata,
imagePaths: result.metadata.localImagePaths.concat(result.metadata.remoteImagePaths),
},
};
};
},

View file

@ -1,15 +1,19 @@
export type MarkdownImagePath = { raw: string; safeName: string };
export function getMarkdownCodeForImages(imagePaths: MarkdownImagePath[], html: string) {
export function getMarkdownCodeForImages(
localImagePaths: MarkdownImagePath[],
remoteImagePaths: string[],
html: string,
) {
return `
import { getImage } from "astro:assets";
${imagePaths
${localImagePaths
.map((entry) => `import Astro__${entry.safeName} from ${JSON.stringify(entry.raw)};`)
.join('\n')}
const images = async function(html) {
const imageSources = {};
${imagePaths
${localImagePaths
.map((entry) => {
const rawUrl = JSON.stringify(entry.raw);
return `{
@ -29,6 +33,25 @@ export function getMarkdownCodeForImages(imagePaths: MarkdownImagePath[], html:
}`;
})
.join('\n')}
${remoteImagePaths
.map((raw) => {
const rawUrl = JSON.stringify(raw);
return `{
const regex = new RegExp('__ASTRO_IMAGE_="([^"]*' + ${rawUrl.replace(
/[.*+?^${}()|[\]\\]/g,
'\\\\$&',
)} + '[^"]*)"', 'g');
let match;
let occurrenceCounter = 0;
while ((match = regex.exec(html)) !== null) {
const matchKey = ${rawUrl} + '_' + occurrenceCounter;
const props = JSON.parse(match[1].replace(/&#x22;/g, '"'));
imageSources[matchKey] = await getImage(props);
occurrenceCounter++;
}
}`;
})
.join('\n')}
return imageSources;
};

View file

@ -60,7 +60,10 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
// Lazily initialize the Markdown processor
if (!processor) {
processor = createMarkdownProcessor(settings.config.markdown);
processor = createMarkdownProcessor({
image: settings.config.image,
...settings.config.markdown,
});
}
const renderResult = await (await processor).render(raw.content, {
@ -75,16 +78,21 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
}
let html = renderResult.code;
const { headings, imagePaths: rawImagePaths, frontmatter } = renderResult.metadata;
const {
headings,
localImagePaths: rawLocalImagePaths,
remoteImagePaths,
frontmatter,
} = renderResult.metadata;
// Add default charset for markdown pages
const isMarkdownPage = isPage(fileURL, settings);
const charset = isMarkdownPage ? '<meta charset="utf-8">' : '';
// Resolve all the extracted images from the content
const imagePaths: MarkdownImagePath[] = [];
for (const imagePath of rawImagePaths) {
imagePaths.push({
const localImagePaths: MarkdownImagePath[] = [];
for (const imagePath of rawLocalImagePaths) {
localImagePaths.push({
raw: imagePath,
safeName: shorthash(imagePath),
});
@ -108,8 +116,8 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
${
// Only include the code relevant to `astro:assets` if there's images in the file
imagePaths.length > 0
? getMarkdownCodeForImages(imagePaths, html)
localImagePaths.length > 0 || remoteImagePaths.length > 0
? getMarkdownCodeForImages(localImagePaths, remoteImagePaths, html)
: `const html = () => ${JSON.stringify(html)};`
}

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js';
describe('Astro dev headers', () => {
@ -38,3 +39,36 @@ describe('Astro dev headers', () => {
});
});
});
describe('Astro dev with vite.base path', () => {
let fixture;
let devServer;
const headers = {
'x-astro': 'test',
};
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-dev-headers/',
server: {
headers,
},
vite: {
base: '/hello',
},
});
await fixture.build();
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
it('Generated script src get included with vite.base path', async () => {
const result = await fixture.fetch('/hello');
const html = await result.text();
const $ = cheerioLoad(html);
assert.match($('script').attr('src'), /^\/hello\/@vite\/client$/);
});
});

View file

@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^9.0.2",
"@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -4,6 +4,6 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "^9.0.2"
"@astrojs/node": "workspace:*"
}
}

View file

@ -0,0 +1,4 @@
---
return Astro.rewrite("/")
---
<h1>hi</h1>

View file

@ -4,5 +4,6 @@
</head>
<body>
<h1>Index</h1>
<p>{Astro.url.pathname}</p>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more