diff --git a/.changeset/bright-eels-cross.md b/.changeset/bright-eels-cross.md
new file mode 100644
index 0000000000..638a5d7c19
--- /dev/null
+++ b/.changeset/bright-eels-cross.md
@@ -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.
diff --git a/.changeset/clever-rice-compete.md b/.changeset/clever-rice-compete.md
new file mode 100644
index 0000000000..59897964db
--- /dev/null
+++ b/.changeset/clever-rice-compete.md
@@ -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 `` and `` 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).
\ No newline at end of file
diff --git a/.changeset/grumpy-sloths-fail.md b/.changeset/grumpy-sloths-fail.md
new file mode 100644
index 0000000000..1a5e9b272b
--- /dev/null
+++ b/.changeset/grumpy-sloths-fail.md
@@ -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).
diff --git a/.changeset/itchy-buckets-dream.md b/.changeset/itchy-buckets-dream.md
deleted file mode 100644
index 3c26324bf2..0000000000
--- a/.changeset/itchy-buckets-dream.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@astrojs/vue': patch
----
-
-Fixes a case where the compiler could not be resolved automatically
diff --git a/.changeset/late-mails-beam.md b/.changeset/late-mails-beam.md
new file mode 100644
index 0000000000..9858996366
--- /dev/null
+++ b/.changeset/late-mails-beam.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/db': patch
+---
+
+Expose `ilike` function from `drizzle-orm`
diff --git a/.changeset/purple-jokes-pay.md b/.changeset/purple-jokes-pay.md
new file mode 100644
index 0000000000..ddb641371b
--- /dev/null
+++ b/.changeset/purple-jokes-pay.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes an issue where a form field named "attributes" shadows the form.attributes property.
diff --git a/.changeset/quiet-birds-joke.md b/.changeset/quiet-birds-joke.md
new file mode 100644
index 0000000000..37b1758509
--- /dev/null
+++ b/.changeset/quiet-birds-joke.md
@@ -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`.
diff --git a/.changeset/shy-bats-exist.md b/.changeset/shy-bats-exist.md
new file mode 100644
index 0000000000..25ac954ef2
--- /dev/null
+++ b/.changeset/shy-bats-exist.md
@@ -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 `
` tag instead. Note that images located in your `public/` folder are still never processed.
diff --git a/.changeset/slimy-cougars-worry.md b/.changeset/slimy-cougars-worry.md
new file mode 100644
index 0000000000..3182f83b73
--- /dev/null
+++ b/.changeset/slimy-cougars-worry.md
@@ -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.
diff --git a/.changeset/stale-oranges-call.md b/.changeset/stale-oranges-call.md
new file mode 100644
index 0000000000..dd21fc4927
--- /dev/null
+++ b/.changeset/stale-oranges-call.md
@@ -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.
diff --git a/.changeset/tiny-cows-march.md b/.changeset/tiny-cows-march.md
new file mode 100644
index 0000000000..95682c85b0
--- /dev/null
+++ b/.changeset/tiny-cows-march.md
@@ -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 `
` tag instead. Note that images located in your `public/` folder are still never processed.
diff --git a/.changeset/tiny-gifts-drum.md b/.changeset/tiny-gifts-drum.md
new file mode 100644
index 0000000000..ca943476b5
--- /dev/null
+++ b/.changeset/tiny-gifts-drum.md
@@ -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/
+ ]
+ }
+ })
+});
+```
diff --git a/.changeset/warm-planes-swim.md b/.changeset/warm-planes-swim.md
new file mode 100644
index 0000000000..f710f1a6e3
--- /dev/null
+++ b/.changeset/warm-planes-swim.md
@@ -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.
diff --git a/.github/scripts/announce.mjs b/.github/scripts/announce.mjs
index df73040372..9a50dbd1d0 100755
--- a/.github/scripts/announce.mjs
+++ b/.github/scripts/announce.mjs
@@ -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) => {
diff --git a/.github/workflows/preview-comment.yml b/.github/workflows/preview-comment.yml
deleted file mode 100644
index 9fb0f48c39..0000000000
--- a/.github/workflows/preview-comment.yml
+++ /dev/null
@@ -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 }}
diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml
index 7b21124b0f..88d0563b3f 100644
--- a/.github/workflows/preview-release.yml
+++ b/.github/workflows/preview-release.yml
@@ -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'
diff --git a/.github/workflows/snapshot-release.yml b/.github/workflows/snapshot-release.yml
deleted file mode 100644
index 483e72e0f9..0000000000
--- a/.github/workflows/snapshot-release.yml
+++ /dev/null
@@ -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 "'
- 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 += `${icon}${title}
`;
- message += '\n\n```\n';
- message += body;
- message += '\n```\n\n ';
- }
-
- // 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,
- })
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d44be03b8c..db4c42d4b4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.
diff --git a/benchmark/package.json b/benchmark/package.json
index 345c2aba00..64e3699f98 100644
--- a/benchmark/package.json
+++ b/benchmark/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/mdx": "workspace:*",
- "@astrojs/node": "^9.0.2",
+ "@astrojs/node": "workspace:*",
"@benchmark/timer": "workspace:*",
"@benchmark/adapter": "workspace:*",
"astro": "workspace:*",
diff --git a/examples/basics/package.json b/examples/basics/package.json
index ddc72560a8..e8e25f82b0 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
}
}
diff --git a/examples/blog/package.json b/examples/blog/package.json
index 80a9232a81..24354aa6f8 100644
--- a/examples/blog/package.json
+++ b/examples/blog/package.json
@@ -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"
}
}
diff --git a/examples/component/package.json b/examples/component/package.json
index a174511762..f962d0dd3c 100644
--- a/examples/component/package.json
+++ b/examples/component/package.json
@@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
},
"peerDependencies": {
"astro": "^4.0.0 || ^5.0.0"
diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json
index 7210301bb7..1df03f11b6 100644
--- a/examples/container-with-vitest/package.json
+++ b/examples/container-with-vitest/package.json
@@ -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"
diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json
index 422f927296..82415dad51 100644
--- a/examples/framework-alpine/package.json
+++ b/examples/framework-alpine/package.json
@@ -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"
}
}
diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json
index 571b47b2e5..f429275bf0 100644
--- a/examples/framework-multiple/package.json
+++ b/examples/framework-multiple/package.json
@@ -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",
diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json
index 67a41c78b4..b76a1c2d5e 100644
--- a/examples/framework-preact/package.json
+++ b/examples/framework-preact/package.json
@@ -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"
}
}
diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json
index 871759c751..e34457936a 100644
--- a/examples/framework-react/package.json
+++ b/examples/framework-react/package.json
@@ -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"
}
diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json
index f957330295..fcdb79b83d 100644
--- a/examples/framework-solid/package.json
+++ b/examples/framework-solid/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/solid-js": "^5.0.4",
- "astro": "^5.3.0",
+ "astro": "^5.3.1",
"solid-js": "^1.9.4"
}
}
diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json
index 3ce9e94bfb..a9646a800c 100644
--- a/examples/framework-svelte/package.json
+++ b/examples/framework-svelte/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@astrojs/svelte": "^7.0.4",
- "astro": "^5.3.0",
+ "astro": "^5.3.1",
"svelte": "^5.19.7"
}
}
diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json
index 279712ecbe..9643a3426b 100644
--- a/examples/framework-vue/package.json
+++ b/examples/framework-vue/package.json
@@ -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"
}
}
diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json
index c4c628ea15..ac564e17c8 100644
--- a/examples/hackernews/package.json
+++ b/examples/hackernews/package.json
@@ -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"
}
}
diff --git a/examples/integration/package.json b/examples/integration/package.json
index 5737dd0249..0b147de027 100644
--- a/examples/integration/package.json
+++ b/examples/integration/package.json
@@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
},
"peerDependencies": {
"astro": "^4.0.0"
diff --git a/examples/minimal/package.json b/examples/minimal/package.json
index ad1145f2e1..ee25b518e8 100644
--- a/examples/minimal/package.json
+++ b/examples/minimal/package.json
@@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
}
}
diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json
index 95bff2c559..794692032f 100644
--- a/examples/portfolio/package.json
+++ b/examples/portfolio/package.json
@@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
}
}
diff --git a/examples/ssr/package.json b/examples/ssr/package.json
index 554cbbc320..f18ac814bc 100644
--- a/examples/ssr/package.json
+++ b/examples/ssr/package.json
@@ -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"
}
}
diff --git a/examples/starlog/package.json b/examples/starlog/package.json
index edb201ac27..addd00814e 100644
--- a/examples/starlog/package.json
+++ b/examples/starlog/package.json
@@ -9,7 +9,7 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^5.3.0",
+ "astro": "^5.3.1",
"sass": "^1.83.4",
"sharp": "^0.33.3"
}
diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json
index 3edec99c13..463af0292e 100644
--- a/examples/toolbar-app/package.json
+++ b/examples/toolbar-app/package.json
@@ -16,6 +16,6 @@
},
"devDependencies": {
"@types/node": "^18.17.8",
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
}
}
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
index b27aaadc67..b0e190fad1 100644
--- a/examples/with-markdoc/package.json
+++ b/examples/with-markdoc/package.json
@@ -11,6 +11,6 @@
},
"dependencies": {
"@astrojs/markdoc": "^0.12.9",
- "astro": "^5.3.0"
+ "astro": "^5.3.1"
}
}
diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json
index c2e6bb8783..ce69e6d5cd 100644
--- a/examples/with-mdx/package.json
+++ b/examples/with-mdx/package.json
@@ -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"
}
}
diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json
index 0f8c3687a7..c4ab506bf0 100644
--- a/examples/with-nanostores/package.json
+++ b/examples/with-nanostores/package.json
@@ -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"
}
diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json
index 7eca55315f..978532602c 100644
--- a/examples/with-tailwindcss/package.json
+++ b/examples/with-tailwindcss/package.json
@@ -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"
}
diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json
index 684eedb304..e586f54698 100644
--- a/examples/with-vitest/package.json
+++ b/examples/with-vitest/package.json
@@ -11,7 +11,7 @@
"test": "vitest"
},
"dependencies": {
- "astro": "^5.3.0",
+ "astro": "^5.3.1",
"vitest": "^3.0.5"
}
}
diff --git a/package.json b/package.json
index aadc2eb9c6..3b6ffbfc81 100644
--- a/package.json
+++ b/package.json
@@ -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"
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 0b4eadbc8c..01e1e7fde1 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -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
diff --git a/packages/astro/components/Image.astro b/packages/astro/components/Image.astro
index a74c40332e..709e4fe681 100644
--- a/packages/astro/components/Image.astro
+++ b/packages/astro/components/Image.astro
@@ -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 */}
diff --git a/packages/astro/components/Picture.astro b/packages/astro/components/Picture.astro
index 139681b51a..08d7e1cd3b 100644
--- a/packages/astro/components/Picture.astro
+++ b/packages/astro/components/Picture.astro
@@ -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,
+};
---
diff --git a/packages/astro/e2e/fixtures/actions-blog/package.json b/packages/astro/e2e/fixtures/actions-blog/package.json
index f6bd12d0e4..4f5b2ae57d 100644
--- a/packages/astro/e2e/fixtures/actions-blog/package.json
+++ b/packages/astro/e2e/fixtures/actions-blog/package.json
@@ -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",
diff --git a/packages/astro/e2e/fixtures/actions-react-19/package.json b/packages/astro/e2e/fixtures/actions-react-19/package.json
index 8c6f980ea4..beef3a1100 100644
--- a/packages/astro/e2e/fixtures/actions-react-19/package.json
+++ b/packages/astro/e2e/fixtures/actions-react-19/package.json
@@ -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",
diff --git a/packages/astro/e2e/fixtures/i18n/package.json b/packages/astro/e2e/fixtures/i18n/package.json
index 010ea3864e..775984e48d 100644
--- a/packages/astro/e2e/fixtures/i18n/package.json
+++ b/packages/astro/e2e/fixtures/i18n/package.json
@@ -4,6 +4,6 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
- "@astrojs/node": "^9.0.2"
+ "@astrojs/node": "workspace:*"
}
}
diff --git a/packages/astro/e2e/fixtures/server-islands-key/package.json b/packages/astro/e2e/fixtures/server-islands-key/package.json
index 34708f54ad..b0ae17501d 100644
--- a/packages/astro/e2e/fixtures/server-islands-key/package.json
+++ b/packages/astro/e2e/fixtures/server-islands-key/package.json
@@ -7,6 +7,6 @@
},
"dependencies": {
"astro": "workspace:*",
- "@astrojs/node": "^9.0.2"
+ "@astrojs/node": "workspace:*"
}
}
diff --git a/packages/astro/e2e/fixtures/server-islands/package.json b/packages/astro/e2e/fixtures/server-islands/package.json
index e4b5047f7b..9958ee2878 100644
--- a/packages/astro/e2e/fixtures/server-islands/package.json
+++ b/packages/astro/e2e/fixtures/server-islands/package.json
@@ -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"
}
diff --git a/packages/astro/e2e/fixtures/view-transitions/package.json b/packages/astro/e2e/fixtures/view-transitions/package.json
index 41eef0e5e3..480e4a29c9 100644
--- a/packages/astro/e2e/fixtures/view-transitions/package.json
+++ b/packages/astro/e2e/fixtures/view-transitions/package.json
@@ -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:*",
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro
index ca5e4cb825..21ac979ff2 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro
@@ -12,5 +12,6 @@ export const prerender = false;
{postShowThrow ? : ''}
+
diff --git a/packages/astro/package.json b/packages/astro/package.json
index fceb1e0ee8..4c027b648e 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -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",
diff --git a/packages/astro/src/assets/endpoint/generic.ts b/packages/astro/src/assets/endpoint/generic.ts
index f8924134b2..d71d069870 100644
--- a/packages/astro/src/assets/endpoint/generic.ts
+++ b/packages/astro/src/assets/endpoint/generic.ts
@@ -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 {
diff --git a/packages/astro/src/assets/endpoint/node.ts b/packages/astro/src/assets/endpoint/node.ts
index 4b18deb382..991d7171f3 100644
--- a/packages/astro/src/assets/endpoint/node.ts
+++ b/packages/astro/src/assets/endpoint/node.ts
@@ -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/, '');
diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts
index d9c2db5a05..334fb38daf 100644
--- a/packages/astro/src/assets/internal.ts
+++ b/packages/astro/src/assets/internal.ts
@@ -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
diff --git a/packages/astro/src/assets/services/service.ts b/packages/astro/src/assets/services/service.ts
index ee3bcb587f..4bb643a9cd 100644
--- a/packages/astro/src/assets/services/service.ts
+++ b/packages/astro/src/assets/services/service.ts
@@ -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;
diff --git a/packages/astro/src/assets/utils/imageAttributes.ts b/packages/astro/src/assets/utils/imageAttributes.ts
index aa67b528f2..e7d1b69493 100644
--- a/packages/astro/src/assets/utils/imageAttributes.ts
+++ b/packages/astro/src/assets/utils/imageAttributes.ts
@@ -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,
@@ -17,32 +18,3 @@ export function addCSSVarsToStyle(
return `${cssVars} ${style}`;
}
-
-const cssFitValues = ['fill', 'contain', 'cover', 'scale-down'];
-
-export function applyResponsiveAttributes<
- T extends LocalImageProps | RemoteImageProps,
->({
- layout,
- image,
- props,
- additionalAttributes,
-}: {
- layout: Exclude;
- image: GetImageResult;
- additionalAttributes: Record;
- 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;
-}
diff --git a/packages/astro/src/assets/utils/index.ts b/packages/astro/src/assets/utils/index.ts
index 3fae182000..d937048b5f 100644
--- a/packages/astro/src/assets/utils/index.ts
+++ b/packages/astro/src/assets/utils/index.ts
@@ -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';
diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts
index 4bf888c430..1ff7bbdeae 100644
--- a/packages/astro/src/cli/dev/index.ts
+++ b/packages/astro/src/cli/dev/index.ts
@@ -20,6 +20,10 @@ export async function dev({ flags }: DevOptions) {
['--host ', `Expose on a network IP address at `],
['--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.'],
],
},
diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts
index 7466fdda7a..c283ec85e0 100644
--- a/packages/astro/src/cli/flags.ts
+++ b/packages/astro/src/cli/flags.ts
@@ -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
+ : [],
},
};
}
diff --git a/packages/astro/src/cli/preview/index.ts b/packages/astro/src/cli/preview/index.ts
index 468332ce3b..b7af0ea1a3 100644
--- a/packages/astro/src/cli/preview/index.ts
+++ b/packages/astro/src/cli/preview/index.ts
@@ -18,6 +18,10 @@ export async function preview({ flags }: PreviewOptions) {
['--host', `Listen on all addresses, including LAN and public addresses.`],
['--host ', `Expose on a network IP address at `],
['--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.'],
],
},
diff --git a/packages/astro/src/config/entrypoint.ts b/packages/astro/src/config/entrypoint.ts
index 2ea036fc91..5540efa368 100644
--- a/packages/astro/src/config/entrypoint.ts
+++ b/packages/astro/src/config/entrypoint.ts
@@ -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';
/**
diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts
index 3c927ec4d0..0a1b854d10 100644
--- a/packages/astro/src/container/index.ts
+++ b/packages/astro/src/container/index.ts
@@ -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'
diff --git a/packages/astro/src/content/loaders/glob.ts b/packages/astro/src/content/loaders/glob.ts
index 7299c7ca30..9727ccc08e 100644
--- a/packages/astro/src/content/loaders/glob.ts
+++ b/packages/astro/src/content/loaders/glob.ts
@@ -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);
diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts
index 019a4c8b12..52aaec642a 100644
--- a/packages/astro/src/content/runtime.ts
+++ b/packages/astro/src/content/runtime.ts
@@ -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('"', '"'));
- 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}`);
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index 92a83367cf..c6a362a24c 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -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 });
}
}
diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
index fcb6c4371e..7a9295ba5d 100644
--- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
+++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
@@ -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,
},
);
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 543d82d0fd..f76408078a 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -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;
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index a052307033..09e6fe4365 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -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['format'];
compressHTML: boolean;
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index fefeeafe20..fe840c9473 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -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,
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index c3d91196ac..61da8a7823 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -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;
diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts
index f051b12877..5c3fa6f4f9 100644
--- a/packages/astro/src/core/build/plugins/plugin-manifest.ts
+++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts
@@ -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,
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 0353d03237..955acf7d8e 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -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 =
diff --git a/packages/astro/src/core/config/merge.ts b/packages/astro/src/core/config/merge.ts
index c897c1441a..3699d968b2 100644
--- a/packages/astro/src/core/config/merge.ts
+++ b/packages/astro/src/core/config/merge.ts
@@ -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,
- overrides: Record,
- isRoot = true,
-): Record {
- return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.');
+export function mergeConfig(
+ defaults: C,
+ overrides: DeepPartial,
+): C {
+ return mergeConfigRecursively(defaults, overrides, '') as C;
}
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 14f5f05997..dca82436f5 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -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().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().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({}),
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index dc1eb177d5..33beb8dd68 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -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
diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts
index d1570f4920..c984fae7d2 100644
--- a/packages/astro/src/core/dev/container.ts
+++ b/packages/astro/src/core/dev/container.ts
@@ -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,
},
diff --git a/packages/astro/src/core/index.ts b/packages/astro/src/core/index.ts
index 14a8c2f99a..91cec209d6 100644
--- a/packages/astro/src/core/index.ts
+++ b/packages/astro/src/core/index.ts
@@ -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.
diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts
index 855506ef91..814566161e 100644
--- a/packages/astro/src/core/preview/static-preview-server.ts
+++ b/packages/astro/src/core/preview/static-preview-server.ts
@@ -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)],
});
diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts
index 2509c82abe..15ccba4962 100644
--- a/packages/astro/src/core/render-context.ts
+++ b/packages/astro/src/core/render-context.ts
@@ -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,
diff --git a/packages/astro/src/core/routing/match.ts b/packages/astro/src/core/routing/match.ts
index 45ad8f595b..caa3ae7431 100644
--- a/packages/astro/src/core/routing/match.ts
+++ b/packages/astro/src/core/routing/match.ts
@@ -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\/?$/;
diff --git a/packages/astro/src/core/routing/rewrite.ts b/packages/astro/src/core/routing/rewrite.ts
index 78f70e8473..df6ae48450 100644
--- a/packages/astro/src/core/routing/rewrite.ts
+++ b/packages/astro/src/core/routing/rewrite.ts
@@ -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) {
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 1963f62edb..efb568b3f1 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -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),
diff --git a/packages/astro/src/runtime/server/render/head.ts b/packages/astro/src/runtime/server/render/head.ts
index 01f11f21e7..79edc96218 100644
--- a/packages/astro/src/runtime/server/render/head.ts
+++ b/packages/astro/src/runtime/server/render/head.ts
@@ -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)
diff --git a/packages/astro/src/runtime/server/render/script.ts b/packages/astro/src/runtime/server/render/script.ts
index 6d9283790a..85feee1631 100644
--- a/packages/astro/src/runtime/server/render/script.ts
+++ b/packages/astro/src/runtime/server/render/script.ts
@@ -20,5 +20,7 @@ export async function renderScript(result: SSRResult, id: string) {
}
const resolved = await result.resolve(id);
- return markHTMLString(``);
+ return markHTMLString(
+ ``,
+ );
}
diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts
index 925aab0a6b..adc6bef16a 100644
--- a/packages/astro/src/transitions/router.ts
+++ b/packages/astro/src/transitions/router.ts
@@ -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;
}
diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts
index e39e72e38d..db1531500a 100644
--- a/packages/astro/src/types/public/config.ts
+++ b/packages/astro/src/types/public/config.ts
@@ -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}
diff --git a/packages/astro/src/types/public/index.ts b/packages/astro/src/types/public/index.ts
index fae134bbeb..09469ae583 100644
--- a/packages/astro/src/types/public/index.ts
+++ b/packages/astro/src/types/public/index.ts
@@ -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,
diff --git a/packages/astro/src/types/public/internal.ts b/packages/astro/src/types/public/internal.ts
index a17ee76506..a2c1b01d6e 100644
--- a/packages/astro/src/types/public/internal.ts
+++ b/packages/astro/src/types/public/internal.ts
@@ -214,6 +214,7 @@ export interface SSRResult {
*/
cancelled: boolean;
base: string;
+ userAssetsBase: string | undefined;
styles: Set;
scripts: Set;
links: Set;
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index 42146d856a..283c40d45b 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -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(),
diff --git a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
index 6f248853f5..a8832ef356 100644
--- a/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
+++ b/packages/astro/src/vite-plugin-markdown/content-entry-type.ts
@@ -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),
+ },
};
};
},
diff --git a/packages/astro/src/vite-plugin-markdown/images.ts b/packages/astro/src/vite-plugin-markdown/images.ts
index d0ed625358..b99d1af233 100644
--- a/packages/astro/src/vite-plugin-markdown/images.ts
+++ b/packages/astro/src/vite-plugin-markdown/images.ts
@@ -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(/"/g, '"'));
+ imageSources[matchKey] = await getImage(props);
+ occurrenceCounter++;
+ }
+ }`;
+ })
+ .join('\n')}
return imageSources;
};
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 8876250f97..9cd080a030 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -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 ? '' : '';
// 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)};`
}
diff --git a/packages/astro/test/astro-dev-headers.test.js b/packages/astro/test/astro-dev-headers.test.js
index ec7999c33d..3e490ebbc4 100644
--- a/packages/astro/test/astro-dev-headers.test.js
+++ b/packages/astro/test/astro-dev-headers.test.js
@@ -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$/);
+ });
+});
diff --git a/packages/astro/test/fixtures/client-address-node/package.json b/packages/astro/test/fixtures/client-address-node/package.json
index 27efc14c03..4b1c6a5ee0 100644
--- a/packages/astro/test/fixtures/client-address-node/package.json
+++ b/packages/astro/test/fixtures/client-address-node/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
- "@astrojs/node": "^9.0.2",
+ "@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}
diff --git a/packages/astro/test/fixtures/custom-assets-name/package.json b/packages/astro/test/fixtures/custom-assets-name/package.json
index 8989df3a71..00237fbddf 100644
--- a/packages/astro/test/fixtures/custom-assets-name/package.json
+++ b/packages/astro/test/fixtures/custom-assets-name/package.json
@@ -4,6 +4,6 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
- "@astrojs/node": "^9.0.2"
+ "@astrojs/node": "workspace:*"
}
}
diff --git a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/bar.astro b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/bar.astro
new file mode 100644
index 0000000000..8d1a33842d
--- /dev/null
+++ b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/bar.astro
@@ -0,0 +1,4 @@
+---
+return Astro.rewrite("/")
+---
+hi
diff --git a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/index.astro b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/index.astro
index 575bbde364..def1d1c310 100644
--- a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/index.astro
+++ b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/index.astro
@@ -4,5 +4,6 @@
Index
+{Astro.url.pathname}