summaryrefslogtreecommitdiff
path: root/packages/astro
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro')
-rw-r--r--packages/astro/CHANGELOG.md47
-rw-r--r--packages/astro/client.d.ts3
-rw-r--r--packages/astro/e2e/actions-blog.test.js14
-rw-r--r--packages/astro/e2e/actions-react-19.test.js10
-rw-r--r--packages/astro/e2e/astro-component.test.js2
-rw-r--r--packages/astro/e2e/astro-envs.test.js2
-rw-r--r--packages/astro/e2e/client-idle-timeout.test.js33
-rw-r--r--packages/astro/e2e/client-only.test.js2
-rw-r--r--packages/astro/e2e/content-collections.test.js2
-rw-r--r--packages/astro/e2e/css.test.js2
-rw-r--r--packages/astro/e2e/custom-client-directives.test.js2
-rw-r--r--packages/astro/e2e/dev-toolbar-audits.test.js2
-rw-r--r--packages/astro/e2e/dev-toolbar.test.js2
-rw-r--r--packages/astro/e2e/error-cyclic.test.js2
-rw-r--r--packages/astro/e2e/error-sass.test.js2
-rw-r--r--packages/astro/e2e/errors.test.js2
-rw-r--r--packages/astro/e2e/fixtures/actions-blog/package.json2
-rw-r--r--packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts9
-rw-r--r--packages/astro/e2e/fixtures/actions-blog/src/components/Logout.tsx16
-rw-r--r--packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro26
-rw-r--r--packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs9
-rw-r--r--packages/astro/e2e/fixtures/client-idle-timeout/package.json13
-rw-r--r--packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx18
-rw-r--r--packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro16
-rw-r--r--packages/astro/e2e/fixtures/client-only/package.json4
-rw-r--r--packages/astro/e2e/fixtures/errors/package.json4
-rw-r--r--packages/astro/e2e/fixtures/multiple-frameworks/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-in-preact/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-in-react/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-in-solid/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-in-svelte/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-in-vue/package.json4
-rw-r--r--packages/astro/e2e/fixtures/nested-recursive/package.json4
-rw-r--r--packages/astro/e2e/fixtures/solid-circular/package.json2
-rw-r--r--packages/astro/e2e/fixtures/solid-circular/src/env.d.ts1
-rw-r--r--packages/astro/e2e/fixtures/solid-component/package.json2
-rw-r--r--packages/astro/e2e/fixtures/solid-recurse/package.json2
-rw-r--r--packages/astro/e2e/fixtures/svelte-component/package.json2
-rw-r--r--packages/astro/e2e/fixtures/svelte-component/src/env.d.ts1
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/package.json2
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/keep-style-one.astro12
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/keep-theme-one.astro12
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/replace-main-one.astro14
-rw-r--r--packages/astro/e2e/hmr.test.js2
-rw-r--r--packages/astro/e2e/hydration-race.test.js2
-rw-r--r--packages/astro/e2e/i18n.test.js2
-rw-r--r--packages/astro/e2e/multiple-frameworks.test.js2
-rw-r--r--packages/astro/e2e/namespaced-component.test.js2
-rw-r--r--packages/astro/e2e/nested-in-preact.test.js2
-rw-r--r--packages/astro/e2e/nested-in-react.test.js2
-rw-r--r--packages/astro/e2e/nested-in-solid.test.js2
-rw-r--r--packages/astro/e2e/nested-in-svelte.test.js2
-rw-r--r--packages/astro/e2e/nested-in-vue.test.js2
-rw-r--r--packages/astro/e2e/nested-recursive.test.js2
-rw-r--r--packages/astro/e2e/nested-styles.test.js2
-rw-r--r--packages/astro/e2e/pass-js.test.js2
-rw-r--r--packages/astro/e2e/preact-compat-component.test.js4
-rw-r--r--packages/astro/e2e/preact-component.test.js4
-rw-r--r--packages/astro/e2e/preact-lazy-component.test.js4
-rw-r--r--packages/astro/e2e/prefetch.test.js2
-rw-r--r--packages/astro/e2e/react-component.test.js4
-rw-r--r--packages/astro/e2e/server-islands.test.js2
-rw-r--r--packages/astro/e2e/shared-component-tests.js5
-rw-r--r--packages/astro/e2e/solid-circular.test.js2
-rw-r--r--packages/astro/e2e/solid-component.test.js1
-rw-r--r--packages/astro/e2e/solid-recurse.test.js2
-rw-r--r--packages/astro/e2e/svelte-component.test.js4
-rw-r--r--packages/astro/e2e/tailwindcss.test.js2
-rw-r--r--packages/astro/e2e/test-utils.js20
-rw-r--r--packages/astro/e2e/ts-resolution.test.js2
-rw-r--r--packages/astro/e2e/view-transitions.test.js8
-rw-r--r--packages/astro/e2e/vue-component.test.js4
-rw-r--r--packages/astro/package.json27
-rw-r--r--packages/astro/performance/fixtures/md/package.json2
-rw-r--r--packages/astro/performance/fixtures/mdoc/package.json2
-rw-r--r--packages/astro/performance/fixtures/mdoc/src/env.d.ts2
-rw-r--r--packages/astro/performance/fixtures/mdx/package.json2
-rw-r--r--packages/astro/performance/fixtures/mdx/src/env.d.ts2
-rw-r--r--packages/astro/playwright.config.js26
-rw-r--r--packages/astro/playwright.firefox.config.js33
-rw-r--r--packages/astro/src/actions/runtime/middleware.ts28
-rw-r--r--packages/astro/src/actions/runtime/utils.ts5
-rw-r--r--packages/astro/src/actions/runtime/virtual/server.ts35
-rw-r--r--packages/astro/src/actions/runtime/virtual/shared.ts34
-rw-r--r--packages/astro/src/cli/add/babel.ts16
-rw-r--r--packages/astro/src/cli/add/imports.ts35
-rw-r--r--packages/astro/src/cli/add/index.ts226
-rw-r--r--packages/astro/src/cli/add/wrapper.ts16
-rw-r--r--packages/astro/src/cli/docs/open.ts7
-rw-r--r--packages/astro/src/cli/install-package.ts10
-rw-r--r--packages/astro/src/container/pipeline.ts13
-rw-r--r--packages/astro/src/content/loaders/file.ts10
-rw-r--r--packages/astro/src/content/utils.ts3
-rw-r--r--packages/astro/src/core/app/pipeline.ts10
-rw-r--r--packages/astro/src/core/app/types.ts1
-rw-r--r--packages/astro/src/core/base-pipeline.ts9
-rw-r--r--packages/astro/src/core/build/generate.ts3
-rw-r--r--packages/astro/src/core/build/pipeline.ts9
-rw-r--r--packages/astro/src/core/build/plugins/plugin-content.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-manifest.ts3
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts2
-rw-r--r--packages/astro/src/core/config/schema.ts1
-rw-r--r--packages/astro/src/core/constants.ts5
-rw-r--r--packages/astro/src/core/create-vite.ts2
-rw-r--r--packages/astro/src/core/errors/errors-data.ts18
-rw-r--r--packages/astro/src/core/render-context.ts57
-rw-r--r--packages/astro/src/core/routing/rewrite.ts43
-rw-r--r--packages/astro/src/core/sync/index.ts2
-rw-r--r--packages/astro/src/core/viteUtils.ts12
-rw-r--r--packages/astro/src/i18n/index.ts13
-rw-r--r--packages/astro/src/i18n/utils.ts9
-rw-r--r--packages/astro/src/integrations/hooks.ts2
-rw-r--r--packages/astro/src/prefetch/index.ts3
-rw-r--r--packages/astro/src/runtime/client/dev-toolbar/toolbar.ts2
-rw-r--r--packages/astro/src/runtime/client/idle.ts14
-rw-r--r--packages/astro/src/transitions/swap-functions.ts8
-rw-r--r--packages/astro/src/transitions/vite-plugin-transitions.ts1
-rw-r--r--packages/astro/src/types/public/config.ts41
-rw-r--r--packages/astro/src/virtual-modules/i18n.ts10
-rw-r--r--packages/astro/src/virtual-modules/transitions-swap-functions.ts1
-rw-r--r--packages/astro/src/vite-plugin-astro-server/pipeline.ts10
-rw-r--r--packages/astro/src/vite-plugin-astro-server/plugin.ts3
-rw-r--r--packages/astro/src/vite-plugin-fileurl/index.ts3
-rw-r--r--packages/astro/src/vite-plugin-scanner/index.ts2
-rw-r--r--packages/astro/templates/actions.mjs4
-rw-r--r--packages/astro/test/actions.test.js93
-rw-r--r--packages/astro/test/fixtures/0-css/package.json2
-rw-r--r--packages/astro/test/fixtures/actions/src/actions/index.ts70
-rw-r--r--packages/astro/test/fixtures/actions/src/pages/subscribe-prerendered.astro17
-rw-r--r--packages/astro/test/fixtures/alias-tsconfig-baseurl-only/package.json2
-rw-r--r--packages/astro/test/fixtures/alias-tsconfig/package.json2
-rw-r--r--packages/astro/test/fixtures/alias/package.json2
-rw-r--r--packages/astro/test/fixtures/astro-children/package.json2
-rw-r--r--packages/astro/test/fixtures/astro-client-only/package.json2
-rw-r--r--packages/astro/test/fixtures/astro-dynamic/package.json2
-rw-r--r--packages/astro/test/fixtures/astro-slots-nested/package.json4
-rw-r--r--packages/astro/test/fixtures/component-library/package.json2
-rw-r--r--packages/astro/test/fixtures/css-dangling-references/package.json2
-rw-r--r--packages/astro/test/fixtures/error-bad-js/src/env.d.ts1
-rw-r--r--packages/astro/test/fixtures/error-build-location/src/env.d.ts1
-rw-r--r--packages/astro/test/fixtures/error-non-error/src/env.d.ts1
-rw-r--r--packages/astro/test/fixtures/fetch/package.json2
-rw-r--r--packages/astro/test/fixtures/jsx/package.json4
-rw-r--r--packages/astro/test/fixtures/large-array/package.json2
-rw-r--r--packages/astro/test/fixtures/postcss/package.json4
-rw-r--r--packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx8
-rw-r--r--packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx8
-rw-r--r--packages/astro/test/fixtures/preact-component/src/pages/signals.astro5
-rw-r--r--packages/astro/test/fixtures/react-and-solid/package.json2
-rw-r--r--packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro1
-rw-r--r--packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro2
-rw-r--r--packages/astro/test/fixtures/server-islands/hybrid/package.json2
-rw-r--r--packages/astro/test/fixtures/server-islands/ssr/package.json2
-rw-r--r--packages/astro/test/fixtures/slots-solid/package.json2
-rw-r--r--packages/astro/test/fixtures/slots-svelte/package.json2
-rw-r--r--packages/astro/test/fixtures/solid-component/deps/solid-jsx-component/package.json2
-rw-r--r--packages/astro/test/fixtures/solid-component/package.json2
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-chunks/package.json2
-rw-r--r--packages/astro/test/fixtures/svelte-component/package.json2
-rw-r--r--packages/astro/test/fixtures/vue-with-multi-renderer/package.json2
-rw-r--r--packages/astro/test/i18n-routing.test.js103
-rw-r--r--packages/astro/test/preact-component.test.js40
-rw-r--r--packages/astro/test/rewrite.test.js2
-rw-r--r--packages/astro/test/units/actions/form-data-to-object.test.js18
164 files changed, 1104 insertions, 579 deletions
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index cbd3aedc0..3b6c119e8 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -53,7 +53,6 @@
</script>
```
-
- [#11788](https://github.com/withastro/astro/pull/11788) [`7c0ccfc`](https://github.com/withastro/astro/commit/7c0ccfc26947b178584e3476584bcaa490c6ba86) Thanks [@ematipico](https://github.com/ematipico)! - Updates the default value of `security.checkOrigin` to `true`, which enables Cross-Site Request Forgery (CSRF) protection by default for pages rendered on demand.
If you had previously configured `security.checkOrigin: true`, you no longer need this set in your Astro config. This is now the default and it is safe to remove.
@@ -109,6 +108,42 @@
If you are using this service, and cannot migrate to the base Sharp image service, a third-party extraction of the previous service is available here: https://github.com/Princesseuh/astro-image-service-squoosh
+## 4.14.6
+
+### Patch Changes
+
+- [#11847](https://github.com/withastro/astro/pull/11847) [`45b599c`](https://github.com/withastro/astro/commit/45b599c4d40ded6a3e03881181b441ae494cbfcf) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes a case where Vite would be imported by the SSR runtime, causing bundling errors and bloat.
+
+- [#11822](https://github.com/withastro/astro/pull/11822) [`6fcaab8`](https://github.com/withastro/astro/commit/6fcaab84de1044ff4d186b2dfa5831964460062d) Thanks [@bluwy](https://github.com/bluwy)! - Marks internal `vite-plugin-fileurl` plugin with `enforce: 'pre'`
+
+- [#11713](https://github.com/withastro/astro/pull/11713) [`497324c`](https://github.com/withastro/astro/commit/497324c4e87538dc1dc13aea3ced9bd3642d9ba6) Thanks [@voidfill](https://github.com/voidfill)! - Prevents prefetching of the same urls with different hashes.
+
+- [#11814](https://github.com/withastro/astro/pull/11814) [`2bb72c6`](https://github.com/withastro/astro/commit/2bb72c63969f8f21dd279fa927c32f192ff79a3f) Thanks [@eduardocereto](https://github.com/eduardocereto)! - Updates the documentation for experimental Content Layer API with a corrected code example
+
+- [#11842](https://github.com/withastro/astro/pull/11842) [`1ffaae0`](https://github.com/withastro/astro/commit/1ffaae04cf790390f730bf900b9722b99642adc1) Thanks [@stephan281094](https://github.com/stephan281094)! - Fixes a typo in the `MissingImageDimension` error message
+
+- [#11828](https://github.com/withastro/astro/pull/11828) [`20d47aa`](https://github.com/withastro/astro/commit/20d47aa85a3a0d7ac3390f749715d92de830cf3e) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Improves error message when invalid data is returned by an Action.
+
+## 4.14.5
+
+### Patch Changes
+
+- [#11809](https://github.com/withastro/astro/pull/11809) [`62e97a2`](https://github.com/withastro/astro/commit/62e97a20f72bacb017c633ddcb776abc89167660) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fixes usage of `.transform()`, `.refine()`, `.passthrough()`, and other effects on Action form inputs.
+
+- [#11812](https://github.com/withastro/astro/pull/11812) [`260c4be`](https://github.com/withastro/astro/commit/260c4be050f91353bc5ba6af073e7bc17429d552) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Exposes `ActionAPIContext` type from the `astro:actions` module.
+
+- [#11813](https://github.com/withastro/astro/pull/11813) [`3f7630a`](https://github.com/withastro/astro/commit/3f7630afd697809b1d4fbac6edd18153983c70ac) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fixes unexpected `undefined` value when calling an action from the client without a return value.
+
+## 4.14.4
+
+### Patch Changes
+
+- [#11794](https://github.com/withastro/astro/pull/11794) [`3691a62`](https://github.com/withastro/astro/commit/3691a626fb67d617e5f8bd057443cd2ff6caa054) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fixes unexpected warning log when using Actions on "hybrid" rendered projects.
+
+- [#11801](https://github.com/withastro/astro/pull/11801) [`9f943c1`](https://github.com/withastro/astro/commit/9f943c1344671b569a0d1ddba683b3cca0068adc) Thanks [@delucis](https://github.com/delucis)! - Fixes a bug where the `filePath` property was not available on content collection entries when using the content layer `file()` loader with a JSON file that contained an object instead of an array. This was breaking use of the `image()` schema utility among other things.
+
+## 4.14.3
+
### Patch Changes
- [#11780](https://github.com/withastro/astro/pull/11780) [`c6622ad`](https://github.com/withastro/astro/commit/c6622adaeb405e961b12c91f0e5d02c7333d01cf) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Deprecates the Squoosh image service, to be removed in Astro 5.0. We recommend migrating to the default Sharp service.
@@ -127,6 +162,16 @@
- [#11774](https://github.com/withastro/astro/pull/11774) [`c6400ab`](https://github.com/withastro/astro/commit/c6400ab99c5e5f4477bc6ef7e801b7869b0aa9ab) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes the path returned by `injectTypes`
+- [#11790](https://github.com/withastro/astro/pull/11790) [`41c3fcb`](https://github.com/withastro/astro/commit/41c3fcb6189709450a67ea8f726071d5f3cdc80e) Thanks [@sarah11918](https://github.com/sarah11918)! - Updates the documentation for experimental `astro:env` with a corrected link to the RFC proposal
+
+- [#11773](https://github.com/withastro/astro/pull/11773) [`86a3391`](https://github.com/withastro/astro/commit/86a33915ff41b23ff6b35bcfb1805fefc0760ca7) Thanks [@ematipico](https://github.com/ematipico)! - Changes messages logged when using unsupported, deprecated, or experimental adapter features for clarity
+
+- [#11745](https://github.com/withastro/astro/pull/11745) [`89bab1e`](https://github.com/withastro/astro/commit/89bab1e70786123fbe933a9d7a1b80c9334dcc5f) Thanks [@bluwy](https://github.com/bluwy)! - Prints prerender dynamic value usage warning only if it's used
+
+- [#11774](https://github.com/withastro/astro/pull/11774) [`c6400ab`](https://github.com/withastro/astro/commit/c6400ab99c5e5f4477bc6ef7e801b7869b0aa9ab) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes the path returned by `injectTypes`
+
+- [#11730](https://github.com/withastro/astro/pull/11730) [`2df49a6`](https://github.com/withastro/astro/commit/2df49a6fb4f6d92fe45f7429430abe63defeacd6) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Simplifies path operations of `astro sync`
+
- [#11771](https://github.com/withastro/astro/pull/11771) [`49650a4`](https://github.com/withastro/astro/commit/49650a45550af46c70c6cf3f848b7b529103a649) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes an error thrown by `astro sync` when an `astro:env` virtual module is imported inside the Content Collections config
- [#11744](https://github.com/withastro/astro/pull/11744) [`b677429`](https://github.com/withastro/astro/commit/b67742961a384c10e5cd04cf5b02d0f014ea7362) Thanks [@bluwy](https://github.com/bluwy)! - Disables the WebSocket server when creating a Vite server for loading config files
diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts
index 8a61150c7..060b25b8f 100644
--- a/packages/astro/client.d.ts
+++ b/packages/astro/client.d.ts
@@ -145,6 +145,9 @@ declare module 'astro:transitions/client' {
import('./dist/virtual-modules/transitions-events.js').TransitionBeforeSwapEvent;
export const isTransitionBeforePreparationEvent: EventModule['isTransitionBeforePreparationEvent'];
export const isTransitionBeforeSwapEvent: EventModule['isTransitionBeforeSwapEvent'];
+ type TransitionSwapFunctionModule =
+ typeof import('./dist/virtual-modules/transitions-swap-functions.js');
+ export const swapFunctions: TransitionSwapFunctionModule['swapFunctions'];
}
declare module 'astro:prefetch' {
diff --git a/packages/astro/e2e/actions-blog.test.js b/packages/astro/e2e/actions-blog.test.js
index e3a8c7cf8..d9c1bc1df 100644
--- a/packages/astro/e2e/actions-blog.test.js
+++ b/packages/astro/e2e/actions-blog.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
-import { testFactory } from './test-utils.js';
+import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/actions-blog/' });
+const test = testFactory(import.meta.url, { root: './fixtures/actions-blog/' });
let devServer;
@@ -23,6 +23,7 @@ test.describe('Astro Actions - Blog', () => {
await page.goto(astro.resolveUrl('/blog/first-post/'));
const likeButton = page.getByLabel('Like');
+ await waitForHydrate(page, likeButton);
await expect(likeButton, 'like button starts with 10 likes').toContainText('10');
await likeButton.click();
await expect(likeButton, 'like button should increment likes').toContainText('11');
@@ -125,4 +126,13 @@ test.describe('Astro Actions - Blog', () => {
await expect(comments).toBeVisible();
await expect(comments).toContainText(body);
});
+
+ test('Logout action redirects', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/blog/first-post/'));
+
+ const logoutButton = page.getByTestId('logout-button');
+ await waitForHydrate(page, logoutButton);
+ await logoutButton.click();
+ await expect(page).toHaveURL(astro.resolveUrl('/blog/'));
+ });
});
diff --git a/packages/astro/e2e/actions-react-19.test.js b/packages/astro/e2e/actions-react-19.test.js
index 5ce72a419..3298db1e3 100644
--- a/packages/astro/e2e/actions-react-19.test.js
+++ b/packages/astro/e2e/actions-react-19.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
-import { testFactory } from './test-utils.js';
+import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/actions-react-19/' });
+const test = testFactory(import.meta.url, { root: './fixtures/actions-react-19/' });
let devServer;
@@ -23,10 +23,12 @@ test.describe('Astro Actions - React 19', () => {
await page.goto(astro.resolveUrl('/blog/first-post/'));
const likeButton = page.getByLabel('likes-client');
+ await waitForHydrate(page, likeButton);
+
await expect(likeButton).toBeVisible();
await likeButton.click();
await expect(likeButton, 'like button should be disabled when pending').toBeDisabled();
- await expect(likeButton).not.toBeDisabled({ timeout: 5000 });
+ await expect(likeButton).not.toBeDisabled();
});
test('Like action - server progressive enhancement', async ({ page, astro }) => {
@@ -43,6 +45,8 @@ test.describe('Astro Actions - React 19', () => {
await page.goto(astro.resolveUrl('/blog/first-post/'));
const likeButton = page.getByLabel('likes-action-client');
+ await waitForHydrate(page, likeButton);
+
await expect(likeButton).toBeVisible();
await likeButton.click();
diff --git a/packages/astro/e2e/astro-component.test.js b/packages/astro/e2e/astro-component.test.js
index b77cbffa1..3a3c738b3 100644
--- a/packages/astro/e2e/astro-component.test.js
+++ b/packages/astro/e2e/astro-component.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/astro-component/' });
+const test = testFactory(import.meta.url, { root: './fixtures/astro-component/' });
let devServer;
diff --git a/packages/astro/e2e/astro-envs.test.js b/packages/astro/e2e/astro-envs.test.js
index f6f3c5031..60baa65a0 100644
--- a/packages/astro/e2e/astro-envs.test.js
+++ b/packages/astro/e2e/astro-envs.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/astro-envs/',
devToolbar: {
enabled: false,
diff --git a/packages/astro/e2e/client-idle-timeout.test.js b/packages/astro/e2e/client-idle-timeout.test.js
new file mode 100644
index 000000000..a39870619
--- /dev/null
+++ b/packages/astro/e2e/client-idle-timeout.test.js
@@ -0,0 +1,33 @@
+import { expect } from '@playwright/test';
+import { testFactory, waitForHydrate } from './test-utils.js';
+
+const test = testFactory(import.meta.url, { root: './fixtures/client-idle-timeout/' });
+
+let devServer;
+
+test.beforeAll(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+});
+
+test.afterAll(async () => {
+ await devServer.stop();
+});
+
+test.describe('Client idle timeout', () => {
+ test('React counter', async ({ astro, page }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ const counter = page.locator('#react-counter');
+ await expect(counter, 'component is visible').toBeVisible();
+
+ const count = counter.locator('pre');
+ await expect(count, 'initial count is 0').toHaveText('0');
+
+ await waitForHydrate(page, counter);
+
+ const inc = counter.locator('.increment');
+ await inc.click();
+
+ await expect(count, 'count incremented by 1').toHaveText('1');
+ });
+});
diff --git a/packages/astro/e2e/client-only.test.js b/packages/astro/e2e/client-only.test.js
index 08c5fb3ac..62a05f8dc 100644
--- a/packages/astro/e2e/client-only.test.js
+++ b/packages/astro/e2e/client-only.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/client-only/' });
+const test = testFactory(import.meta.url, { root: './fixtures/client-only/' });
let devServer;
diff --git a/packages/astro/e2e/content-collections.test.js b/packages/astro/e2e/content-collections.test.js
index 63c5077c9..fdb8d5e00 100644
--- a/packages/astro/e2e/content-collections.test.js
+++ b/packages/astro/e2e/content-collections.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/content-collections/' });
+const test = testFactory(import.meta.url, { root: './fixtures/content-collections/' });
let devServer;
diff --git a/packages/astro/e2e/css.test.js b/packages/astro/e2e/css.test.js
index fd4de700e..f865969f7 100644
--- a/packages/astro/e2e/css.test.js
+++ b/packages/astro/e2e/css.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/css/',
devToolbar: {
enabled: false,
diff --git a/packages/astro/e2e/custom-client-directives.test.js b/packages/astro/e2e/custom-client-directives.test.js
index ef9de808a..8f90916f2 100644
--- a/packages/astro/e2e/custom-client-directives.test.js
+++ b/packages/astro/e2e/custom-client-directives.test.js
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import testAdapter from '../test/test-adapter.js';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/custom-client-directives/',
});
diff --git a/packages/astro/e2e/dev-toolbar-audits.test.js b/packages/astro/e2e/dev-toolbar-audits.test.js
index 6ef63cc1e..d0c5da847 100644
--- a/packages/astro/e2e/dev-toolbar-audits.test.js
+++ b/packages/astro/e2e/dev-toolbar-audits.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/dev-toolbar/',
});
diff --git a/packages/astro/e2e/dev-toolbar.test.js b/packages/astro/e2e/dev-toolbar.test.js
index ae8b6ef5c..628b3af3a 100644
--- a/packages/astro/e2e/dev-toolbar.test.js
+++ b/packages/astro/e2e/dev-toolbar.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/dev-toolbar/',
});
diff --git a/packages/astro/e2e/error-cyclic.test.js b/packages/astro/e2e/error-cyclic.test.js
index 84f4d1d1d..62b502fab 100644
--- a/packages/astro/e2e/error-cyclic.test.js
+++ b/packages/astro/e2e/error-cyclic.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { getErrorOverlayContent, testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/error-cyclic/',
});
diff --git a/packages/astro/e2e/error-sass.test.js b/packages/astro/e2e/error-sass.test.js
index 11862fb86..05774220e 100644
--- a/packages/astro/e2e/error-sass.test.js
+++ b/packages/astro/e2e/error-sass.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { getErrorOverlayContent, testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/error-sass/',
});
diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js
index 34cecd816..f64a22b5c 100644
--- a/packages/astro/e2e/errors.test.js
+++ b/packages/astro/e2e/errors.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { getErrorOverlayContent, testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/errors/',
// Only test the error overlay, don't print to console
vite: {
diff --git a/packages/astro/e2e/fixtures/actions-blog/package.json b/packages/astro/e2e/fixtures/actions-blog/package.json
index 04685b810..545ae2d37 100644
--- a/packages/astro/e2e/fixtures/actions-blog/package.json
+++ b/packages/astro/e2e/fixtures/actions-blog/package.json
@@ -14,7 +14,7 @@
"@astrojs/db": "workspace:*",
"@astrojs/node": "workspace:*",
"@astrojs/react": "workspace:*",
- "@types/react": "^18.3.3",
+ "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"astro": "workspace:*",
"react": "^18.3.1",
diff --git a/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts b/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
index 0588f626c..7b640be51 100644
--- a/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
+++ b/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
@@ -3,11 +3,16 @@ import { ActionError, defineAction, z } from 'astro:actions';
import { getCollection } from 'astro:content';
export const server = {
+ logout: defineAction({
+ handler: async () => {
+ await new Promise((r) => setTimeout(r, 500));
+ },
+ }),
blog: {
like: defineAction({
input: z.object({ postId: z.string() }),
handler: async ({ postId }) => {
- await new Promise((r) => setTimeout(r, 1000));
+ await new Promise((r) => setTimeout(r, 500));
const { likes } = await db
.update(Likes)
@@ -30,7 +35,7 @@ export const server = {
body: z.string().min(10),
}),
handler: async ({ postId, author, body }) => {
- if (!(await getCollection('blog')).find(b => b.id === postId)) {
+ if (!(await getCollection('blog')).find((b) => b.id === postId)) {
throw new ActionError({
code: 'NOT_FOUND',
message: 'Post not found',
diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/Logout.tsx b/packages/astro/e2e/fixtures/actions-blog/src/components/Logout.tsx
new file mode 100644
index 000000000..737718d91
--- /dev/null
+++ b/packages/astro/e2e/fixtures/actions-blog/src/components/Logout.tsx
@@ -0,0 +1,16 @@
+import { actions } from 'astro:actions';
+import { navigate } from 'astro:transitions/client';
+
+export function Logout() {
+ return (
+ <button
+ data-testid="logout-button"
+ onClick={async () => {
+ const { error } = await actions.logout();
+ if (!error) navigate('/blog/');
+ }}
+ >
+ Logout
+ </button>
+ );
+}
diff --git a/packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro b/packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro
index fe97a8de1..ad4aea521 100644
--- a/packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro
+++ b/packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro
@@ -1,6 +1,7 @@
---
import { type CollectionEntry, getCollection, getEntry } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
+import { Logout } from '../../components/Logout';
import { db, eq, Comment, Likes } from 'astro:db';
import { Like } from '../../components/Like';
import { PostComment } from '../../components/PostComment';
@@ -17,14 +18,13 @@ export async function getStaticPaths() {
}));
}
-
type Props = CollectionEntry<'blog'>;
const post = await getEntry('blog', Astro.params.slug)!;
const { Content } = await post.render();
if (Astro.url.searchParams.has('like')) {
- await Astro.callAction(actions.blog.like.orThrow, {postId: post.id});
+ await Astro.callAction(actions.blog.like.orThrow, { postId: post.id });
}
const comment = Astro.getActionResult(actions.blog.comment);
@@ -40,6 +40,8 @@ const commentPostIdOverride = Astro.url.searchParams.get('commentPostIdOverride'
<BlogPost {...post.data}>
<Like postId={post.id} initial={initialLikes?.likes ?? 0} client:load />
+ <Logout client:load />
+
<form>
<input type="hidden" name="like" />
<button type="submit" aria-label="get-request">Like GET request</button>
@@ -57,17 +59,17 @@ const commentPostIdOverride = Astro.url.searchParams.get('commentPostIdOverride'
/>
<form method="POST" data-testid="progressive-fallback" action={actions.blog.comment.queryString}>
<input type="hidden" name="postId" value={post.id} />
- <label for="fallback-author">
- Author
- </label>
- <input id="fallback-author" type="text" name="author" required />
- <label for="fallback-body" class="sr-only">
- Comment
- </label>
+ <label for="fallback-author"> Author </label>
+ <input id="fallback-author" type="text" name="author" required />
+ <label for="fallback-body" class="sr-only"> Comment </label>
<textarea id="fallback-body" rows={10} name="body" required></textarea>
- {isInputError(comment?.error) && comment.error.fields.body && (
- <p class="error" data-error="body">{comment.error.fields.body.toString()}</p>
- )}
+ {
+ isInputError(comment?.error) && comment.error.fields.body && (
+ <p class="error" data-error="body">
+ {comment.error.fields.body.toString()}
+ </p>
+ )
+ }
<button type="submit">Post Comment</button>
</form>
<div data-testid="server-comments">
diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs b/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs
new file mode 100644
index 000000000..02dccb978
--- /dev/null
+++ b/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs
@@ -0,0 +1,9 @@
+import react from '@astrojs/react';
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [
+ react(),
+ ],
+});
diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/package.json b/packages/astro/e2e/fixtures/client-idle-timeout/package.json
new file mode 100644
index 000000000..af4c41605
--- /dev/null
+++ b/packages/astro/e2e/fixtures/client-idle-timeout/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "@e2e/client-idle-timeout",
+ "version": "0.0.0",
+ "private": true,
+ "devDependencies": {
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ }
+}
diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx b/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx
new file mode 100644
index 000000000..9d2212b0c
--- /dev/null
+++ b/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx
@@ -0,0 +1,18 @@
+import React, { useState } from 'react';
+
+export default function Counter({ children, count: initialCount = 0, id }) {
+ const [count, setCount] = useState(initialCount);
+ const add = () => setCount((i) => i + 1);
+ const subtract = () => setCount((i) => i - 1);
+
+ return (
+ <>
+ <div id={id} className="counter">
+ <button className="decrement" onClick={subtract}>-</button>
+ <pre>{count}</pre>
+ <button className="increment" onClick={add}>+</button>
+ </div>
+ <div className="counter-message">{children}</div>
+ </>
+ );
+}
diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro b/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro
new file mode 100644
index 000000000..0045ca55c
--- /dev/null
+++ b/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro
@@ -0,0 +1,16 @@
+---
+import Counter from '../components/Counter.jsx';
+---
+
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
+ </head>
+ <body>
+ <main>
+ <Counter id="react-counter" client:idle={{timeout: 200}}></Counter>
+ </main>
+ </body>
+</html>
diff --git a/packages/astro/e2e/fixtures/client-only/package.json b/packages/astro/e2e/fixtures/client-only/package.json
index 4c9903c7b..d89fe7898 100644
--- a/packages/astro/e2e/fixtures/client-only/package.json
+++ b/packages/astro/e2e/fixtures/client-only/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/errors/package.json b/packages/astro/e2e/fixtures/errors/package.json
index 1d01a376f..ab47d7da8 100644
--- a/packages/astro/e2e/fixtures/errors/package.json
+++ b/packages/astro/e2e/fixtures/errors/package.json
@@ -13,8 +13,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.77.8",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/multiple-frameworks/package.json b/packages/astro/e2e/fixtures/multiple-frameworks/package.json
index 810e503f7..f90d9725d 100644
--- a/packages/astro/e2e/fixtures/multiple-frameworks/package.json
+++ b/packages/astro/e2e/fixtures/multiple-frameworks/package.json
@@ -16,8 +16,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-in-preact/package.json b/packages/astro/e2e/fixtures/nested-in-preact/package.json
index 339cdcbc1..c5f10cd43 100644
--- a/packages/astro/e2e/fixtures/nested-in-preact/package.json
+++ b/packages/astro/e2e/fixtures/nested-in-preact/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-in-react/package.json b/packages/astro/e2e/fixtures/nested-in-react/package.json
index 9573ef266..bac0d1bae 100644
--- a/packages/astro/e2e/fixtures/nested-in-react/package.json
+++ b/packages/astro/e2e/fixtures/nested-in-react/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-in-solid/package.json b/packages/astro/e2e/fixtures/nested-in-solid/package.json
index 6e71432f0..4a2748eb5 100644
--- a/packages/astro/e2e/fixtures/nested-in-solid/package.json
+++ b/packages/astro/e2e/fixtures/nested-in-solid/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/package.json b/packages/astro/e2e/fixtures/nested-in-svelte/package.json
index 1f319d028..a98e441f5 100644
--- a/packages/astro/e2e/fixtures/nested-in-svelte/package.json
+++ b/packages/astro/e2e/fixtures/nested-in-svelte/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-in-vue/package.json b/packages/astro/e2e/fixtures/nested-in-vue/package.json
index bd44589f3..1702f98bf 100644
--- a/packages/astro/e2e/fixtures/nested-in-vue/package.json
+++ b/packages/astro/e2e/fixtures/nested-in-vue/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/nested-recursive/package.json b/packages/astro/e2e/fixtures/nested-recursive/package.json
index ab809a3a3..d35cf0eb5 100644
--- a/packages/astro/e2e/fixtures/nested-recursive/package.json
+++ b/packages/astro/e2e/fixtures/nested-recursive/package.json
@@ -14,8 +14,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
},
"scripts": {
diff --git a/packages/astro/e2e/fixtures/solid-circular/package.json b/packages/astro/e2e/fixtures/solid-circular/package.json
index 21a0b66ba..aba997250 100644
--- a/packages/astro/e2e/fixtures/solid-circular/package.json
+++ b/packages/astro/e2e/fixtures/solid-circular/package.json
@@ -7,6 +7,6 @@
"astro": "workspace:*"
},
"devDependencies": {
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/e2e/fixtures/solid-circular/src/env.d.ts b/packages/astro/e2e/fixtures/solid-circular/src/env.d.ts
deleted file mode 100644
index 8c34fb45e..000000000
--- a/packages/astro/e2e/fixtures/solid-circular/src/env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="astro/client" /> \ No newline at end of file
diff --git a/packages/astro/e2e/fixtures/solid-component/package.json b/packages/astro/e2e/fixtures/solid-component/package.json
index 850a41587..86269a915 100644
--- a/packages/astro/e2e/fixtures/solid-component/package.json
+++ b/packages/astro/e2e/fixtures/solid-component/package.json
@@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/solid-js": "workspace:*",
"astro": "workspace:*",
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/e2e/fixtures/solid-recurse/package.json b/packages/astro/e2e/fixtures/solid-recurse/package.json
index aa51ebe5d..a7d97b39c 100644
--- a/packages/astro/e2e/fixtures/solid-recurse/package.json
+++ b/packages/astro/e2e/fixtures/solid-recurse/package.json
@@ -7,6 +7,6 @@
"astro": "workspace:*"
},
"devDependencies": {
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/e2e/fixtures/svelte-component/package.json b/packages/astro/e2e/fixtures/svelte-component/package.json
index e5d238f74..04acc35e7 100644
--- a/packages/astro/e2e/fixtures/svelte-component/package.json
+++ b/packages/astro/e2e/fixtures/svelte-component/package.json
@@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/e2e/fixtures/svelte-component/src/env.d.ts b/packages/astro/e2e/fixtures/svelte-component/src/env.d.ts
deleted file mode 100644
index 8c34fb45e..000000000
--- a/packages/astro/e2e/fixtures/svelte-component/src/env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="astro/client" /> \ No newline at end of file
diff --git a/packages/astro/e2e/fixtures/view-transitions/package.json b/packages/astro/e2e/fixtures/view-transitions/package.json
index b5460ebcd..0a0ad81f2 100644
--- a/packages/astro/e2e/fixtures/view-transitions/package.json
+++ b/packages/astro/e2e/fixtures/view-transitions/package.json
@@ -10,7 +10,7 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "svelte": "^4.2.18",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-style-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-style-one.astro
index 413a404d7..f71898d81 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-style-one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-style-one.astro
@@ -7,22 +7,22 @@ import Layout from '../components/Layout.astro';
</Layout>
<script>
- import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from '../../node_modules/astro/dist/transitions/swap-functions'
+ import { swapFunctions } from 'astro:transitions/client';
document.addEventListener('astro:before-swap', (e) => {
e.swap = () => keepStyle(e.newDocument)
});
function keepStyle(doc: Document) {
- deselectScripts(doc);
- swapRootAttributes(doc);
+ swapFunctions.deselectScripts(doc);
+ swapFunctions.swapRootAttributes(doc);
{
const dynamicStyle = document.head.querySelector('style:not(:empty)');
- swapHeadElements(doc);
+ swapFunctions.swapHeadElements(doc);
dynamicStyle && document.head.insertAdjacentElement('afterbegin', dynamicStyle);
}
- const restoreFocusFunction = saveFocus();
- swapBodyElement(doc.body, document.body)
+ const restoreFocusFunction = swapFunctions.saveFocus();
+ swapFunctions.swapBodyElement(doc.body, document.body)
restoreFocusFunction();
}
</script>
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-theme-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-theme-one.astro
index a4c942d58..18ae0221f 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-theme-one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/keep-theme-one.astro
@@ -6,18 +6,18 @@ import Layout from '../components/Layout.astro';
<a id="click" href="/keep-two">go to next page</a>
</Layout>
<script>
- import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from '../../node_modules/astro/dist/transitions/swap-functions'
+ import { swapFunctions } from 'astro:transitions/client';
function keepTheme(doc:Document) {
- deselectScripts(doc);
+ swapFunctions.deselectScripts(doc);
{
const theme = document.documentElement.getAttribute('data-theme')!;
- swapRootAttributes(doc);
+ swapFunctions.swapRootAttributes(doc);
document.documentElement.setAttribute('data-theme', theme);
}
- swapHeadElements(doc);
- const restoreFocusFunction = saveFocus();
- swapBodyElement(doc.body, document.body)
+ swapFunctions.swapHeadElements(doc);
+ const restoreFocusFunction = swapFunctions.saveFocus();
+ swapFunctions.swapBodyElement(doc.body, document.body)
restoreFocusFunction();
}
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/replace-main-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/replace-main-one.astro
index 1709661e8..c43ce78a9 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/replace-main-one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/replace-main-one.astro
@@ -9,24 +9,24 @@ import Layout from '../components/Layout.astro';
<a id="click" href="/keep-two">go to next page</a>
</Layout>
<script>
- import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from "../../node_modules/astro/dist/transitions/swap-functions"
+ import { swapFunctions } from 'astro:transitions/client';
document.addEventListener('astro:before-swap', (e) => {
e.swap = () => replaceMain(e.newDocument)
});
function replaceMain(doc:Document){
- deselectScripts(doc);
- swapRootAttributes(doc);
- swapHeadElements(doc);
- const restoreFocusFunction = saveFocus();
+ swapFunctions.deselectScripts(doc);
+ swapFunctions.swapRootAttributes(doc);
+ swapFunctions.swapHeadElements(doc);
+ const restoreFocusFunction = swapFunctions.saveFocus();
{
const newMain = doc.body.querySelector('main section');
const oldMain = document.body.querySelector('main section');
if (newMain && oldMain) {
- swapBodyElement(newMain, oldMain);
+ swapFunctions.swapBodyElement(newMain, oldMain);
} else {
- swapBodyElement(doc.body, document.body);
+ swapFunctions.swapBodyElement(doc.body, document.body);
}
}
restoreFocusFunction();
diff --git a/packages/astro/e2e/hmr.test.js b/packages/astro/e2e/hmr.test.js
index 72eaf8375..1f0cda2c4 100644
--- a/packages/astro/e2e/hmr.test.js
+++ b/packages/astro/e2e/hmr.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/hmr/',
devToolbar: {
enabled: false,
diff --git a/packages/astro/e2e/hydration-race.test.js b/packages/astro/e2e/hydration-race.test.js
index 0ee578243..95469fe73 100644
--- a/packages/astro/e2e/hydration-race.test.js
+++ b/packages/astro/e2e/hydration-race.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/hydration-race/',
});
diff --git a/packages/astro/e2e/i18n.test.js b/packages/astro/e2e/i18n.test.js
index e7d74a551..88d3a0b08 100644
--- a/packages/astro/e2e/i18n.test.js
+++ b/packages/astro/e2e/i18n.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/i18n/',
devToolbar: {
enabled: false,
diff --git a/packages/astro/e2e/multiple-frameworks.test.js b/packages/astro/e2e/multiple-frameworks.test.js
index 08a2fb648..4b52dfd4c 100644
--- a/packages/astro/e2e/multiple-frameworks.test.js
+++ b/packages/astro/e2e/multiple-frameworks.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/multiple-frameworks/' });
+const test = testFactory(import.meta.url, { root: './fixtures/multiple-frameworks/' });
let devServer;
diff --git a/packages/astro/e2e/namespaced-component.test.js b/packages/astro/e2e/namespaced-component.test.js
index 58c00713a..1212e0ce1 100644
--- a/packages/astro/e2e/namespaced-component.test.js
+++ b/packages/astro/e2e/namespaced-component.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/namespaced-component/',
});
diff --git a/packages/astro/e2e/nested-in-preact.test.js b/packages/astro/e2e/nested-in-preact.test.js
index f2bc4d728..2ad62e95f 100644
--- a/packages/astro/e2e/nested-in-preact.test.js
+++ b/packages/astro/e2e/nested-in-preact.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/nested-in-preact/' });
+const test = testFactory(import.meta.url, { root: './fixtures/nested-in-preact/' });
let devServer;
diff --git a/packages/astro/e2e/nested-in-react.test.js b/packages/astro/e2e/nested-in-react.test.js
index db9eeb0dd..7dee69937 100644
--- a/packages/astro/e2e/nested-in-react.test.js
+++ b/packages/astro/e2e/nested-in-react.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/nested-in-react/' });
+const test = testFactory(import.meta.url, { root: './fixtures/nested-in-react/' });
let devServer;
diff --git a/packages/astro/e2e/nested-in-solid.test.js b/packages/astro/e2e/nested-in-solid.test.js
index 0fab17468..2d9deade1 100644
--- a/packages/astro/e2e/nested-in-solid.test.js
+++ b/packages/astro/e2e/nested-in-solid.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/nested-in-solid/' });
+const test = testFactory(import.meta.url, { root: './fixtures/nested-in-solid/' });
let devServer;
diff --git a/packages/astro/e2e/nested-in-svelte.test.js b/packages/astro/e2e/nested-in-svelte.test.js
index 88aa826a8..eeecb0442 100644
--- a/packages/astro/e2e/nested-in-svelte.test.js
+++ b/packages/astro/e2e/nested-in-svelte.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/nested-in-svelte/' });
+const test = testFactory(import.meta.url, { root: './fixtures/nested-in-svelte/' });
let devServer;
diff --git a/packages/astro/e2e/nested-in-vue.test.js b/packages/astro/e2e/nested-in-vue.test.js
index deed309c7..7e25e4747 100644
--- a/packages/astro/e2e/nested-in-vue.test.js
+++ b/packages/astro/e2e/nested-in-vue.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/nested-in-vue/' });
+const test = testFactory(import.meta.url, { root: './fixtures/nested-in-vue/' });
let devServer;
diff --git a/packages/astro/e2e/nested-recursive.test.js b/packages/astro/e2e/nested-recursive.test.js
index 262cd6772..d9f612642 100644
--- a/packages/astro/e2e/nested-recursive.test.js
+++ b/packages/astro/e2e/nested-recursive.test.js
@@ -3,7 +3,7 @@ import { loadFixture, waitForHydrate } from './test-utils.js';
const test = base.extend({
astro: async ({}, use) => {
- const fixture = await loadFixture({ root: './fixtures/nested-recursive/' });
+ const fixture = await loadFixture(import.meta.url, { root: './fixtures/nested-recursive/' });
await use(fixture);
},
});
diff --git a/packages/astro/e2e/nested-styles.test.js b/packages/astro/e2e/nested-styles.test.js
index c482570f0..1a3aeb0e0 100644
--- a/packages/astro/e2e/nested-styles.test.js
+++ b/packages/astro/e2e/nested-styles.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/nested-styles/',
devToolbar: {
enabled: false,
diff --git a/packages/astro/e2e/pass-js.test.js b/packages/astro/e2e/pass-js.test.js
index 0db9895d1..91e3b5c5e 100644
--- a/packages/astro/e2e/pass-js.test.js
+++ b/packages/astro/e2e/pass-js.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/pass-js/',
});
diff --git a/packages/astro/e2e/preact-compat-component.test.js b/packages/astro/e2e/preact-compat-component.test.js
index e1b603e7f..83831c9e7 100644
--- a/packages/astro/e2e/preact-compat-component.test.js
+++ b/packages/astro/e2e/preact-compat-component.test.js
@@ -1,6 +1,8 @@
import { prepareTestFactory } from './shared-component-tests.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/preact-compat-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/preact-compat-component/',
+});
const config = {
counterComponentFilePath: './src/components/Counter.jsx',
diff --git a/packages/astro/e2e/preact-component.test.js b/packages/astro/e2e/preact-component.test.js
index d808b4890..3beb14da3 100644
--- a/packages/astro/e2e/preact-component.test.js
+++ b/packages/astro/e2e/preact-component.test.js
@@ -1,6 +1,8 @@
import { prepareTestFactory } from './shared-component-tests.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/preact-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/preact-component/',
+});
const config = {
counterComponentFilePath: './src/components/Counter.jsx',
diff --git a/packages/astro/e2e/preact-lazy-component.test.js b/packages/astro/e2e/preact-lazy-component.test.js
index 585d2d347..ce0e3afec 100644
--- a/packages/astro/e2e/preact-lazy-component.test.js
+++ b/packages/astro/e2e/preact-lazy-component.test.js
@@ -1,6 +1,8 @@
import { prepareTestFactory } from './shared-component-tests.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/preact-lazy-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/preact-lazy-component/',
+});
const config = {
counterComponentFilePath: './src/components/Counter.jsx',
diff --git a/packages/astro/e2e/prefetch.test.js b/packages/astro/e2e/prefetch.test.js
index 84ead590c..2da44189a 100644
--- a/packages/astro/e2e/prefetch.test.js
+++ b/packages/astro/e2e/prefetch.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({
+const test = testFactory(import.meta.url, {
root: './fixtures/prefetch/',
});
diff --git a/packages/astro/e2e/react-component.test.js b/packages/astro/e2e/react-component.test.js
index 361ee8d69..4585887ad 100644
--- a/packages/astro/e2e/react-component.test.js
+++ b/packages/astro/e2e/react-component.test.js
@@ -1,7 +1,9 @@
import { expect } from '@playwright/test';
import { prepareTestFactory } from './shared-component-tests.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/react-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/react-component/',
+});
const config = {
counterComponentFilePath: './src/components/Counter.jsx',
diff --git a/packages/astro/e2e/server-islands.test.js b/packages/astro/e2e/server-islands.test.js
index b45a58cd8..24ac7b918 100644
--- a/packages/astro/e2e/server-islands.test.js
+++ b/packages/astro/e2e/server-islands.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/server-islands/' });
+const test = testFactory(import.meta.url, { root: './fixtures/server-islands/' });
test.describe('Server islands', () => {
test.describe('Development', () => {
diff --git a/packages/astro/e2e/shared-component-tests.js b/packages/astro/e2e/shared-component-tests.js
index 024f1aade..ea45da13b 100644
--- a/packages/astro/e2e/shared-component-tests.js
+++ b/packages/astro/e2e/shared-component-tests.js
@@ -1,8 +1,8 @@
import { expect } from '@playwright/test';
import { scrollToElement, testFactory, waitForHydrate } from './test-utils.js';
-export function prepareTestFactory(opts, { canReplayClicks = false } = {}) {
- const test = testFactory(opts);
+export function prepareTestFactory(testFile, opts, { canReplayClicks = false } = {}) {
+ const test = testFactory(testFile, opts);
let devServer;
@@ -120,6 +120,7 @@ export function prepareTestFactory(opts, { canReplayClicks = false } = {}) {
await page.goto(astro.resolveUrl(pageUrl));
const label = page.locator('#client-only');
+ await waitForHydrate(page, label);
await expect(label, 'component is visible').toBeVisible();
await expect(label, 'slot text is visible').toHaveText('Framework client:only component');
diff --git a/packages/astro/e2e/solid-circular.test.js b/packages/astro/e2e/solid-circular.test.js
index 5dd0e8b80..796800a21 100644
--- a/packages/astro/e2e/solid-circular.test.js
+++ b/packages/astro/e2e/solid-circular.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/solid-circular/' });
+const test = testFactory(import.meta.url, { root: './fixtures/solid-circular/' });
let devServer;
diff --git a/packages/astro/e2e/solid-component.test.js b/packages/astro/e2e/solid-component.test.js
index b998d1873..6a934b1c4 100644
--- a/packages/astro/e2e/solid-component.test.js
+++ b/packages/astro/e2e/solid-component.test.js
@@ -1,6 +1,7 @@
import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory(
+ import.meta.url,
{ root: './fixtures/solid-component/' },
{
canReplayClicks: true,
diff --git a/packages/astro/e2e/solid-recurse.test.js b/packages/astro/e2e/solid-recurse.test.js
index de3759e98..eb0fa3770 100644
--- a/packages/astro/e2e/solid-recurse.test.js
+++ b/packages/astro/e2e/solid-recurse.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/solid-recurse/' });
+const test = testFactory(import.meta.url, { root: './fixtures/solid-recurse/' });
let devServer;
diff --git a/packages/astro/e2e/svelte-component.test.js b/packages/astro/e2e/svelte-component.test.js
index 01c7aa41d..d72235797 100644
--- a/packages/astro/e2e/svelte-component.test.js
+++ b/packages/astro/e2e/svelte-component.test.js
@@ -2,7 +2,9 @@ import { expect } from '@playwright/test';
import { prepareTestFactory } from './shared-component-tests.js';
import { waitForHydrate } from './test-utils.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/svelte-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/svelte-component/',
+});
const config = {
componentFilePath: './src/components/SvelteComponent.svelte',
diff --git a/packages/astro/e2e/tailwindcss.test.js b/packages/astro/e2e/tailwindcss.test.js
index e58e10dfd..c86d01b0e 100644
--- a/packages/astro/e2e/tailwindcss.test.js
+++ b/packages/astro/e2e/tailwindcss.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
-const test = testFactory({ root: './fixtures/tailwindcss/' });
+const test = testFactory(import.meta.url, { root: './fixtures/tailwindcss/' });
let devServer;
diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js
index 48fcd17bd..933186a71 100644
--- a/packages/astro/e2e/test-utils.js
+++ b/packages/astro/e2e/test-utils.js
@@ -14,30 +14,40 @@ const testFileToPort = new Map();
for (let i = 0; i < testFiles.length; i++) {
const file = testFiles[i];
if (file.endsWith('.test.js')) {
- testFileToPort.set(file.slice(0, -8), 4000 + i);
+ testFileToPort.set(file, 4000 + i);
}
}
-export function loadFixture(inlineConfig) {
+export function loadFixture(testFile, inlineConfig) {
if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
+ const port = testFileToPort.get(path.basename(testFile));
+
// resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath
// without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
return baseLoadFixture({
...inlineConfig,
root: fileURLToPath(new URL(inlineConfig.root, import.meta.url)),
server: {
- port: testFileToPort.get(path.basename(inlineConfig.root)),
+ ...inlineConfig?.server,
+ port,
+ },
+ vite: {
+ ...inlineConfig?.vite,
+ server: {
+ ...inlineConfig?.vite?.server,
+ strictPort: true,
+ },
},
});
}
-export function testFactory(inlineConfig) {
+export function testFactory(testFile, inlineConfig) {
let fixture;
const test = testBase.extend({
astro: async ({}, use) => {
- fixture = fixture || (await loadFixture(inlineConfig));
+ fixture = fixture || (await loadFixture(testFile, inlineConfig));
await use(fixture);
},
});
diff --git a/packages/astro/e2e/ts-resolution.test.js b/packages/astro/e2e/ts-resolution.test.js
index d2d3fcfe8..256269542 100644
--- a/packages/astro/e2e/ts-resolution.test.js
+++ b/packages/astro/e2e/ts-resolution.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/ts-resolution/' });
+const test = testFactory(import.meta.url, { root: './fixtures/ts-resolution/' });
function runTest(it) {
it('client:idle', async ({ page, astro }) => {
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index 135ec5571..181994cfa 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
-const test = testFactory({ root: './fixtures/view-transitions/' });
+const test = testFactory(import.meta.url, { root: './fixtures/view-transitions/' });
let devServer;
@@ -447,7 +447,9 @@ test.describe('View Transitions', () => {
expect(consoleCount).toEqual(1);
// forward '' to 'hash' (no transition)
- await page.goForward();
+ // NOTE: the networkidle below is needed for Firefox to consistently
+ // pass the `#longpage` viewport check below
+ await page.goForward({ waitUntil: 'networkidle' });
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
expect(consoleCount).toEqual(1);
@@ -1445,7 +1447,7 @@ test.describe('View Transitions', () => {
await page.click('#click');
await expect(page.locator('#name'), 'should have content').toHaveText('Keep 2');
- const styleElement = await page.$('head > style');
+ const styleElement = await page.$('head > style:nth-child(1)');
const styleContent = await page.evaluate((style) => style.innerHTML, styleElement);
expect(styleContent).toBe('body { background-color: purple; }');
});
diff --git a/packages/astro/e2e/vue-component.test.js b/packages/astro/e2e/vue-component.test.js
index 3be31af85..034fe5a6e 100644
--- a/packages/astro/e2e/vue-component.test.js
+++ b/packages/astro/e2e/vue-component.test.js
@@ -1,6 +1,8 @@
import { expect } from '@playwright/test';
import { prepareTestFactory } from './shared-component-tests.js';
-const { test, createTests } = prepareTestFactory({ root: './fixtures/vue-component/' });
+const { test, createTests } = prepareTestFactory(import.meta.url, {
+ root: './fixtures/vue-component/',
+});
const config = {
componentFilePath: './src/components/VueComponent.vue',
diff --git a/packages/astro/package.json b/packages/astro/package.json
index c15d9aba2..a448146ee 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -125,10 +125,9 @@
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
- "@babel/generator": "^7.25.0",
- "@babel/parser": "^7.25.3",
- "@babel/traverse": "^7.25.3",
- "@babel/types": "^7.25.2",
+ "@babel/core": "^7.25.2",
+ "@babel/plugin-transform-react-jsx": "^7.25.2",
+ "@babel/types": "^7.25.4",
"@oslojs/encoding": "^0.4.1",
"@rollup/pluginutils": "^5.1.0",
"@types/cookie": "^0.6.0",
@@ -150,7 +149,6 @@
"es-module-lexer": "^1.5.4",
"esbuild": "^0.21.5",
"estree-walker": "^3.0.3",
- "execa": "^8.0.1",
"fast-glob": "^3.3.2",
"flattie": "^1.1.1",
"github-slugger": "^2.0.0",
@@ -160,10 +158,11 @@
"js-yaml": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.11",
- "micromatch": "^4.0.7",
+ "magicast": "^0.3.5",
+ "micromatch": "^4.0.8",
"mrmime": "^2.0.0",
"neotraverse": "^0.6.18",
- "ora": "^8.0.1",
+ "ora": "^8.1.0",
"p-limit": "^6.1.0",
"p-queue": "^8.0.1",
"path-to-regexp": "^6.2.2",
@@ -174,10 +173,11 @@
"shiki": "^1.14.1",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
+ "tinyexec": "^0.3.0",
"tsconfck": "^3.1.1",
"unist-util-visit": "^5.0.0",
- "vfile": "^6.0.2",
- "vite": "^5.4.1",
+ "vfile": "^6.0.3",
+ "vite": "^5.4.2",
"vitefu": "^0.2.5",
"which-pm": "^3.0.0",
"xxhash-wasm": "^1.0.2",
@@ -212,9 +212,10 @@
"astro-scripts": "workspace:*",
"cheerio": "1.0.0",
"eol": "^0.9.1",
- "expect-type": "^0.19.0",
+ "execa": "^8.0.1",
+ "expect-type": "^0.20.0",
"mdast-util-mdx": "^3.0.0",
- "mdast-util-mdx-jsx": "^3.1.2",
+ "mdast-util-mdx-jsx": "^3.1.3",
"memfs": "^4.11.1",
"node-mocks-http": "^1.15.1",
"parse-srcset": "^1.0.2",
@@ -222,9 +223,9 @@
"rehype-slug": "^6.0.0",
"rehype-toc": "^3.0.2",
"remark-code-titles": "^0.1.2",
- "rollup": "^4.21.0",
+ "rollup": "^4.21.1",
"sass": "^1.77.8",
- "undici": "^6.19.7",
+ "undici": "^6.19.8",
"unified": "^11.0.5"
},
"engines": {
diff --git a/packages/astro/performance/fixtures/md/package.json b/packages/astro/performance/fixtures/md/package.json
index d51beb903..90db727e4 100644
--- a/packages/astro/performance/fixtures/md/package.json
+++ b/packages/astro/performance/fixtures/md/package.json
@@ -16,7 +16,7 @@
"dependencies": {
"@astrojs/react": "workspace:*",
"@performance/utils": "workspace:*",
- "@types/react": "^18.3.3",
+ "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"astro": "workspace:*",
"react": "^18.3.1",
diff --git a/packages/astro/performance/fixtures/mdoc/package.json b/packages/astro/performance/fixtures/mdoc/package.json
index 406c9b1d4..5bcd443ae 100644
--- a/packages/astro/performance/fixtures/mdoc/package.json
+++ b/packages/astro/performance/fixtures/mdoc/package.json
@@ -17,7 +17,7 @@
"@astrojs/markdoc": "workspace:*",
"@astrojs/react": "workspace:*",
"@performance/utils": "workspace:*",
- "@types/react": "^18.3.3",
+ "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"astro": "workspace:*",
"react": "^18.3.1",
diff --git a/packages/astro/performance/fixtures/mdoc/src/env.d.ts b/packages/astro/performance/fixtures/mdoc/src/env.d.ts
deleted file mode 100644
index acef35f17..000000000
--- a/packages/astro/performance/fixtures/mdoc/src/env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/// <reference path="../.astro/types.d.ts" />
-/// <reference types="astro/client" />
diff --git a/packages/astro/performance/fixtures/mdx/package.json b/packages/astro/performance/fixtures/mdx/package.json
index 973bfd530..e1fc69d0d 100644
--- a/packages/astro/performance/fixtures/mdx/package.json
+++ b/packages/astro/performance/fixtures/mdx/package.json
@@ -17,7 +17,7 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/react": "workspace:*",
"@performance/utils": "workspace:*",
- "@types/react": "^18.3.3",
+ "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"astro": "workspace:*",
"react": "^18.3.1",
diff --git a/packages/astro/performance/fixtures/mdx/src/env.d.ts b/packages/astro/performance/fixtures/mdx/src/env.d.ts
deleted file mode 100644
index acef35f17..000000000
--- a/packages/astro/performance/fixtures/mdx/src/env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/// <reference path="../.astro/types.d.ts" />
-/// <reference types="astro/client" />
diff --git a/packages/astro/playwright.config.js b/packages/astro/playwright.config.js
index 5aacd6d01..26572c66c 100644
--- a/packages/astro/playwright.config.js
+++ b/packages/astro/playwright.config.js
@@ -1,4 +1,5 @@
import { defineConfig } from '@playwright/test';
+
// NOTE: Sometimes, tests fail with `TypeError: process.stdout.clearLine is not a function`
// for some reason. This comes from Vite, and is conditionally called based on `isTTY`.
// We set it to false here to skip this odd behavior.
@@ -6,35 +7,16 @@ process.stdout.isTTY = false;
export default defineConfig({
testMatch: 'e2e/*.test.js',
- /* Maximum time one test can run for. */
- timeout: 40 * 1000,
- expect: {
- /**
- * Maximum time expect() should wait for the condition to be met.
- * For example in `await expect(locator).toHaveText();`
- */
- timeout: 4 * 1000,
- },
- /* Fail the build on CI if you accidentally left test in the source code. */
+ timeout: 40000,
forbidOnly: !!process.env.CI,
- /* Retry on CI only */
- retries: process.env.CI ? 3 : 0,
- /* Opt out of parallel tests on CI. */
- workers: 1,
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
- actionTimeout: 0,
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: 'on-first-retry',
- },
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
projects: [
{
name: 'Chrome Stable',
use: {
browserName: 'chromium',
channel: 'chrome',
- args: ['--use-gl=egl'],
},
},
],
diff --git a/packages/astro/playwright.firefox.config.js b/packages/astro/playwright.firefox.config.js
index 537bb4099..00b82d999 100644
--- a/packages/astro/playwright.firefox.config.js
+++ b/packages/astro/playwright.firefox.config.js
@@ -1,43 +1,24 @@
+import { defineConfig } from '@playwright/test';
+
// NOTE: Sometimes, tests fail with `TypeError: process.stdout.clearLine is not a function`
// for some reason. This comes from Vite, and is conditionally called based on `isTTY`.
// We set it to false here to skip this odd behavior.
process.stdout.isTTY = false;
-const config = {
+export default defineConfig({
// TODO: add more tests like view transitions and audits, and fix them. Some of them are failing.
testMatch: ['e2e/css.test.js', 'e2e/prefetch.test.js', 'e2e/view-transitions.test.js'],
- /* Maximum time one test can run for. */
- timeout: 40 * 1000,
- expect: {
- /**
- * Maximum time expect() should wait for the condition to be met.
- * For example in `await expect(locator).toHaveText();`
- */
- timeout: 4 * 1000,
- },
- /* Fail the build on CI if you accidentally left test in the source code. */
+ timeout: 40000,
forbidOnly: !!process.env.CI,
- /* Retry on CI only */
- retries: process.env.CI ? 3 : 0,
- /* Opt out of parallel tests on CI. */
- workers: 1,
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
- actionTimeout: 0,
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: 'on-first-retry',
- },
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
projects: [
{
name: 'Firefox Stable',
use: {
browserName: 'firefox',
channel: 'firefox',
- args: ['--use-gl=egl'],
},
},
],
-};
-
-export default config;
+});
diff --git a/packages/astro/src/actions/runtime/middleware.ts b/packages/astro/src/actions/runtime/middleware.ts
index b6f3221b5..b7cb4861c 100644
--- a/packages/astro/src/actions/runtime/middleware.ts
+++ b/packages/astro/src/actions/runtime/middleware.ts
@@ -23,8 +23,18 @@ export type Locals = {
};
export const onRequest = defineMiddleware(async (context, next) => {
+ if ((context as any)._isPrerendered) {
+ if (context.request.method === 'POST') {
+ // eslint-disable-next-line no-console
+ console.warn(
+ yellow('[astro:actions]'),
+ 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".',
+ );
+ }
+ return next();
+ }
+
const locals = context.locals as Locals;
- const { request } = context;
// Actions middleware may have run already after a path rewrite.
// See https://github.com/withastro/roadmap/blob/feat/reroute/proposals/0047-rerouting.md#ctxrewrite
// `_actionPayload` is the same for every page,
@@ -39,16 +49,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
return renderResult({ context, next, ...actionPayload });
}
- // Heuristic: If body is null, Astro might've reset this for prerendering.
- if (import.meta.env.DEV && request.method === 'POST' && request.body === null) {
- // eslint-disable-next-line no-console
- console.warn(
- yellow('[astro:actions]'),
- 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".',
- );
- return next();
- }
-
const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
if (context.request.method === 'POST' && actionName) {
@@ -93,7 +93,11 @@ async function handlePost({
context,
next,
actionName,
-}: { context: APIContext; next: MiddlewareNext; actionName: string }) {
+}: {
+ context: APIContext;
+ next: MiddlewareNext;
+ actionName: string;
+}) {
const { request } = context;
const baseAction = await getAction(actionName);
diff --git a/packages/astro/src/actions/runtime/utils.ts b/packages/astro/src/actions/runtime/utils.ts
index 199809d4e..d8b339a09 100644
--- a/packages/astro/src/actions/runtime/utils.ts
+++ b/packages/astro/src/actions/runtime/utils.ts
@@ -10,7 +10,10 @@ export function hasContentType(contentType: string, expected: string[]) {
return expected.some((t) => type === t);
}
-export type ActionAPIContext = Omit<APIContext, 'getActionResult' | 'callAction' | 'props'>;
+export type ActionAPIContext = Omit<
+ APIContext,
+ 'getActionResult' | 'callAction' | 'props' | 'redirect'
+>;
export type MaybePromise<T> = T | Promise<T>;
/**
diff --git a/packages/astro/src/actions/runtime/virtual/server.ts b/packages/astro/src/actions/runtime/virtual/server.ts
index 9bc387d6b..fcb0dc603 100644
--- a/packages/astro/src/actions/runtime/virtual/server.ts
+++ b/packages/astro/src/actions/runtime/virtual/server.ts
@@ -9,9 +9,6 @@ export * from './shared.js';
export { z } from 'zod';
export type ActionAccept = 'form' | 'json';
-export type ActionInputSchema<T extends ActionAccept | undefined> = T extends 'form'
- ? z.AnyZodObject | z.ZodType<FormData>
- : z.ZodType;
export type ActionHandler<TInputSchema, TOutput> = TInputSchema extends z.ZodType
? (input: z.infer<TInputSchema>, context: ActionAPIContext) => MaybePromise<TOutput>
@@ -22,7 +19,7 @@ export type ActionReturnType<T extends ActionHandler<any, any>> = Awaited<Return
export type ActionClient<
TOutput,
TAccept extends ActionAccept | undefined,
- TInputSchema extends ActionInputSchema<TAccept> | undefined,
+ TInputSchema extends z.ZodType | undefined,
> = TInputSchema extends z.ZodType
? ((
input: TAccept extends 'form' ? FormData : z.input<TInputSchema>,
@@ -46,7 +43,7 @@ export type ActionClient<
export function defineAction<
TOutput,
TAccept extends ActionAccept | undefined = undefined,
- TInputSchema extends ActionInputSchema<ActionAccept> | undefined = TAccept extends 'form'
+ TInputSchema extends z.ZodType | undefined = TAccept extends 'form'
? // If `input` is omitted, default to `FormData` for forms and `any` for JSON.
z.ZodType<FormData>
: undefined,
@@ -83,7 +80,7 @@ export function defineAction<
return safeServerHandler as ActionClient<TOutput, TAccept, TInputSchema> & string;
}
-function getFormServerHandler<TOutput, TInputSchema extends ActionInputSchema<'form'>>(
+function getFormServerHandler<TOutput, TInputSchema extends z.ZodType>(
handler: ActionHandler<TInputSchema, TOutput>,
inputSchema?: TInputSchema,
) {
@@ -95,9 +92,14 @@ function getFormServerHandler<TOutput, TInputSchema extends ActionInputSchema<'f
});
}
- if (!(inputSchema instanceof z.ZodObject)) return await handler(unparsedInput, context);
+ if (!inputSchema) return await handler(unparsedInput, context);
- const parsed = await inputSchema.safeParseAsync(formDataToObject(unparsedInput, inputSchema));
+ const baseSchema = unwrapSchemaEffects(inputSchema);
+ const parsed = await inputSchema.safeParseAsync(
+ baseSchema instanceof z.ZodObject
+ ? formDataToObject(unparsedInput, baseSchema)
+ : unparsedInput,
+ );
if (!parsed.success) {
throw new ActionInputError(parsed.error.issues);
}
@@ -105,7 +107,7 @@ function getFormServerHandler<TOutput, TInputSchema extends ActionInputSchema<'f
};
}
-function getJsonServerHandler<TOutput, TInputSchema extends ActionInputSchema<'json'>>(
+function getJsonServerHandler<TOutput, TInputSchema extends z.ZodType>(
handler: ActionHandler<TInputSchema, TOutput>,
inputSchema?: TInputSchema,
) {
@@ -131,7 +133,8 @@ export function formDataToObject<T extends z.AnyZodObject>(
formData: FormData,
schema: T,
): Record<string, unknown> {
- const obj: Record<string, unknown> = {};
+ const obj: Record<string, unknown> =
+ schema._def.unknownKeys === 'passthrough' ? Object.fromEntries(formData.entries()) : {};
for (const [key, baseValidator] of Object.entries(schema.shape)) {
let validator = baseValidator;
@@ -189,3 +192,15 @@ function handleFormDataGet(
}
return validator instanceof z.ZodNumber ? Number(value) : value;
}
+
+function unwrapSchemaEffects(schema: z.ZodType) {
+ while (schema instanceof z.ZodEffects || schema instanceof z.ZodPipeline) {
+ if (schema instanceof z.ZodEffects) {
+ schema = schema._def.schema;
+ }
+ if (schema instanceof z.ZodPipeline) {
+ schema = schema._def.in;
+ }
+ }
+ return schema;
+}
diff --git a/packages/astro/src/actions/runtime/virtual/shared.ts b/packages/astro/src/actions/runtime/virtual/shared.ts
index d792a9af5..01f9bd4e6 100644
--- a/packages/astro/src/actions/runtime/virtual/shared.ts
+++ b/packages/astro/src/actions/runtime/virtual/shared.ts
@@ -1,8 +1,16 @@
import { parse as devalueParse, stringify as devalueStringify } from 'devalue';
import type { z } from 'zod';
+import { REDIRECT_STATUS_CODES } from '../../../core/constants.js';
+import { ActionsReturnedInvalidDataError } from '../../../core/errors/errors-data.js';
+import { AstroError } from '../../../core/errors/errors.js';
import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from '../../consts.js';
-import type { ErrorInferenceObject, MaybePromise } from '../utils.js';
+import type {
+ ErrorInferenceObject,
+ MaybePromise,
+ ActionAPIContext as _ActionAPIContext,
+} from '../utils.js';
+export type ActionAPIContext = _ActionAPIContext;
export const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS;
export const ACTION_ERROR_CODES = [
@@ -232,14 +240,30 @@ export function serializeActionResult(res: SafeResult<any, any>): SerializedActi
status: 204,
};
}
+ let body;
+ try {
+ body = devalueStringify(res.data, {
+ // Add support for URL objects
+ URL: (value) => value instanceof URL && value.href,
+ });
+ } catch (e) {
+ let hint = ActionsReturnedInvalidDataError.hint;
+ if (res.data instanceof Response) {
+ hint = REDIRECT_STATUS_CODES.includes(res.data.status as any)
+ ? 'If you need to redirect when the action succeeds, trigger a redirect where the action is called. See the Actions guide for server and client redirect examples: https://docs.astro.build/en/guides/actions.'
+ : 'If you need to return a Response object, try using a server endpoint instead. See https://docs.astro.build/en/guides/endpoints/#server-endpoints-api-routes';
+ }
+ throw new AstroError({
+ ...ActionsReturnedInvalidDataError,
+ message: ActionsReturnedInvalidDataError.message(String(e)),
+ hint,
+ });
+ }
return {
type: 'data',
status: 200,
contentType: 'application/json+devalue',
- body: devalueStringify(res.data, {
- // Add support for URL objects
- URL: (value) => value instanceof URL && value.href,
- }),
+ body,
};
}
diff --git a/packages/astro/src/cli/add/babel.ts b/packages/astro/src/cli/add/babel.ts
deleted file mode 100644
index facaabd54..000000000
--- a/packages/astro/src/cli/add/babel.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import generator from '@babel/generator';
-import parser from '@babel/parser';
-import traverse from '@babel/traverse';
-import * as t from '@babel/types';
-
-export const visit = traverse.default;
-export { t };
-
-export async function generate(ast: t.File) {
- const astToText = generator.default;
- const { code } = astToText(ast);
- return code;
-}
-
-export const parse = (code: string) =>
- parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] });
diff --git a/packages/astro/src/cli/add/imports.ts b/packages/astro/src/cli/add/imports.ts
deleted file mode 100644
index 375ca1dd8..000000000
--- a/packages/astro/src/cli/add/imports.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { t, visit } from './babel.js';
-
-export function ensureImport(root: t.File, importDeclaration: t.ImportDeclaration) {
- let specifiersToFind = [...importDeclaration.specifiers];
-
- visit(root, {
- ImportDeclaration(path) {
- if (path.node.source.value === importDeclaration.source.value) {
- path.node.specifiers.forEach((specifier) =>
- specifiersToFind.forEach((specifierToFind, i) => {
- if (specifier.type !== specifierToFind.type) return;
- if (specifier.local.name === specifierToFind.local.name) {
- specifiersToFind.splice(i, 1);
- }
- }),
- );
- }
- },
- });
-
- if (specifiersToFind.length === 0) return;
-
- visit(root, {
- Program(path) {
- const declaration = t.importDeclaration(specifiersToFind, importDeclaration.source);
- const latestImport = path
- .get('body')
- .filter((statement) => statement.isImportDeclaration())
- .pop();
-
- if (latestImport) latestImport.insertAfter(declaration);
- else path.unshiftContainer('body', declaration);
- },
- });
-}
diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts
index f710184d2..2b230048c 100644
--- a/packages/astro/src/cli/add/index.ts
+++ b/packages/astro/src/cli/add/index.ts
@@ -3,12 +3,14 @@ import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import boxen from 'boxen';
import { diffWords } from 'diff';
-import { execa } from 'execa';
import { bold, cyan, dim, green, magenta, red, yellow } from 'kleur/colors';
+import { type ASTNode, type ProxifiedModule, builders, generateCode, loadFile } from 'magicast';
+import { getDefaultExportOptions } from 'magicast/helpers';
import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts';
import maxSatisfying from 'semver/ranges/max-satisfying.js';
+import { exec } from 'tinyexec';
import {
loadTSConfig,
resolveConfig,
@@ -30,9 +32,6 @@ import { ensureProcessNodeEnv, parseNpmName } from '../../core/util.js';
import { eventCliSession, telemetry } from '../../events/index.js';
import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js';
import { fetchPackageJson, fetchPackageVersions } from '../install-package.js';
-import { generate, parse, t, visit } from './babel.js';
-import { ensureImport } from './imports.js';
-import { wrapDefaultExport } from './wrapper.js';
interface AddOptions {
flags: Flags;
@@ -261,29 +260,26 @@ export async function add(names: string[], { flags }: AddOptions) {
await fs.writeFile(fileURLToPath(configURL), STUBS.ASTRO_CONFIG, { encoding: 'utf-8' });
}
- let ast: t.File | null = null;
+ let mod: ProxifiedModule<any> | undefined;
try {
- ast = await parseAstroConfig(configURL);
-
+ mod = await loadFile(fileURLToPath(configURL));
logger.debug('add', 'Parsed astro config');
- const defineConfig = t.identifier('defineConfig');
- ensureImport(
- ast,
- t.importDeclaration(
- [t.importSpecifier(defineConfig, defineConfig)],
- t.stringLiteral('astro/config'),
- ),
- );
- wrapDefaultExport(ast, defineConfig);
-
+ if (mod.exports.default.$type !== 'function-call') {
+ // ensure config is wrapped with `defineConfig`
+ mod.imports.$prepend({ imported: 'defineConfig', from: 'astro/config' });
+ mod.exports.default = builders.functionCall('defineConfig', mod.exports.default);
+ } else if (mod.exports.default.$args[0] == null) {
+ // ensure first argument of `defineConfig` is not empty
+ mod.exports.default.$args[0] = {};
+ }
logger.debug('add', 'Astro config ensured `defineConfig`');
for (const integration of integrations) {
if (isAdapter(integration)) {
const officialExportName = OFFICIAL_ADAPTER_TO_IMPORT_MAP[integration.id];
if (officialExportName) {
- await setAdapter(ast, integration, officialExportName);
+ setAdapter(mod, integration);
} else {
logger.info(
'SKIP_FORMAT',
@@ -295,7 +291,7 @@ export async function add(names: string[], { flags }: AddOptions) {
);
}
} else {
- await addIntegration(ast, integration);
+ addIntegration(mod, integration);
}
logger.debug('add', `Astro config added integration ${integration.id}`);
}
@@ -306,11 +302,11 @@ export async function add(names: string[], { flags }: AddOptions) {
let configResult: UpdateResult | undefined;
- if (ast) {
+ if (mod) {
try {
configResult = await updateAstroConfig({
configURL,
- ast,
+ mod,
flags,
logger,
logAdapterInstructions: integrations.some(isAdapter),
@@ -390,17 +386,6 @@ function isAdapter(
return integration.type === 'adapter';
}
-async function parseAstroConfig(configURL: URL): Promise<t.File> {
- const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
- const result = parse(source);
-
- if (!result) throw new Error('Unknown error parsing astro config');
- if (result.errors.length > 0)
- throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors));
-
- return result;
-}
-
// Convert an arbitrary NPM package name into a JS identifier
// Some examples:
// - @astrojs/image => image
@@ -437,130 +422,47 @@ Documentation: https://docs.astro.build/en/guides/integrations-guide/`;
return err;
}
-async function addIntegration(ast: t.File, integration: IntegrationInfo) {
- const integrationId = t.identifier(toIdent(integration.id));
-
- ensureImport(
- ast,
- t.importDeclaration(
- [t.importDefaultSpecifier(integrationId)],
- t.stringLiteral(integration.packageName),
- ),
- );
+function addIntegration(mod: ProxifiedModule<any>, integration: IntegrationInfo) {
+ const config = getDefaultExportOptions(mod);
+ const integrationId = toIdent(integration.id);
- visit(ast, {
- // eslint-disable-next-line @typescript-eslint/no-shadow
- ExportDefaultDeclaration(path) {
- if (!t.isCallExpression(path.node.declaration)) return;
-
- const configObject = path.node.declaration.arguments[0];
- if (!t.isObjectExpression(configObject)) return;
-
- let integrationsProp = configObject.properties.find((prop) => {
- if (prop.type !== 'ObjectProperty') return false;
- if (prop.key.type === 'Identifier') {
- if (prop.key.name === 'integrations') return true;
- }
- if (prop.key.type === 'StringLiteral') {
- if (prop.key.value === 'integrations') return true;
- }
- return false;
- }) as t.ObjectProperty | undefined;
-
- const integrationCall = t.callExpression(integrationId, []);
-
- if (!integrationsProp) {
- configObject.properties.push(
- t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall])),
- );
- return;
- }
-
- if (integrationsProp.value.type !== 'ArrayExpression')
- throw new Error('Unable to parse integrations');
-
- const existingIntegrationCall = integrationsProp.value.elements.find(
- (expr) =>
- t.isCallExpression(expr) &&
- t.isIdentifier(expr.callee) &&
- expr.callee.name === integrationId.name,
- );
-
- if (existingIntegrationCall) return;
+ if (!mod.imports.$items.some((imp) => imp.local === integrationId)) {
+ mod.imports.$append({ imported: integrationId, from: integration.packageName });
+ }
- integrationsProp.value.elements.push(integrationCall);
- },
- });
+ config.integrations ??= [];
+ if (
+ !config.integrations.$ast.elements.some(
+ (el: ASTNode) =>
+ el.type === 'CallExpression' &&
+ el.callee.type === 'Identifier' &&
+ el.callee.name === integrationId,
+ )
+ ) {
+ config.integrations.push(builders.functionCall(integrationId));
+ }
}
-async function setAdapter(ast: t.File, adapter: IntegrationInfo, exportName: string) {
- const adapterId = t.identifier(toIdent(adapter.id));
+export function setAdapter(mod: ProxifiedModule<any>, adapter: IntegrationInfo) {
+ const config = getDefaultExportOptions(mod);
+ const adapterId = toIdent(adapter.id);
- ensureImport(
- ast,
- t.importDeclaration([t.importDefaultSpecifier(adapterId)], t.stringLiteral(exportName)),
- );
-
- visit(ast, {
- // eslint-disable-next-line @typescript-eslint/no-shadow
- ExportDefaultDeclaration(path) {
- if (!t.isCallExpression(path.node.declaration)) return;
-
- const configObject = path.node.declaration.arguments[0];
- if (!t.isObjectExpression(configObject)) return;
-
- let outputProp = configObject.properties.find((prop) => {
- if (prop.type !== 'ObjectProperty') return false;
- if (prop.key.type === 'Identifier') {
- if (prop.key.name === 'output') return true;
- }
- if (prop.key.type === 'StringLiteral') {
- if (prop.key.value === 'output') return true;
- }
- return false;
- }) as t.ObjectProperty | undefined;
-
- if (!outputProp) {
- configObject.properties.push(
- t.objectProperty(t.identifier('output'), t.stringLiteral('server')),
- );
- }
-
- let adapterProp = configObject.properties.find((prop) => {
- if (prop.type !== 'ObjectProperty') return false;
- if (prop.key.type === 'Identifier') {
- if (prop.key.name === 'adapter') return true;
- }
- if (prop.key.type === 'StringLiteral') {
- if (prop.key.value === 'adapter') return true;
- }
- return false;
- }) as t.ObjectProperty | undefined;
-
- let adapterCall;
- switch (adapter.id) {
- // the node adapter requires a mode
- case 'node': {
- adapterCall = t.callExpression(adapterId, [
- t.objectExpression([
- t.objectProperty(t.identifier('mode'), t.stringLiteral('standalone')),
- ]),
- ]);
- break;
- }
- default: {
- adapterCall = t.callExpression(adapterId, []);
- }
- }
+ if (!mod.imports.$items.some((imp) => imp.local === adapterId)) {
+ mod.imports.$append({ imported: adapterId, from: adapter.packageName });
+ }
- if (!adapterProp) {
- configObject.properties.push(t.objectProperty(t.identifier('adapter'), adapterCall));
- return;
- }
+ if (!config.output) {
+ config.output = 'server';
+ }
- adapterProp.value = adapterCall;
- },
- });
+ switch (adapter.id) {
+ case 'node':
+ config.adapter = builders.functionCall(adapterId, { mode: 'standalone' });
+ break;
+ default:
+ config.adapter = builders.functionCall(adapterId);
+ break;
+ }
}
const enum UpdateResult {
@@ -572,23 +474,25 @@ const enum UpdateResult {
async function updateAstroConfig({
configURL,
- ast,
+ mod,
flags,
logger,
logAdapterInstructions,
}: {
configURL: URL;
- ast: t.File;
+ mod: ProxifiedModule<any>;
flags: Flags;
logger: Logger;
logAdapterInstructions: boolean;
}): Promise<UpdateResult> {
const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
- let output = await generate(ast);
- const comment = '// https://astro.build/config';
- const defaultExport = 'export default defineConfig';
- output = output.replace(`\n${comment}`, '');
- output = output.replace(`${defaultExport}`, `\n${comment}\n${defaultExport}`);
+ const output = generateCode(mod, {
+ format: {
+ objectCurlySpacing: true,
+ useTabs: false,
+ tabWidth: 2,
+ },
+ }).code;
if (input === output) {
return UpdateResult.none;
@@ -755,7 +659,7 @@ async function tryToInstallIntegrations({
if (await askToContinue({ flags })) {
const spinner = ora('Installing dependencies...').start();
try {
- await execa(
+ await exec(
installCommand.pm,
[
installCommand.command,
@@ -764,9 +668,11 @@ async function tryToInstallIntegrations({
...installCommand.dependencies,
],
{
- cwd,
- // reset NODE_ENV to ensure install command run in dev mode
- env: { NODE_ENV: undefined },
+ nodeOptions: {
+ cwd,
+ // reset NODE_ENV to ensure install command run in dev mode
+ env: { NODE_ENV: undefined },
+ },
},
);
spinner.succeed();
diff --git a/packages/astro/src/cli/add/wrapper.ts b/packages/astro/src/cli/add/wrapper.ts
deleted file mode 100644
index c86e87698..000000000
--- a/packages/astro/src/cli/add/wrapper.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { t, visit } from './babel.js';
-
-export function wrapDefaultExport(ast: t.File, functionIdentifier: t.Identifier) {
- visit(ast, {
- ExportDefaultDeclaration(path) {
- if (!t.isExpression(path.node.declaration)) return;
- if (
- t.isCallExpression(path.node.declaration) &&
- t.isIdentifier(path.node.declaration.callee) &&
- path.node.declaration.callee.name === functionIdentifier.name
- )
- return;
- path.node.declaration = t.callExpression(functionIdentifier, [path.node.declaration]);
- },
- });
-}
diff --git a/packages/astro/src/cli/docs/open.ts b/packages/astro/src/cli/docs/open.ts
index 3913ccec4..6f2fe4c82 100644
--- a/packages/astro/src/cli/docs/open.ts
+++ b/packages/astro/src/cli/docs/open.ts
@@ -1,5 +1,4 @@
-import type { ExecaChildProcess } from 'execa';
-import { execa } from 'execa';
+import { type Result, exec } from 'tinyexec';
/**
* Credit: Azhar22
@@ -26,7 +25,7 @@ const getPlatformSpecificCommand = (): [string] | [string, string[]] => {
}
};
-export async function openInBrowser(url: string): Promise<ExecaChildProcess> {
+export async function openInBrowser(url: string): Promise<Result> {
const [command, args = []] = getPlatformSpecificCommand();
- return execa(command, [...args, encodeURI(url)]);
+ return exec(command, [...args, encodeURI(url)]);
}
diff --git a/packages/astro/src/cli/install-package.ts b/packages/astro/src/cli/install-package.ts
index 637390ef3..d61a752de 100644
--- a/packages/astro/src/cli/install-package.ts
+++ b/packages/astro/src/cli/install-package.ts
@@ -1,11 +1,11 @@
import { createRequire } from 'node:module';
import boxen from 'boxen';
import ci from 'ci-info';
-import { execa } from 'execa';
import { bold, cyan, dim, magenta } from 'kleur/colors';
import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts';
+import { exec } from 'tinyexec';
import whichPm from 'which-pm';
import type { Logger } from '../core/logger/core.js';
@@ -141,10 +141,10 @@ async function installPackage(
if (Boolean(response)) {
const spinner = ora('Installing dependencies...').start();
try {
- await execa(
+ await exec(
installCommand.pm,
[installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
- { cwd: cwd },
+ { nodeOptions: { cwd: cwd } },
);
spinner.succeed();
@@ -203,8 +203,8 @@ async function getRegistry(): Promise<string> {
const fallback = 'https://registry.npmjs.org';
const packageManager = (await preferredPM(process.cwd()))?.name || 'npm';
try {
- const { stdout } = await execa(packageManager, ['config', 'get', 'registry']);
- _registry = stdout?.trim()?.replace(/\/$/, '') || fallback;
+ const { stdout } = await exec(packageManager, ['config', 'get', 'registry']);
+ _registry = stdout.trim()?.replace(/\/$/, '') || fallback;
// Detect cases where the shell command returned a non-URL (e.g. a warning)
if (!new URL(_registry).host) _registry = fallback;
} catch {
diff --git a/packages/astro/src/container/pipeline.ts b/packages/astro/src/container/pipeline.ts
index 73caa4ecd..167285158 100644
--- a/packages/astro/src/container/pipeline.ts
+++ b/packages/astro/src/container/pipeline.ts
@@ -1,4 +1,4 @@
-import { type HeadElements, Pipeline } from '../core/base-pipeline.js';
+import { type HeadElements, Pipeline, type TryRewriteResult } from '../core/base-pipeline.js';
import type { SinglePageBuiltModule } from '../core/build/types.js';
import {
createModuleScriptElement,
@@ -64,11 +64,8 @@ export class ContainerPipeline extends Pipeline {
return { links, styles, scripts };
}
- async tryRewrite(
- payload: RewritePayload,
- request: Request,
- ): Promise<[RouteData, ComponentInstance, URL]> {
- const [foundRoute, finalUrl] = findRouteToRewrite({
+ async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
+ const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,
routes: this.manifest?.routes.map((r) => r.routeData),
@@ -77,8 +74,8 @@ export class ContainerPipeline extends Pipeline {
base: this.manifest.base,
});
- const componentInstance = await this.getComponentByRoute(foundRoute);
- return [foundRoute, componentInstance, finalUrl];
+ const componentInstance = await this.getComponentByRoute(routeData);
+ return { componentInstance, routeData, newUrl, pathname };
}
insertRoute(route: RouteData, componentInstance: ComponentInstance): void {
diff --git a/packages/astro/src/content/loaders/file.ts b/packages/astro/src/content/loaders/file.ts
index cbc684a99..75e5e214d 100644
--- a/packages/astro/src/content/loaders/file.ts
+++ b/packages/astro/src/content/loaders/file.ts
@@ -26,6 +26,8 @@ export function file(fileName: string): Loader {
return;
}
+ const normalizedFilePath = posixRelative(fileURLToPath(settings.config.root), filePath);
+
if (Array.isArray(json)) {
if (json.length === 0) {
logger.warn(`No items found in ${fileName}`);
@@ -39,11 +41,7 @@ export function file(fileName: string): Loader {
continue;
}
const data = await parseData({ id, data: rawItem, filePath });
- store.set({
- id,
- data,
- filePath: posixRelative(fileURLToPath(settings.config.root), filePath),
- });
+ store.set({ id, data, filePath: normalizedFilePath });
}
} else if (typeof json === 'object') {
const entries = Object.entries<Record<string, unknown>>(json);
@@ -51,7 +49,7 @@ export function file(fileName: string): Loader {
store.clear();
for (const [id, rawItem] of entries) {
const data = await parseData({ id, data: rawItem, filePath });
- store.set({ id, data });
+ store.set({ id, data, filePath: normalizedFilePath });
}
} else {
logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts
index 27b0e1915..ba76d5e11 100644
--- a/packages/astro/src/content/utils.ts
+++ b/packages/astro/src/content/utils.ts
@@ -4,12 +4,13 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter';
import type { PluginContext } from 'rollup';
-import { type ViteDevServer, normalizePath } from 'vite';
+import type { ViteDevServer } from 'vite';
import xxhash from 'xxhash-wasm';
import { z } from 'zod';
import { AstroError, AstroErrorData, MarkdownError, errorMap } from '../core/errors/index.js';
import { isYAMLException } from '../core/errors/utils.js';
import type { Logger } from '../core/logger/core.js';
+import { normalizePath } from '../core/viteUtils.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type { ContentEntryType, DataEntryType } from '../types/public/content.js';
diff --git a/packages/astro/src/core/app/pipeline.ts b/packages/astro/src/core/app/pipeline.ts
index d42472f50..43ff91fdd 100644
--- a/packages/astro/src/core/app/pipeline.ts
+++ b/packages/astro/src/core/app/pipeline.ts
@@ -1,7 +1,7 @@
import type { ComponentInstance, ManifestData } from '../../types/astro.js';
import type { RewritePayload } from '../../types/public/common.js';
import type { RouteData, SSRElement, SSRResult } from '../../types/public/internal.js';
-import { Pipeline } from '../base-pipeline.js';
+import { Pipeline, type TryRewriteResult } from '../base-pipeline.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { RedirectSinglePageBuiltModule } from '../redirects/component.js';
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
@@ -89,8 +89,8 @@ export class AppPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
- ): Promise<[RouteData, ComponentInstance, URL]> {
- const [foundRoute, finalUrl] = findRouteToRewrite({
+ ): Promise<TryRewriteResult> {
+ const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,
routes: this.manifest?.routes.map((r) => r.routeData),
@@ -99,8 +99,8 @@ export class AppPipeline extends Pipeline {
base: this.manifest.base,
});
- const componentInstance = await this.getComponentByRoute(foundRoute);
- return [foundRoute, componentInstance, finalUrl];
+ const componentInstance = await this.getComponentByRoute(routeData);
+ return { newUrl, pathname, componentInstance, routeData };
}
async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts
index 66140e778..4bf2a350e 100644
--- a/packages/astro/src/core/app/types.ts
+++ b/packages/astro/src/core/app/types.ts
@@ -74,6 +74,7 @@ export type SSRManifest = {
export type SSRManifestI18n = {
fallback: Record<string, string> | undefined;
+ fallbackType: 'redirect' | 'rewrite';
strategy: RoutingStrategies;
locales: Locales;
defaultLocale: string;
diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts
index 9d772de9d..2c8199446 100644
--- a/packages/astro/src/core/base-pipeline.ts
+++ b/packages/astro/src/core/base-pipeline.ts
@@ -94,7 +94,7 @@ export abstract class Pipeline {
rewritePayload: RewritePayload,
request: Request,
sourceRoute: RouteData,
- ): Promise<[RouteData, ComponentInstance, URL]>;
+ ): Promise<TryRewriteResult>;
/**
* Tells the pipeline how to retrieve a component give a `RouteData`
@@ -105,3 +105,10 @@ export abstract class Pipeline {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface HeadElements extends Pick<SSRResult, 'scripts' | 'styles' | 'links'> {}
+
+export interface TryRewriteResult {
+ routeData: RouteData;
+ componentInstance: ComponentInstance;
+ newUrl: URL;
+ pathname: string;
+}
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index b2fed3763..0a33d554c 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -14,7 +14,7 @@ import {
removeLeadingForwardSlash,
removeTrailingForwardSlash,
} from '../../core/path.js';
-import { toRoutingStrategy } from '../../i18n/utils.js';
+import { toFallbackType, toRoutingStrategy } from '../../i18n/utils.js';
import { runHookBuildGenerated } from '../../integrations/hooks.js';
import { getOutputDirectory } from '../../prerender/utils.js';
import type { AstroSettings, ComponentInstance } from '../../types/astro.js';
@@ -504,6 +504,7 @@ function createBuildManifest(
if (settings.config.i18n) {
i18nManifest = {
fallback: settings.config.i18n.fallback,
+ fallbackType: toFallbackType(settings.config.i18n.routing),
strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
defaultLocale: settings.config.i18n.defaultLocale,
locales: settings.config.i18n.locales,
diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts
index 02be5df9f..55cc0d456 100644
--- a/packages/astro/src/core/build/pipeline.ts
+++ b/packages/astro/src/core/build/pipeline.ts
@@ -9,6 +9,7 @@ import type {
} from '../../types/public/internal.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { SSRManifest } from '../app/types.js';
+import type { TryRewriteResult } from '../base-pipeline.js';
import { routeIsFallback, routeIsRedirect } from '../redirects/helpers.js';
import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
import { Pipeline } from '../render/index.js';
@@ -265,8 +266,8 @@ export class BuildPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
- ): Promise<[RouteData, ComponentInstance, URL]> {
- const [foundRoute, finalUrl] = findRouteToRewrite({
+ ): Promise<TryRewriteResult> {
+ const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,
routes: this.options.manifest.routes,
@@ -275,8 +276,8 @@ export class BuildPipeline extends Pipeline {
base: this.config.base,
});
- const componentInstance = await this.getComponentByRoute(foundRoute);
- return [foundRoute, componentInstance, finalUrl];
+ const componentInstance = await this.getComponentByRoute(routeData);
+ return { routeData, componentInstance, newUrl, pathname };
}
async retrieveSsrEntry(route: RouteData, filePath: string): Promise<SinglePageBuiltModule> {
diff --git a/packages/astro/src/core/build/plugins/plugin-content.ts b/packages/astro/src/core/build/plugins/plugin-content.ts
index 5fe0b6792..3482657fb 100644
--- a/packages/astro/src/core/build/plugins/plugin-content.ts
+++ b/packages/astro/src/core/build/plugins/plugin-content.ts
@@ -22,7 +22,7 @@ import {
import { isContentCollectionsCacheEnabled } from '../../util.js';
import { addRollupInput } from '../add-rollup-input.js';
import { CHUNKS_PATH, CONTENT_PATH } from '../consts.js';
-import { type BuildInternals } from '../internal.js';
+import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import { copyFiles } from '../static-build.js';
import type { StaticBuildOptions } from '../types.js';
diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts
index ef8eb0915..6259d1526 100644
--- a/packages/astro/src/core/build/plugins/plugin-manifest.ts
+++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts
@@ -4,7 +4,7 @@ import type { OutputChunk } from 'rollup';
import type { Plugin as VitePlugin } from 'vite';
import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js';
import { normalizeTheLocale } from '../../../i18n/index.js';
-import { toRoutingStrategy } from '../../../i18n/utils.js';
+import { toFallbackType, toRoutingStrategy } from '../../../i18n/utils.js';
import { runHookBuildSsr } from '../../../integrations/hooks.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type {
@@ -244,6 +244,7 @@ function buildManifest(
if (settings.config.i18n) {
i18nManifest = {
fallback: settings.config.i18n.fallback,
+ fallbackType: toFallbackType(settings.config.i18n.routing),
strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
locales: settings.config.i18n.locales,
defaultLocale: settings.config.i18n.defaultLocale,
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index 2dc400a6c..6996e3342 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -1,7 +1,7 @@
import type { Plugin as VitePlugin } from 'vite';
import { routeIsRedirect } from '../../redirects/index.js';
import { addRollupInput } from '../add-rollup-input.js';
-import { type BuildInternals } from '../internal.js';
+import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 5a2f0a611..e52eb560b 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -400,6 +400,7 @@ export const AstroConfigSchema = z.object({
.object({
prefixDefaultLocale: z.boolean().optional().default(false),
redirectToDefaultLocale: z.boolean().optional().default(true),
+ fallbackType: z.enum(['redirect', 'rewrite']).optional().default('redirect'),
})
.refine(
({ prefixDefaultLocale, redirectToDefaultLocale }) => {
diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts
index 8e9f5ac74..274f86797 100644
--- a/packages/astro/src/core/constants.ts
+++ b/packages/astro/src/core/constants.ts
@@ -46,6 +46,11 @@ export const DEFAULT_404_COMPONENT = 'astro-default-404.astro';
export const DEFAULT_500_COMPONENT = 'astro-default-500.astro';
/**
+ * A response with one of these status codes will create a redirect response.
+ */
+export const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308, 300, 304] as const;
+
+/**
* A response with one of these status codes will be rewritten
* with the result of rendering the respective error page.
*/
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 6a5995e3f..471f8f4f1 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -149,7 +149,7 @@ export async function createVite(
astroPrefetch({ settings }),
astroTransitions({ settings }),
astroDevToolbar({ settings, logger }),
- vitePluginFileURL({}),
+ vitePluginFileURL(),
astroInternationalization({ settings }),
settings.config.experimental.serverIslands && vitePluginServerIslands({ settings }),
astroContainer(),
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index 475acdbbf..382c025d3 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -541,7 +541,7 @@ export const MissingImageDimension = {
message: (missingDimension: 'width' | 'height' | 'both', imageURL: string) =>
`Missing ${
missingDimension === 'both' ? 'width and height attributes' : `${missingDimension} attribute`
- } for ${imageURL}. When using remote images, both dimensions are required unless in order to avoid CLS.`,
+ } for ${imageURL}. When using remote images, both dimensions are required in order to avoid CLS.`,
hint: 'If your image is inside your `src` folder, you probably meant to import it instead. See [the Imports guide for more information](https://docs.astro.build/en/guides/imports/#other-assets). You can also use `inferSize={true}` for remote images to get the original dimensions.',
} satisfies ErrorData;
/**
@@ -1670,6 +1670,7 @@ export const ActionsWithoutServerOutputError = {
* - [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md)
* @description
* Action was called from a form using a GET request, but only POST requests are supported. This often occurs if `method="POST"` is missing on the form.
+ * @deprecated Deprecated since version 4.13.2.
*/
export const ActionsUsedWithForGetError = {
name: 'ActionsUsedWithForGetError',
@@ -1684,6 +1685,21 @@ export const ActionsUsedWithForGetError = {
* @see
* - [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md)
* @description
+ * Action handler returned invalid data. Handlers should return serializable data types, and cannot return a Response object.
+ */
+export const ActionsReturnedInvalidDataError = {
+ name: 'ActionsReturnedInvalidDataError',
+ title: 'Action handler returned invalid data.',
+ message: (error: string) =>
+ `Action handler returned invalid data. Handlers should return serializable data types like objects, arrays, strings, and numbers. Parse error: ${error}`,
+ hint: 'See the devalue library for all supported types: https://github.com/rich-harris/devalue',
+} satisfies ErrorData;
+
+/**
+ * @docs
+ * @see
+ * - [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md)
+ * @description
* The server received the query string `?_astroAction=name`, but could not find an action with that name. Use the action function's `.queryString` property to retrieve the form `action` URL.
*/
export const ActionQueryStringInvalidError = {
diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts
index 1c0017c30..7c75df4f3 100644
--- a/packages/astro/src/core/render-context.ts
+++ b/packages/astro/src/core/render-context.ts
@@ -37,7 +37,7 @@ import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;
-
+
// The component pattern to send to the users
routePattern: string;
@@ -111,6 +111,8 @@ export class RenderContext {
const { cookies, middleware, pipeline } = this;
const { logger, serverLike, streaming, manifest } = pipeline;
+ const isPrerendered = !serverLike || this.routeData.prerender;
+
const props =
Object.keys(this.props).length > 0
? this.props
@@ -123,7 +125,7 @@ export class RenderContext {
serverLike,
base: manifest.base,
});
- const apiContext = this.createAPIContext(props);
+ const apiContext = this.createAPIContext(props, isPrerendered);
this.counter++;
if (this.counter === 4) {
@@ -138,13 +140,13 @@ export class RenderContext {
if (payload) {
pipeline.logger.debug('router', 'Called rewriting to:', payload);
// we intentionally let the error bubble up
- const [routeData, component] = await pipeline.tryRewrite(
+ const { routeData, componentInstance: newComponent } = await pipeline.tryRewrite(
payload,
this.request,
this.originalRoute,
);
this.routeData = routeData;
- componentInstance = component;
+ componentInstance = newComponent;
this.isRewriting = true;
this.status = 200;
}
@@ -210,18 +212,27 @@ export class RenderContext {
return response;
}
- createAPIContext(props: APIContext['props']): APIContext {
+ createAPIContext(props: APIContext['props'], isPrerendered: boolean): APIContext {
const context = this.createActionAPIContext();
+ const redirect = (path: string, status = 302) =>
+ new Response(null, { status, headers: { Location: path } });
+
return Object.assign(context, {
props,
+ redirect,
getActionResult: createGetActionResult(context.locals),
callAction: createCallAction(context),
+ // Used internally by Actions middleware.
+ // TODO: discuss exposing this information from APIContext.
+ // middleware runs on prerendered routes in the dev server,
+ // so this is useful information to have.
+ _isPrerendered: isPrerendered,
});
}
async #executeRewrite(reroutePayload: RewritePayload) {
this.pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload);
- const [routeData, component, newURL] = await this.pipeline.tryRewrite(
+ const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute,
@@ -230,25 +241,22 @@ export class RenderContext {
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
- this.request = this.#copyRequest(newURL, this.request);
+ this.request = this.#copyRequest(newUrl, this.request);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
- this.params = getParams(routeData, this.url.pathname);
- this.pathname = this.url.pathname;
+ this.params = getParams(routeData, pathname);
+ this.pathname = pathname;
this.isRewriting = true;
// we found a route and a component, we can change the status code to 200
this.status = 200;
- this.routePattern = getAstroRoutePattern(routeData.component);
- return await this.render(component);
+ return await this.render(componentInstance);
}
createActionAPIContext(): ActionAPIContext {
const renderContext = this;
const { cookies, params, pipeline, url } = this;
const generator = `Astro v${ASTRO_VERSION}`;
- const redirect = (path: string, status = 302) =>
- new Response(null, { status, headers: { Location: path } });
const rewrite = async (reroutePayload: RewritePayload) => {
return await this.#executeRewrite(reroutePayload);
@@ -285,7 +293,6 @@ export class RenderContext {
get preferredLocaleList() {
return renderContext.computePreferredLocaleList();
},
- redirect,
rewrite,
request: this.request,
site: pipeline.site,
@@ -580,25 +587,27 @@ export class RenderContext {
* @param component
*/
function getAstroRoutePattern(component: RouteData['component']): string {
- let splitComponent = component.split("/");
+ let splitComponent = component.split('/');
while (true) {
const currentPart = splitComponent.shift();
- if (!currentPart) {break}
-
+ if (!currentPart) {
+ break;
+ }
+
// "pages" isn't configurable, so it's safe to stop here
- if (currentPart === "pages") {
- break
+ if (currentPart === 'pages') {
+ break;
}
}
-
- const pathWithoutPages = splitComponent.join("/");
+
+ const pathWithoutPages = splitComponent.join('/');
// This covers cases where routes don't have extensions, so they can be: [slug] or [...slug]
- if (pathWithoutPages.endsWith("]")) {
+ if (pathWithoutPages.endsWith(']')) {
return pathWithoutPages;
}
- splitComponent = splitComponent.join("/").split(".");
+ splitComponent = splitComponent.join('/').split('.');
// this should remove the extension
splitComponent.pop();
- return "/" + splitComponent.join("/");
+ return '/' + splitComponent.join('/');
}
diff --git a/packages/astro/src/core/routing/rewrite.ts b/packages/astro/src/core/routing/rewrite.ts
index a6fce3354..3ad6a9bd2 100644
--- a/packages/astro/src/core/routing/rewrite.ts
+++ b/packages/astro/src/core/routing/rewrite.ts
@@ -14,6 +14,17 @@ export type FindRouteToRewrite = {
base: AstroConfig['base'];
};
+export interface FindRouteToRewriteResult {
+ routeData: RouteData;
+ newUrl: URL;
+ pathname: string;
+}
+
+/**
+ * Shared logic to retrieve the rewritten route. It returns a tuple that represents:
+ * 1. The new `Request` object. It contains `base`
+ * 2.
+ */
export function findRouteToRewrite({
payload,
routes,
@@ -21,23 +32,25 @@ export function findRouteToRewrite({
trailingSlash,
buildFormat,
base,
-}: FindRouteToRewrite): [RouteData, URL] {
- let finalUrl: URL | undefined = undefined;
+}: FindRouteToRewrite): FindRouteToRewriteResult {
+ let newUrl: URL | undefined = undefined;
if (payload instanceof URL) {
- finalUrl = payload;
+ newUrl = payload;
} else if (payload instanceof Request) {
- finalUrl = new URL(payload.url);
+ newUrl = new URL(payload.url);
} else {
- finalUrl = new URL(payload, new URL(request.url).origin);
+ newUrl = new URL(payload, new URL(request.url).origin);
+ }
+ let pathname = newUrl.pathname;
+ if (base !== '/' && newUrl.pathname.startsWith(base)) {
+ pathname = shouldAppendForwardSlash(trailingSlash, buildFormat)
+ ? appendForwardSlash(newUrl.pathname)
+ : removeTrailingForwardSlash(newUrl.pathname);
+ pathname = pathname.slice(base.length);
}
let foundRoute;
for (const route of routes) {
- const pathname = shouldAppendForwardSlash(trailingSlash, buildFormat)
- ? appendForwardSlash(finalUrl.pathname)
- : base !== '/'
- ? removeTrailingForwardSlash(finalUrl.pathname)
- : finalUrl.pathname;
if (route.pattern.test(decodeURI(pathname))) {
foundRoute = route;
break;
@@ -45,13 +58,17 @@ export function findRouteToRewrite({
}
if (foundRoute) {
- return [foundRoute, finalUrl];
+ return {
+ routeData: foundRoute,
+ newUrl,
+ pathname,
+ };
} else {
const custom404 = routes.find((route) => route.route === '/404');
if (custom404) {
- return [custom404, finalUrl];
+ return { routeData: custom404, newUrl, pathname };
} else {
- return [DEFAULT_404_ROUTE, finalUrl];
+ return { routeData: DEFAULT_404_ROUTE, newUrl, pathname };
}
}
}
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index c9769443e..9b2973233 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -58,7 +58,7 @@ export default async function sync(
}
let settings = await createSettings(astroConfig, inlineConfig.root);
settings = await runHookConfigSetup({
- command: 'build',
+ command: 'sync',
settings,
logger,
});
diff --git a/packages/astro/src/core/viteUtils.ts b/packages/astro/src/core/viteUtils.ts
index bfe1eaadc..46c59d25d 100644
--- a/packages/astro/src/core/viteUtils.ts
+++ b/packages/astro/src/core/viteUtils.ts
@@ -1,10 +1,18 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
-import { normalizePath } from 'vite';
-import { prependForwardSlash } from '../core/path.js';
+import { prependForwardSlash, slash } from '../core/path.js';
import type { ModuleLoader } from './module-loader/index.js';
import { VALID_ID_PREFIX, resolveJsToTs, unwrapId, viteID } from './util.js';
+const isWindows = typeof process !== 'undefined' && process.platform === 'win32';
+
+/**
+ * Re-implementation of Vite's normalizePath that can be used without Vite
+ */
+export function normalizePath(id: string) {
+ return path.posix.normalize(isWindows ? slash(id) : id);
+}
+
/**
* Resolve the hydration paths so that it can be imported in the client
*/
diff --git a/packages/astro/src/i18n/index.ts b/packages/astro/src/i18n/index.ts
index c7e676f75..af08db408 100644
--- a/packages/astro/src/i18n/index.ts
+++ b/packages/astro/src/i18n/index.ts
@@ -278,6 +278,7 @@ export type MiddlewarePayload = {
defaultLocale: string;
domains: Record<string, string> | undefined;
fallback: Record<string, string> | undefined;
+ fallbackType: 'redirect' | 'rewrite';
};
// NOTE: public function exported to the users via `astro:i18n` module
@@ -328,7 +329,7 @@ export function notFound({ base, locales }: MiddlewarePayload) {
}
// NOTE: public function exported to the users via `astro:i18n` module
-export type RedirectToFallback = (context: APIContext, response: Response) => Response;
+export type RedirectToFallback = (context: APIContext, response: Response) => Promise<Response>;
export function redirectToFallback({
fallback,
@@ -336,8 +337,9 @@ export function redirectToFallback({
defaultLocale,
strategy,
base,
+ fallbackType,
}: MiddlewarePayload) {
- return function (context: APIContext, response: Response): Response {
+ return async function (context: APIContext, response: Response): Promise<Response> {
if (response.status >= 300 && fallback) {
const fallbackKeys = fallback ? Object.keys(fallback) : [];
// we split the URL using the `/`, and then check in the returned array we have the locale
@@ -371,7 +373,12 @@ export function redirectToFallback({
} else {
newPathname = context.url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`);
}
- return context.redirect(newPathname);
+
+ if (fallbackType === 'rewrite') {
+ return await context.rewrite(newPathname);
+ } else {
+ return context.redirect(newPathname);
+ }
}
}
return response;
diff --git a/packages/astro/src/i18n/utils.ts b/packages/astro/src/i18n/utils.ts
index 98a44a19c..5dc58908a 100644
--- a/packages/astro/src/i18n/utils.ts
+++ b/packages/astro/src/i18n/utils.ts
@@ -215,3 +215,12 @@ export function toRoutingStrategy(
return strategy;
}
+
+export function toFallbackType(
+ routing: NonNullable<AstroConfig['i18n']>['routing'],
+): 'redirect' | 'rewrite' {
+ if (routing === 'manual') {
+ return 'rewrite';
+ }
+ return routing.fallbackType;
+}
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 1dc681db6..8f3e62b8c 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -119,7 +119,7 @@ export async function runHookConfigSetup({
fs = fsMod,
}: {
settings: AstroSettings;
- command: 'dev' | 'build' | 'preview';
+ command: 'dev' | 'build' | 'preview' | 'sync';
logger: Logger;
isRestart?: boolean;
fs?: typeof fsMod;
diff --git a/packages/astro/src/prefetch/index.ts b/packages/astro/src/prefetch/index.ts
index 177945f37..3eb8cd570 100644
--- a/packages/astro/src/prefetch/index.ts
+++ b/packages/astro/src/prefetch/index.ts
@@ -215,6 +215,9 @@ export interface PrefetchOptions {
* @param opts Additional options for prefetching.
*/
export function prefetch(url: string, opts?: PrefetchOptions) {
+ // Remove url hash to avoid prefetching the same URL multiple times
+ url = url.replace(/#.*/, '');
+
const ignoreSlowConnection = opts?.ignoreSlowConnection ?? false;
if (!canPrefetchUrl(url, ignoreSlowConnection)) return;
prefetchedUrls.add(url);
diff --git a/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts b/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts
index e24698d43..c089664da 100644
--- a/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts
+++ b/packages/astro/src/runtime/client/dev-toolbar/toolbar.ts
@@ -3,7 +3,7 @@ import type { ResolvedDevToolbarApp as DevToolbarAppDefinition } from '../../../
import { type ToolbarAppEventTarget, serverHelpers } from './helpers.js';
import { settings } from './settings.js';
import { type Icon, getIconElement, isDefinedIcon } from './ui-library/icons.js';
-import { type Placement } from './ui-library/window.js';
+import type { Placement } from './ui-library/window.js';
export type DevToolbarApp = DevToolbarAppDefinition & {
builtIn: boolean;
diff --git a/packages/astro/src/runtime/client/idle.ts b/packages/astro/src/runtime/client/idle.ts
index e32b1a42f..801e1c983 100644
--- a/packages/astro/src/runtime/client/idle.ts
+++ b/packages/astro/src/runtime/client/idle.ts
@@ -1,14 +1,22 @@
import type { ClientDirective } from '../../types/public/integrations.js';
-const idleDirective: ClientDirective = (load) => {
+const idleDirective: ClientDirective = (load, options) => {
const cb = async () => {
const hydrate = await load();
await hydrate();
};
+
+ const rawOptions =
+ typeof options.value === 'object' ? (options.value as IdleRequestOptions) : undefined;
+
+ const idleOptions: IdleRequestOptions = {
+ timeout: rawOptions?.timeout,
+ };
+
if ('requestIdleCallback' in window) {
- (window as any).requestIdleCallback(cb);
+ (window as any).requestIdleCallback(cb, idleOptions);
} else {
- setTimeout(cb, 200);
+ setTimeout(cb, idleOptions.timeout || 200);
}
};
diff --git a/packages/astro/src/transitions/swap-functions.ts b/packages/astro/src/transitions/swap-functions.ts
index 4c8db82ee..e2d8557f5 100644
--- a/packages/astro/src/transitions/swap-functions.ts
+++ b/packages/astro/src/transitions/swap-functions.ts
@@ -133,6 +133,14 @@ const shouldCopyProps = (el: HTMLElement): boolean => {
return persistProps == null || persistProps === 'false';
};
+export const swapFunctions = {
+ deselectScripts,
+ swapRootAttributes,
+ swapHeadElements,
+ swapBodyElement,
+ saveFocus,
+};
+
export const swap = (doc: Document) => {
deselectScripts(doc);
swapRootAttributes(doc);
diff --git a/packages/astro/src/transitions/vite-plugin-transitions.ts b/packages/astro/src/transitions/vite-plugin-transitions.ts
index 56f086a06..e8122cac4 100644
--- a/packages/astro/src/transitions/vite-plugin-transitions.ts
+++ b/packages/astro/src/transitions/vite-plugin-transitions.ts
@@ -42,6 +42,7 @@ export default function astroTransitions({ settings }: { settings: AstroSettings
TRANSITION_BEFORE_SWAP, isTransitionBeforeSwapEvent, TransitionBeforeSwapEvent,
TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD
} from "astro/virtual-modules/transitions-events.js";
+ export { swapFunctions } from "astro/virtual-modules/transitions-swap-functions.js";
`;
}
},
diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts
index b429104e4..9630313ec 100644
--- a/packages/astro/src/types/public/config.ts
+++ b/packages/astro/src/types/public/config.ts
@@ -1298,6 +1298,43 @@ export interface AstroUserConfig {
redirectToDefaultLocale?: boolean;
/**
+ * @docs
+ * @name i18n.routing.fallbackType
+ * @kind h4
+ * @type {"redirect" | "rewrite"}
+ * @default `"redirect"`
+ * @version 4.15.0
+ * @description
+ *
+ * When [`i18n.fallback`](#i18nfallback) is configured to avoid showing a 404 page for missing page routes, this option controls whether to [redirect](https://docs.astro.build/en/guides/routing/#redirects) to the fallback page, or to [rewrite](https://docs.astro.build/en/guides/routing/#rewrites) the fallback page's content in place.
+ *
+ * By default, Astro's i18n routing creates pages that redirect your visitors to a new destination based on your fallback configuration. The browser will refresh and show the destination address in the URL bar.
+ *
+ * When `i18n.routing.fallback: "rewrite"` is configured, Astro will create pages that render the contents of the fallback page on the original, requested URL.
+ *
+ * With the following configuration, if you have the file `src/pages/en/about.astro` but not `src/pages/fr/about.astro`, the `astro build` command will generate `dist/fr/about.html` with the same content as the `dist/en/index.html` page.
+ * Your site visitor will see the English version of the page at `https://example.com/fr/about/` and will not be redirected.
+ *
+ * ```js
+ * //astro.config.mjs
+ * export default defineConfig({
+ * i18n: {
+ * defaultLocale: "en",
+ * locales: ["en", "fr"],
+ * routing: {
+ * prefixDefaultLocale: false,
+ * fallbackType: "rewrite",
+ * },
+ * fallback: {
+ * fr: "en",
+ * }
+ * },
+ * })
+ * ```
+ */
+ fallbackType: 'redirect' | 'rewrite';
+
+ /**
* @name i18n.routing.strategy
* @type {"pathname"}
* @default `"pathname"`
@@ -1775,7 +1812,7 @@ export interface AstroUserConfig {
* ```
*
* :::note
- * Loaders will not automatically [exclude files prefaced with an `_`](/en/guides/routing/#excluding-pages). Use a regular expression such as `pattern: '**\/[^_]*.md` in your loader to ignore these files.
+ * Loaders will not automatically [exclude files prefaced with an `_`](/en/guides/routing/#excluding-pages). Use a regular expression such as `pattern: '**\/[^_]*.md'` in your loader to ignore these files.
* :::
*
* #### Querying and rendering with the Content Layer API
@@ -1807,7 +1844,7 @@ export interface AstroUserConfig {
*
* const post = await getEntry('blog', Astro.params.slug);
*
- * const { Content, headings } = await render(entry);
+ * const { Content, headings } = await render(post);
* ---
*
* <Content />
diff --git a/packages/astro/src/virtual-modules/i18n.ts b/packages/astro/src/virtual-modules/i18n.ts
index 927d479aa..8f85ae5f6 100644
--- a/packages/astro/src/virtual-modules/i18n.ts
+++ b/packages/astro/src/virtual-modules/i18n.ts
@@ -3,7 +3,7 @@ import { IncorrectStrategyForI18n } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/index.js';
import * as I18nInternals from '../i18n/index.js';
import type { RedirectToFallback } from '../i18n/index.js';
-import { toRoutingStrategy } from '../i18n/utils.js';
+import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
import type { I18nInternalConfig } from '../i18n/vite-plugin-i18n.js';
import type { MiddlewareHandler } from '../types/public/common.js';
import type { AstroConfig, ValidRedirectStatus } from '../types/public/config.js';
@@ -18,6 +18,7 @@ const { defaultLocale, locales, domains, fallback, routing } = i18n!;
const base = import.meta.env.BASE_URL;
let strategy = toRoutingStrategy(routing, domains);
+let fallbackType = toFallbackType(routing);
export type GetLocaleOptions = I18nInternals.GetLocaleOptions;
@@ -264,6 +265,7 @@ if (i18n?.routing === 'manual') {
strategy,
domains,
fallback,
+ fallbackType,
});
} else {
redirectToDefaultLocale = noop('redirectToDefaultLocale');
@@ -292,6 +294,7 @@ if (i18n?.routing === 'manual') {
strategy,
domains,
fallback,
+ fallbackType,
});
} else {
notFound = noop('notFound');
@@ -314,7 +317,7 @@ if (i18n?.routing === 'manual') {
* Allows to use the build-in fallback system of Astro
*
* @param {APIContext} context The context passed to the middleware
- * @param {Response} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
+ * @param {Promise<Response>} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
*/
export let redirectToFallback: RedirectToFallback;
@@ -328,6 +331,7 @@ if (i18n?.routing === 'manual') {
strategy,
domains,
fallback,
+ fallbackType,
});
} else {
redirectToFallback = noop('useFallback');
@@ -371,11 +375,13 @@ export let middleware: (customOptions: NewAstroRoutingConfigWithoutManual) => Mi
if (i18n?.routing === 'manual') {
middleware = (customOptions: NewAstroRoutingConfigWithoutManual) => {
strategy = toRoutingStrategy(customOptions, {});
+ fallbackType = toFallbackType(customOptions);
const manifest: SSRManifest['i18n'] = {
...i18n,
fallback: undefined,
strategy,
domainLookupTable: {},
+ fallbackType,
};
return I18nInternals.createMiddleware(manifest, base, trailingSlash, format);
};
diff --git a/packages/astro/src/virtual-modules/transitions-swap-functions.ts b/packages/astro/src/virtual-modules/transitions-swap-functions.ts
new file mode 100644
index 000000000..5947533e3
--- /dev/null
+++ b/packages/astro/src/virtual-modules/transitions-swap-functions.ts
@@ -0,0 +1 @@
+export * from '../transitions/swap-functions.js';
diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts
index 63384f87b..b22a3653d 100644
--- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts
+++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts
@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url';
import { getInfoOutput } from '../cli/info/index.js';
-import { type HeadElements } from '../core/base-pipeline.js';
+import type { HeadElements, TryRewriteResult } from '../core/base-pipeline.js';
import { ASTRO_VERSION } from '../core/constants.js';
import { enhanceViteSSRError } from '../core/errors/dev/index.js';
import { AggregateError, CSSError, MarkdownError } from '../core/errors/index.js';
@@ -197,11 +197,11 @@ export class DevPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
- ): Promise<[RouteData, ComponentInstance, URL]> {
+ ): Promise<TryRewriteResult> {
if (!this.manifestData) {
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
}
- const [foundRoute, finalUrl] = findRouteToRewrite({
+ const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,
routes: this.manifestData?.routes,
@@ -210,8 +210,8 @@ export class DevPipeline extends Pipeline {
base: this.config.base,
});
- const componentInstance = await this.getComponentByRoute(foundRoute);
- return [foundRoute, componentInstance, finalUrl];
+ const componentInstance = await this.getComponentByRoute(routeData);
+ return { newUrl, pathname, componentInstance, routeData };
}
setManifestData(manifestData: ManifestData) {
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index 414034561..f1cfa16ba 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -11,7 +11,7 @@ import type { Logger } from '../core/logger/core.js';
import { createViteLoader } from '../core/module-loader/index.js';
import { injectDefaultRoutes } from '../core/routing/default.js';
import { createRouteManifest } from '../core/routing/index.js';
-import { toRoutingStrategy } from '../i18n/utils.js';
+import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
import type { AstroSettings, ManifestData } from '../types/astro.js';
import { baseMiddleware } from './base.js';
import { createController } from './controller.js';
@@ -128,6 +128,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
defaultLocale: settings.config.i18n.defaultLocale,
locales: settings.config.i18n.locales,
domainLookupTable: {},
+ fallbackType: toFallbackType(settings.config.i18n.routing),
};
}
diff --git a/packages/astro/src/vite-plugin-fileurl/index.ts b/packages/astro/src/vite-plugin-fileurl/index.ts
index 4a14323a0..73132f3af 100644
--- a/packages/astro/src/vite-plugin-fileurl/index.ts
+++ b/packages/astro/src/vite-plugin-fileurl/index.ts
@@ -1,8 +1,9 @@
import type { Plugin as VitePlugin } from 'vite';
-export default function vitePluginFileURL({}): VitePlugin {
+export default function vitePluginFileURL(): VitePlugin {
return {
name: 'astro:vite-plugin-file-url',
+ enforce: 'pre',
resolveId(source, importer) {
if (source.startsWith('file://')) {
const rest = source.slice(7);
diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts
index 2633743ae..5923377a4 100644
--- a/packages/astro/src/vite-plugin-scanner/index.ts
+++ b/packages/astro/src/vite-plugin-scanner/index.ts
@@ -2,7 +2,7 @@ import { extname } from 'node:path';
import { bold } from 'kleur/colors';
import type { Plugin as VitePlugin } from 'vite';
import { normalizePath } from 'vite';
-import { type Logger } from '../core/logger/core.js';
+import type { Logger } from '../core/logger/core.js';
import { isEndpoint, isPage, isServerLikeOutput } from '../core/util.js';
import { rootRelativePath } from '../core/viteUtils.js';
import { runHookRouteSetup } from '../integrations/hooks.js';
diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs
index 823699e15..3ff65cecf 100644
--- a/packages/astro/templates/actions.mjs
+++ b/packages/astro/templates/actions.mjs
@@ -93,7 +93,9 @@ async function handleAction(param, path, context) {
body,
headers,
});
- if (rawResult.status === 204) return;
+ if (rawResult.status === 204) {
+ return deserializeActionResult({ type: 'empty', status: 204 });
+ }
return deserializeActionResult({
type: rawResult.ok ? 'data' : 'error',
diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js
index 341e7c8d6..3c803972c 100644
--- a/packages/astro/test/actions.test.js
+++ b/packages/astro/test/actions.test.js
@@ -2,6 +2,8 @@ import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import * as cheerio from 'cheerio';
import * as devalue from 'devalue';
+import { serializeActionResult } from '../dist/actions/runtime/virtual/shared.js';
+import { REDIRECT_STATUS_CODES } from '../dist/core/constants.js';
import testAdapter from './test-adapter.js';
import { loadFixture } from './test-utils.js';
@@ -25,6 +27,28 @@ describe('Astro Actions', () => {
await devServer.stop();
});
+ it('Does not process middleware cookie for prerendered routes', async () => {
+ const cookie = new URLSearchParams();
+ cookie.append(
+ '_astroActionPayload',
+ JSON.stringify({
+ actionName: 'subscribe',
+ actionResult: serializeActionResult({
+ data: { channel: 'bholmesdev', subscribeButtonState: 'smashed' },
+ error: undefined,
+ }),
+ }),
+ );
+ const res = await fixture.fetch('/subscribe-prerendered', {
+ headers: {
+ Cookie: cookie.toString(),
+ },
+ });
+ const html = await res.text();
+ const $ = cheerio.load(html);
+ assert.equal($('body').text().trim(), 'No cookie found.');
+ });
+
it('Exposes subscribe action', async () => {
const res = await fixture.fetch('/_actions/subscribe', {
method: 'POST',
@@ -217,6 +241,71 @@ describe('Astro Actions', () => {
assert.ok($('#user'));
});
+ it('Supports effects on form input validators', async () => {
+ const formData = new FormData();
+ formData.set('password', 'benisawesome');
+ formData.set('confirmPassword', 'benisveryawesome');
+
+ const req = new Request('http://example.com/_actions/validatePassword', {
+ method: 'POST',
+ body: formData,
+ });
+
+ const res = await app.render(req);
+
+ assert.equal(res.ok, false);
+ assert.equal(res.status, 400);
+ assert.equal(res.headers.get('Content-Type'), 'application/json');
+
+ const data = await res.json();
+ assert.equal(data.type, 'AstroActionInputError');
+ assert.equal(data.issues?.[0]?.message, 'Passwords do not match');
+ });
+
+ it('Supports complex chained effects on form input validators', async () => {
+ const formData = new FormData();
+ formData.set('currentPassword', 'benisboring');
+ formData.set('newPassword', 'benisawesome');
+ formData.set('confirmNewPassword', 'benisawesome');
+
+ const req = new Request('http://example.com/_actions/validatePasswordComplex', {
+ method: 'POST',
+ body: formData,
+ });
+
+ const res = await app.render(req);
+
+ assert.equal(res.ok, true);
+ assert.equal(res.headers.get('Content-Type'), 'application/json+devalue');
+
+ const data = devalue.parse(await res.text());
+ assert.equal(Object.keys(data).length, 2, 'More keys than expected');
+ assert.deepEqual(data, {
+ currentPassword: 'benisboring',
+ newPassword: 'benisawesome',
+ });
+ });
+
+ it('Supports input form data transforms', async () => {
+ const formData = new FormData();
+ formData.set('name', 'ben');
+ formData.set('age', '42');
+
+ const req = new Request('http://example.com/_actions/transformFormInput', {
+ method: 'POST',
+ body: formData,
+ });
+
+ const res = await app.render(req);
+
+ assert.equal(res.ok, true);
+ assert.equal(res.headers.get('Content-Type'), 'application/json+devalue');
+
+ const data = devalue.parse(await res.text());
+ assert.equal(data?.name, 'ben');
+ assert.equal(data?.age, '42');
+ });
+
describe('legacy', () => {
it('Response middleware fallback', async () => {
const formData = new FormData();
@@ -348,8 +437,6 @@ describe('Astro Actions', () => {
});
});
-const validRedirectStatuses = new Set([301, 302, 303, 304, 307, 308]);
-
/**
* Follow an expected redirect response.
*
@@ -360,7 +447,7 @@ const validRedirectStatuses = new Set([301, 302, 303, 304, 307, 308]);
async function followExpectedRedirect(req, app) {
const redirect = await app.render(req, { addCookieHeader: true });
assert.ok(
- validRedirectStatuses.has(redirect.status),
+ REDIRECT_STATUS_CODES.includes(redirect.status),
`Expected redirect status, got ${redirect.status}`,
);
diff --git a/packages/astro/test/fixtures/0-css/package.json b/packages/astro/test/fixtures/0-css/package.json
index 394045795..782bddc06 100644
--- a/packages/astro/test/fixtures/0-css/package.json
+++ b/packages/astro/test/fixtures/0-css/package.json
@@ -9,7 +9,7 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "svelte": "^4.2.18",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/fixtures/actions/src/actions/index.ts b/packages/astro/test/fixtures/actions/src/actions/index.ts
index bc61ade3a..881656994 100644
--- a/packages/astro/test/fixtures/actions/src/actions/index.ts
+++ b/packages/astro/test/fixtures/actions/src/actions/index.ts
@@ -1,5 +1,10 @@
import { defineAction, ActionError, z } from 'astro:actions';
+const passwordSchema = z
+ .string()
+ .min(8, 'Password should be at least 8 chars length')
+ .max(128, 'Password length exceeded. Max 128 chars.');
+
export const server = {
subscribe: defineAction({
input: z.object({ channel: z.string() }),
@@ -44,7 +49,56 @@ export const server = {
accept: 'form',
handler: async (_, { locals }) => {
return locals.user;
- }
+ },
+ }),
+ validatePassword: defineAction({
+ accept: 'form',
+ input: z
+ .object({ password: z.string(), confirmPassword: z.string() })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: 'Passwords do not match',
+ }),
+ handler: async ({ password }) => {
+ return password;
+ },
+ }),
+ validatePasswordComplex: defineAction({
+ accept: 'form',
+ input: z
+ .object({
+ currentPassword: passwordSchema,
+ newPassword: passwordSchema,
+ confirmNewPassword: passwordSchema,
+ })
+ .required()
+ .refine(
+ ({ newPassword, confirmNewPassword }) => newPassword === confirmNewPassword,
+ 'The new password confirmation does not match',
+ )
+ .refine(
+ ({ currentPassword, newPassword }) => currentPassword !== newPassword,
+ 'The old password and the new password must not match',
+ )
+ .transform((input) => ({
+ currentPassword: input.currentPassword,
+ newPassword: input.newPassword,
+ }))
+ .pipe(
+ z.object({
+ currentPassword: passwordSchema,
+ newPassword: passwordSchema,
+ }),
+ ),
+ handler: async (data) => {
+ return data;
+ },
+ }),
+ transformFormInput: defineAction({
+ accept: 'form',
+ input: z.instanceof(FormData).transform((formData) => Object.fromEntries(formData.entries())),
+ handler: async (data) => {
+ return data;
+ },
}),
getUserOrThrow: defineAction({
accept: 'form',
@@ -57,22 +111,22 @@ export const server = {
});
}
return locals.user;
- }
+ },
}),
fireAndForget: defineAction({
handler: async () => {
return;
- }
+ },
}),
zero: defineAction({
handler: async () => {
return 0;
- }
+ },
}),
false: defineAction({
handler: async () => {
return false;
- }
+ },
}),
complexValues: defineAction({
handler: async () => {
@@ -80,7 +134,7 @@ export const server = {
date: new Date(),
set: new Set(),
url: new URL('https://example.com'),
- }
- }
- })
+ };
+ },
+ }),
};
diff --git a/packages/astro/test/fixtures/actions/src/pages/subscribe-prerendered.astro b/packages/astro/test/fixtures/actions/src/pages/subscribe-prerendered.astro
new file mode 100644
index 000000000..3d3b37772
--- /dev/null
+++ b/packages/astro/test/fixtures/actions/src/pages/subscribe-prerendered.astro
@@ -0,0 +1,17 @@
+---
+import { actions } from 'astro:actions';
+
+export const prerender = true;
+
+const result = Astro.getActionResult(actions.subscribe);
+---
+
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Document</title>
+ </head>
+ <body>{result?.data?.subscribeButtonState ?? 'No cookie found.'}</body>
+</html>
diff --git a/packages/astro/test/fixtures/alias-tsconfig-baseurl-only/package.json b/packages/astro/test/fixtures/alias-tsconfig-baseurl-only/package.json
index 6a6cb3491..0ae8cb82c 100644
--- a/packages/astro/test/fixtures/alias-tsconfig-baseurl-only/package.json
+++ b/packages/astro/test/fixtures/alias-tsconfig-baseurl-only/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/alias-tsconfig/package.json b/packages/astro/test/fixtures/alias-tsconfig/package.json
index 56047eede..833a0a068 100644
--- a/packages/astro/test/fixtures/alias-tsconfig/package.json
+++ b/packages/astro/test/fixtures/alias-tsconfig/package.json
@@ -6,6 +6,6 @@
"@astrojs/svelte": "workspace:*",
"@test/namespace-package": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/alias/package.json b/packages/astro/test/fixtures/alias/package.json
index 06d4c32ac..bd0599c5d 100644
--- a/packages/astro/test/fixtures/alias/package.json
+++ b/packages/astro/test/fixtures/alias/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/astro-children/package.json b/packages/astro/test/fixtures/astro-children/package.json
index d2e7a6d5e..038487d67 100644
--- a/packages/astro/test/fixtures/astro-children/package.json
+++ b/packages/astro/test/fixtures/astro-children/package.json
@@ -8,7 +8,7 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"preact": "^10.23.2",
- "svelte": "^4.2.18",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/fixtures/astro-client-only/package.json b/packages/astro/test/fixtures/astro-client-only/package.json
index 02eae8101..e6f71f353 100644
--- a/packages/astro/test/fixtures/astro-client-only/package.json
+++ b/packages/astro/test/fixtures/astro-client-only/package.json
@@ -9,6 +9,6 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/astro-dynamic/package.json b/packages/astro/test/fixtures/astro-dynamic/package.json
index 30c80157b..3d606041a 100644
--- a/packages/astro/test/fixtures/astro-dynamic/package.json
+++ b/packages/astro/test/fixtures/astro-dynamic/package.json
@@ -8,6 +8,6 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/astro-slots-nested/package.json b/packages/astro/test/fixtures/astro-slots-nested/package.json
index 229bd2560..4f3ed29e0 100644
--- a/packages/astro/test/fixtures/astro-slots-nested/package.json
+++ b/packages/astro/test/fixtures/astro-slots-nested/package.json
@@ -12,8 +12,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/fixtures/component-library/package.json b/packages/astro/test/fixtures/component-library/package.json
index a8b89e9ee..96f5cecac 100644
--- a/packages/astro/test/fixtures/component-library/package.json
+++ b/packages/astro/test/fixtures/component-library/package.json
@@ -11,6 +11,6 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/css-dangling-references/package.json b/packages/astro/test/fixtures/css-dangling-references/package.json
index 2b9a90cb6..be0392db0 100644
--- a/packages/astro/test/fixtures/css-dangling-references/package.json
+++ b/packages/astro/test/fixtures/css-dangling-references/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
} \ No newline at end of file
diff --git a/packages/astro/test/fixtures/error-bad-js/src/env.d.ts b/packages/astro/test/fixtures/error-bad-js/src/env.d.ts
deleted file mode 100644
index f964fe0cf..000000000
--- a/packages/astro/test/fixtures/error-bad-js/src/env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="astro/client" />
diff --git a/packages/astro/test/fixtures/error-build-location/src/env.d.ts b/packages/astro/test/fixtures/error-build-location/src/env.d.ts
deleted file mode 100644
index f964fe0cf..000000000
--- a/packages/astro/test/fixtures/error-build-location/src/env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="astro/client" />
diff --git a/packages/astro/test/fixtures/error-non-error/src/env.d.ts b/packages/astro/test/fixtures/error-non-error/src/env.d.ts
deleted file mode 100644
index f964fe0cf..000000000
--- a/packages/astro/test/fixtures/error-non-error/src/env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="astro/client" />
diff --git a/packages/astro/test/fixtures/fetch/package.json b/packages/astro/test/fixtures/fetch/package.json
index 52f60a20f..97aa25a78 100644
--- a/packages/astro/test/fixtures/fetch/package.json
+++ b/packages/astro/test/fixtures/fetch/package.json
@@ -8,7 +8,7 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"preact": "^10.23.2",
- "svelte": "^4.2.18",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/fixtures/jsx/package.json b/packages/astro/test/fixtures/jsx/package.json
index 2e45e6575..6d32dffe4 100644
--- a/packages/astro/test/fixtures/jsx/package.json
+++ b/packages/astro/test/fixtures/jsx/package.json
@@ -15,8 +15,8 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/fixtures/large-array/package.json b/packages/astro/test/fixtures/large-array/package.json
index 9f1f25828..0bf422b67 100644
--- a/packages/astro/test/fixtures/large-array/package.json
+++ b/packages/astro/test/fixtures/large-array/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/solid-js": "workspace:*",
"astro": "workspace:*",
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/test/fixtures/postcss/package.json b/packages/astro/test/fixtures/postcss/package.json
index 7ea4f8378..fad256924 100644
--- a/packages/astro/test/fixtures/postcss/package.json
+++ b/packages/astro/test/fixtures/postcss/package.json
@@ -9,8 +9,8 @@
"astro": "workspace:*",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
- "solid-js": "^1.8.21",
- "svelte": "^4.2.18",
+ "solid-js": "^1.8.22",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
},
"devDependencies": {
diff --git a/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx b/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
new file mode 100644
index 000000000..69940f730
--- /dev/null
+++ b/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
@@ -0,0 +1,8 @@
+import { h } from 'preact';
+
+export default ({ signalsArray }) => {
+ return <div class="preact-signal-array">
+ <h1>{signalsArray[0]} {signalsArray[3]}</h1>
+ <p>{signalsArray[1].value}-{signalsArray[2].value}-{signalsArray[4].value}</p>
+ </div>
+}
diff --git a/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx b/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
new file mode 100644
index 000000000..6187ce8c5
--- /dev/null
+++ b/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
@@ -0,0 +1,8 @@
+import { h } from 'preact';
+
+export default ({ signalsObject }) => {
+ return <div class="preact-signal-object">
+ <h1>{signalsObject.title}</h1>
+ <p>{signalsObject.counter.value}</p>
+ </div>
+}
diff --git a/packages/astro/test/fixtures/preact-component/src/pages/signals.astro b/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
index b68fde36d..37b43a73c 100644
--- a/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
+++ b/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
@@ -1,7 +1,10 @@
---
import { signal } from '@preact/signals';
import Signals from '../components/Signals';
+import SignalsInArray from '../components/SignalsInArray';
+import SignalsInObject from '../components/SignalsInObject';
const count = signal(1);
+const secondCount = signal(2);
---
<html>
<head>
@@ -10,5 +13,7 @@ const count = signal(1);
<body>
<Signals client:load count={count} />
<Signals client:load count={count} />
+ <SignalsInArray client:load signalsArray={["I'm not a signal", count, count, 12345, secondCount]} />
+ <SignalsInObject client:load signalsObject={{title:'I am a title', counter: count}} />
</body>
</html>
diff --git a/packages/astro/test/fixtures/react-and-solid/package.json b/packages/astro/test/fixtures/react-and-solid/package.json
index dbb45a68f..5df316b51 100644
--- a/packages/astro/test/fixtures/react-and-solid/package.json
+++ b/packages/astro/test/fixtures/react-and-solid/package.json
@@ -7,6 +7,6 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro b/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro
index d468d103b..bbc1a2d9e 100644
--- a/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro
+++ b/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro
@@ -1,6 +1,5 @@
---
const { slug } = Astro.params;
-console.log("is it here???", Astro.params)
export const prerender = false;
---
<html>
diff --git a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro
index 70ce07395..f7e38bbc5 100644
--- a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro
+++ b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro
@@ -1,3 +1,3 @@
---
-return Astro.rewrite("/")
+return Astro.rewrite("/base")
---
diff --git a/packages/astro/test/fixtures/server-islands/hybrid/package.json b/packages/astro/test/fixtures/server-islands/hybrid/package.json
index fdb447b0e..03e184e63 100644
--- a/packages/astro/test/fixtures/server-islands/hybrid/package.json
+++ b/packages/astro/test/fixtures/server-islands/hybrid/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/server-islands/ssr/package.json b/packages/astro/test/fixtures/server-islands/ssr/package.json
index fa6e000dd..16e044fe3 100644
--- a/packages/astro/test/fixtures/server-islands/ssr/package.json
+++ b/packages/astro/test/fixtures/server-islands/ssr/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/slots-solid/package.json b/packages/astro/test/fixtures/slots-solid/package.json
index 55d2cfa32..59ebea174 100644
--- a/packages/astro/test/fixtures/slots-solid/package.json
+++ b/packages/astro/test/fixtures/slots-solid/package.json
@@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/solid-js": "workspace:*",
"astro": "workspace:*",
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/test/fixtures/slots-svelte/package.json b/packages/astro/test/fixtures/slots-svelte/package.json
index 94d15cad2..ddfa80d33 100644
--- a/packages/astro/test/fixtures/slots-svelte/package.json
+++ b/packages/astro/test/fixtures/slots-svelte/package.json
@@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/solid-component/deps/solid-jsx-component/package.json b/packages/astro/test/fixtures/solid-component/deps/solid-jsx-component/package.json
index 32042224f..976ba6604 100644
--- a/packages/astro/test/fixtures/solid-component/deps/solid-jsx-component/package.json
+++ b/packages/astro/test/fixtures/solid-component/deps/solid-jsx-component/package.json
@@ -10,6 +10,6 @@
}
},
"dependencies": {
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/test/fixtures/solid-component/package.json b/packages/astro/test/fixtures/solid-component/package.json
index f1f87f2b2..2dc56f6cc 100644
--- a/packages/astro/test/fixtures/solid-component/package.json
+++ b/packages/astro/test/fixtures/solid-component/package.json
@@ -7,6 +7,6 @@
"@solidjs/router": "^0.14.3",
"@test/solid-jsx-component": "file:./deps/solid-jsx-component",
"astro": "workspace:*",
- "solid-js": "^1.8.21"
+ "solid-js": "^1.8.22"
}
}
diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/package.json b/packages/astro/test/fixtures/ssr-prerender-chunks/package.json
index 8c8adac13..c386358b8 100644
--- a/packages/astro/test/fixtures/ssr-prerender-chunks/package.json
+++ b/packages/astro/test/fixtures/ssr-prerender-chunks/package.json
@@ -5,7 +5,7 @@
"dependencies": {
"@astrojs/react": "workspace:*",
"@test/ssr-prerender-chunks-test-adapter": "link:./deps/test-adapter",
- "@types/react": "^18.3.3",
+ "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"astro": "workspace:*",
"react": "^18.3.1",
diff --git a/packages/astro/test/fixtures/svelte-component/package.json b/packages/astro/test/fixtures/svelte-component/package.json
index 42b4ca310..830d980b7 100644
--- a/packages/astro/test/fixtures/svelte-component/package.json
+++ b/packages/astro/test/fixtures/svelte-component/package.json
@@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18"
+ "svelte": "^4.2.19"
}
}
diff --git a/packages/astro/test/fixtures/vue-with-multi-renderer/package.json b/packages/astro/test/fixtures/vue-with-multi-renderer/package.json
index 65be000bf..f91b6a9c3 100644
--- a/packages/astro/test/fixtures/vue-with-multi-renderer/package.json
+++ b/packages/astro/test/fixtures/vue-with-multi-renderer/package.json
@@ -6,7 +6,7 @@
"@astrojs/svelte": "workspace:*",
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
- "svelte": "^4.2.18",
+ "svelte": "^4.2.19",
"vue": "^3.4.38"
}
}
diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js
index df8083b81..ddb31762f 100644
--- a/packages/astro/test/i18n-routing.test.js
+++ b/packages/astro/test/i18n-routing.test.js
@@ -1929,3 +1929,106 @@ describe('SSR fallback from missing locale index to default locale index', () =>
assert.equal(response.headers.get('location'), '/');
});
});
+
+describe('Fallback rewrite dev server', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let devServer;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/i18n-routing-fallback/',
+ i18n: {
+ defaultLocale: 'en',
+ locales: ['en', 'fr'],
+ routing: {
+ prefixDefaultLocale: false,
+ },
+ fallback: {
+ fr: 'en',
+ },
+ fallbackType: 'rewrite',
+ },
+ });
+ devServer = await fixture.startDevServer();
+ });
+ after(async () => {
+ devServer.stop();
+ });
+
+ it('should correctly rewrite to en', async () => {
+ const html = await fixture.fetch('/fr').then((res) => res.text());
+ assert.match(html, /Hello/);
+ // assert.fail()
+ });
+});
+
+describe('Fallback rewrite SSG', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/i18n-routing-fallback/',
+ i18n: {
+ defaultLocale: 'en',
+ locales: ['en', 'fr'],
+ routing: {
+ prefixDefaultLocale: false,
+ fallbackType: 'rewrite',
+ },
+ fallback: {
+ fr: 'en',
+ },
+ },
+ });
+ await fixture.build();
+ // app = await fixture.loadTestAdapterApp();
+ });
+
+ it('should correctly rewrite to en', async () => {
+ const html = await fixture.readFile('/fr/index.html');
+ assert.match(html, /Hello/);
+ // assert.fail()
+ });
+});
+
+describe('Fallback rewrite SSR', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+ let app;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/i18n-routing-fallback/',
+ output: 'server',
+ outDir: './dist/i18n-routing-fallback',
+ build: {
+ client: './dist/i18n-routing-fallback/client',
+ server: './dist/i18n-routing-fallback/server',
+ },
+ adapter: testAdapter(),
+ i18n: {
+ defaultLocale: 'en',
+ locales: ['en', 'fr'],
+ routing: {
+ prefixDefaultLocale: false,
+ fallbackType: 'rewrite',
+ },
+ fallback: {
+ fr: 'en',
+ },
+ },
+ });
+ await fixture.build();
+ app = await fixture.loadTestAdapterApp();
+ });
+
+ it('should correctly rewrite to en', async () => {
+ const request = new Request('http://example.com/fr');
+ const response = await app.render(request);
+ assert.equal(response.status, 200);
+ const html = await response.text();
+ assert.match(html, /Hello/);
+ });
+});
diff --git a/packages/astro/test/preact-component.test.js b/packages/astro/test/preact-component.test.js
index e8c89d5bf..f5b5c7233 100644
--- a/packages/astro/test/preact-component.test.js
+++ b/packages/astro/test/preact-component.test.js
@@ -100,4 +100,44 @@ describe('Preact component', () => {
assert.notEqual(sigs1.count, undefined);
assert.equal(sigs1.count, sigs2.count);
});
+
+ it('Can use signals in array', async () => {
+ const html = await fixture.readFile('/signals/index.html');
+ const $ = cheerio.load(html);
+ const element = $('.preact-signal-array');
+ assert.equal(element.length, 1);
+
+ const sigs1Raw = $($('astro-island')[2]).attr('data-preact-signals');
+
+ const sigs1 = JSON.parse(sigs1Raw);
+
+ assert.deepEqual(sigs1, {
+ signalsArray: [
+ ['p0', 1],
+ ['p0', 2],
+ ['p1', 4],
+ ],
+ });
+
+ assert.equal(element.find('h1').text(), "I'm not a signal 12345");
+ assert.equal(element.find('p').text(), '1-1-2');
+ });
+
+ it('Can use signals in object', async () => {
+ const html = await fixture.readFile('/signals/index.html');
+ const $ = cheerio.load(html);
+ const element = $('.preact-signal-object');
+ assert.equal(element.length, 1);
+
+ const sigs1Raw = $($('astro-island')[3]).attr('data-preact-signals');
+
+ const sigs1 = JSON.parse(sigs1Raw);
+
+ assert.deepEqual(sigs1, {
+ signalsObject: [['p0', 'counter']],
+ });
+
+ assert.equal(element.find('h1').text(), 'I am a title');
+ assert.equal(element.find('p').text(), '1');
+ });
});
diff --git a/packages/astro/test/rewrite.test.js b/packages/astro/test/rewrite.test.js
index 7839e7d33..10d7c70d2 100644
--- a/packages/astro/test/rewrite.test.js
+++ b/packages/astro/test/rewrite.test.js
@@ -104,7 +104,7 @@ describe('Dev rewrite, trailing slash -> never, with base', () => {
});
it('should rewrite to the homepage', async () => {
- const html = await fixture.fetch('/foo').then((res) => res.text());
+ const html = await fixture.fetch('/base/foo').then((res) => res.text());
const $ = cheerioLoad(html);
assert.equal($('h1').text(), 'Index');
diff --git a/packages/astro/test/units/actions/form-data-to-object.test.js b/packages/astro/test/units/actions/form-data-to-object.test.js
index 136909305..e9f52a13f 100644
--- a/packages/astro/test/units/actions/form-data-to-object.test.js
+++ b/packages/astro/test/units/actions/form-data-to-object.test.js
@@ -192,4 +192,22 @@ describe('formDataToObject', () => {
assert.equal(res.files instanceof Array, true);
assert.deepEqual(res.files, [file1, file2]);
});
+
+ it('should allow object passthrough when chaining .passthrough() on root object', () => {
+ const formData = new FormData();
+ formData.set('expected', '42');
+ formData.set('unexpected', '42');
+
+ const input = z
+ .object({
+ expected: z.number(),
+ })
+ .passthrough();
+
+ const res = formDataToObject(formData, input);
+ assert.deepEqual(res, {
+ expected: 42,
+ unexpected: '42',
+ });
+ });
});