From 5b4070efef877a77247bb05a4806b75f22e557c8 Mon Sep 17 00:00:00 2001
From: Ben Holmes
Date: Thu, 29 Aug 2024 06:13:49 -0400
Subject: [PATCH 1/2] Actions stable release (#11843)
* feat: baseline experimental actions
* feat(test): remove experimental config
* feat: remove getActionProps()
* feat: make actions file non-break
* feat: detect actions usage
* chore: changeset
* feat: improve actions usage check
* refactor: remove define action symbol now that we check server exp
* fix: remove old import
* chore: move actionsIntegration to top import
* fix: warn only when actions are used
* fix: srcDir check
* refactor: split out action plugins to simplify integration
* feat: new integration and plugins
* chore: update error hints
* fix(test): pass default src dir
* feat: add ActionNotFoundError
* fix: handle json parse errors in deserializer
* chore: unused import
* 500 -> 404
* New `astro:schema` module (#11810)
* feat: expose zod from astro:schema
* chore: changeset
* chore: update release strategy in changeset
* fix: move deprecated notice to type def
* fix: update config doc reference
* chore: remove z from astro:actions
* edit: changeset with minor release note remove
* wip: increase button click timeouts
* Revert "wip: increase button click timeouts"
This reverts commit a870bc2dc4bdfd77b8b4fc54b62d8bde01d20c14.
* chore: remove content collections disclaimer
* fix: undo biome change
* agh tabs
* agh newlines
* fix: bad docs merge
* wip: add back timeout extension
* fix(test): astro schema import
* refactor: move static output error to config done
* refactor: usesActions -> isActionsFilePresent
* fix: check whether startup and current value disagree
* chore: unused import
* edit: sell actions a little more
* changeset nit
---------
Co-authored-by: Sarah Rainsberger
---
.changeset/perfect-wasps-grow.md | 17 +++
.changeset/spicy-suits-explode.md | 38 ++++++
packages/astro/client.d.ts | 4 +
.../fixtures/actions-blog/astro.config.mjs | 3 -
.../actions-blog/src/actions/index.ts | 3 +-
.../src/components/PostComment.tsx | 46 +++----
.../actions-react-19/astro.config.mjs | 3 -
.../actions-react-19/src/actions/index.ts | 3 +-
packages/astro/playwright.config.js | 5 +-
packages/astro/playwright.firefox.config.js | 5 +-
packages/astro/src/@types/astro.ts | 101 --------------
packages/astro/src/actions/index.ts | 125 ------------------
packages/astro/src/actions/integration.ts | 52 ++++++++
packages/astro/src/actions/plugins.ts | 91 +++++++++++++
.../astro/src/actions/runtime/middleware.ts | 45 -------
packages/astro/src/actions/runtime/route.ts | 11 +-
.../src/actions/runtime/virtual/client.ts | 9 --
.../src/actions/runtime/virtual/get-action.ts | 19 ++-
.../src/actions/runtime/virtual/server.ts | 2 -
.../src/actions/runtime/virtual/shared.ts | 36 ++---
packages/astro/src/actions/utils.ts | 52 ++++++++
packages/astro/src/core/config/schema.ts | 2 -
packages/astro/src/core/create-vite.ts | 7 +
packages/astro/src/core/errors/errors-data.ts | 39 ++----
packages/astro/src/integrations/hooks.ts | 7 +-
packages/astro/test/actions.test.js | 39 ------
.../test/fixtures/actions/astro.config.mjs | 3 -
.../fixtures/actions/src/actions/index.ts | 3 +-
.../astro/test/units/integrations/api.test.js | 9 ++
29 files changed, 361 insertions(+), 418 deletions(-)
create mode 100644 .changeset/perfect-wasps-grow.md
create mode 100644 .changeset/spicy-suits-explode.md
delete mode 100644 packages/astro/src/actions/index.ts
create mode 100644 packages/astro/src/actions/integration.ts
create mode 100644 packages/astro/src/actions/plugins.ts
diff --git a/.changeset/perfect-wasps-grow.md b/.changeset/perfect-wasps-grow.md
new file mode 100644
index 0000000000..855c709837
--- /dev/null
+++ b/.changeset/perfect-wasps-grow.md
@@ -0,0 +1,17 @@
+---
+'astro': minor
+---
+
+Exposes `z` from the new `astro:schema` module. This is the new recommended import source for all Zod utilities when using Astro Actions.
+
+## Migration for Astro Actions users
+
+`z` will no longer be exposed from `astro:actions`. To use `z` in your actions, import it from `astro:schema` instead:
+
+```diff
+import {
+ defineAction,
+- z,
+} from 'astro:actions';
++ import { z } from 'astro:schema';
+```
diff --git a/.changeset/spicy-suits-explode.md b/.changeset/spicy-suits-explode.md
new file mode 100644
index 0000000000..1e43ca4a2b
--- /dev/null
+++ b/.changeset/spicy-suits-explode.md
@@ -0,0 +1,38 @@
+---
+"astro": minor
+---
+
+The Astro Actions API introduced behind a flag in [v4.8.0](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#480) is no longer experimental and is available for general use.
+
+Astro Actions allow you to define and call backend functions with type-safety, performing data fetching, JSON parsing, and input validation for you.
+
+Actions can be called from client-side components and HTML forms. This gives you to flexibility to build apps using any technology: React, Svelte, HTMX, or just plain Astro components. This example calls a newsletter action and renders the result using an Astro component:
+
+```astro
+---
+// src/pages/newsletter.astro
+import { actions } from 'astro:actions';
+const result = Astro.getActionResult(actions.newsletter);
+---
+{result && !result.error && Thanks for signing up!
}
+
+```
+
+If you were previously using this feature, please remove the experimental flag from your Astro config:
+
+```diff
+import { defineConfig } from 'astro'
+
+export default defineConfig({
+- experimental: {
+- actions: true,
+- }
+})
+```
+
+If you have been waiting for stabilization before using Actions, you can now do so.
+
+For more information and usage examples, see our [brand new Actions guide](https://docs.astro.build/en/guides/actions).
diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts
index 64b488a16d..4d4b031432 100644
--- a/packages/astro/client.d.ts
+++ b/packages/astro/client.d.ts
@@ -175,6 +175,10 @@ declare module 'astro:components' {
export * from 'astro/components';
}
+declare module 'astro:schema' {
+ export * from 'astro/zod';
+}
+
type MD = import('./dist/@types/astro.js').MarkdownInstance>;
interface ExportedMarkdownModuleEntities {
frontmatter: MD['frontmatter'];
diff --git a/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
index acbed1768b..8f9f0e3539 100644
--- a/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
@@ -11,7 +11,4 @@ export default defineConfig({
adapter: node({
mode: 'standalone',
}),
- experimental: {
- actions: true,
- },
});
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 7b640be516..43ffb43d42 100644
--- a/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
+++ b/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
@@ -1,5 +1,6 @@
import { db, Comment, Likes, eq, sql } from 'astro:db';
-import { ActionError, defineAction, z } from 'astro:actions';
+import { ActionError, defineAction } from 'astro:actions';
+import { z } from 'astro:schema';
import { getCollection } from 'astro:content';
export const server = {
diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx
index b6b6bcea1c..781206b84a 100644
--- a/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx
+++ b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx
@@ -1,4 +1,4 @@
-import { getActionProps, actions, isInputError } from 'astro:actions';
+import { actions, isInputError } from 'astro:actions';
import { useState } from 'react';
export function PostComment({
@@ -17,6 +17,7 @@ export function PostComment({
)}
-
+
- {comments.map((c) => (
-
- {c.body}
- {c.author}
-
- ))}
+ {comments.map((c) => (
+
+ {c.body}
+ {c.author}
+
+ ))}
>
);
diff --git a/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs b/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
index acbed1768b..8f9f0e3539 100644
--- a/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
@@ -11,7 +11,4 @@ export default defineConfig({
adapter: node({
mode: 'standalone',
}),
- experimental: {
- actions: true,
- },
});
diff --git a/packages/astro/e2e/fixtures/actions-react-19/src/actions/index.ts b/packages/astro/e2e/fixtures/actions-react-19/src/actions/index.ts
index cd42207729..754db0171e 100644
--- a/packages/astro/e2e/fixtures/actions-react-19/src/actions/index.ts
+++ b/packages/astro/e2e/fixtures/actions-react-19/src/actions/index.ts
@@ -1,5 +1,6 @@
import { db, Likes, eq, sql } from 'astro:db';
-import { defineAction, z, type SafeResult } from 'astro:actions';
+import { defineAction, type SafeResult } from 'astro:actions';
+import { z } from 'astro:schema';
import { experimental_getActionState } from '@astrojs/react/actions';
export const server = {
diff --git a/packages/astro/playwright.config.js b/packages/astro/playwright.config.js
index 26572c66c8..d9ea2c4691 100644
--- a/packages/astro/playwright.config.js
+++ b/packages/astro/playwright.config.js
@@ -7,7 +7,10 @@ process.stdout.isTTY = false;
export default defineConfig({
testMatch: 'e2e/*.test.js',
- timeout: 40000,
+ timeout: 40_000,
+ expect: {
+ timeout: 6_000,
+ },
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
diff --git a/packages/astro/playwright.firefox.config.js b/packages/astro/playwright.firefox.config.js
index 00b82d9997..140f288f9e 100644
--- a/packages/astro/playwright.firefox.config.js
+++ b/packages/astro/playwright.firefox.config.js
@@ -8,7 +8,10 @@ process.stdout.isTTY = false;
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'],
- timeout: 40000,
+ timeout: 40_000,
+ expect: {
+ timeout: 6_000,
+ },
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 52a9af058a..5aeb6d112c 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1833,107 +1833,6 @@ export interface AstroUserConfig {
*/
directRenderScript?: boolean;
- /**
- * @docs
- * @name experimental.actions
- * @type {boolean}
- * @default `false`
- * @version 4.8.0
- * @description
- *
- * Actions help you write type-safe backend functions you can call from anywhere. Enable server rendering [using the `output` property](https://docs.astro.build/en/basics/rendering-modes/#on-demand-rendered) and add the `actions` flag to the `experimental` object:
- *
- * ```js
- * {
- * output: 'hybrid', // or 'server'
- * experimental: {
- * actions: true,
- * },
- * }
- * ```
- *
- * Declare all your actions in `src/actions/index.ts`. This file is the global actions handler.
- *
- * Define an action using the `defineAction()` utility from the `astro:actions` module. An action accepts the `handler` property to define your server-side request handler. If your action accepts arguments, apply the `input` property to validate parameters with Zod.
- *
- * This example defines two actions: `like` and `comment`. The `like` action accepts a JSON object with a `postId` string, while the `comment` action accepts [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) with `postId`, `author`, and `body` strings. Each `handler` updates your database and return a type-safe response.
- *
- * ```ts
- * // src/actions/index.ts
- * import { defineAction, z } from "astro:actions";
- *
- * export const server = {
- * like: defineAction({
- * input: z.object({ postId: z.string() }),
- * handler: async ({ postId }) => {
- * // update likes in db
- *
- * return likes;
- * },
- * }),
- * comment: defineAction({
- * accept: 'form',
- * input: z.object({
- * postId: z.string(),
- * author: z.string(),
- * body: z.string(),
- * }),
- * handler: async ({ postId }) => {
- * // insert comments in db
- *
- * return comment;
- * },
- * }),
- * };
- * ```
- *
- * Then, call an action from your client components using the `actions` object from `astro:actions`. You can pass a type-safe object when using JSON, or a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) object when using `accept: 'form'` in your action definition.
- *
- * This example calls the `like` and `comment` actions from a React component:
- *
- * ```tsx "actions"
- * // src/components/blog.tsx
- * import { actions } from "astro:actions";
- * import { useState } from "react";
- *
- * export function Like({ postId }: { postId: string }) {
- * const [likes, setLikes] = useState(0);
- * return (
- *
- * );
- * }
- *
- * export function Comment({ postId }: { postId: string }) {
- * return (
- *
- * );
- * }
- * ```
- *
- * For a complete overview, and to give feedback on this experimental API, see the [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md).
- */
- actions?: boolean;
-
/**
* @docs
* @name experimental.contentCollectionCache
diff --git a/packages/astro/src/actions/index.ts b/packages/astro/src/actions/index.ts
deleted file mode 100644
index 2423b7017d..0000000000
--- a/packages/astro/src/actions/index.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import fsMod from 'node:fs';
-import type { Plugin as VitePlugin } from 'vite';
-import type { AstroIntegration, AstroSettings } from '../@types/astro.js';
-import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js';
-import { AstroError } from '../core/errors/errors.js';
-import { isServerLikeOutput, viteID } from '../core/util.js';
-import {
- ACTIONS_TYPES_FILE,
- NOOP_ACTIONS,
- RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
- RESOLVED_VIRTUAL_MODULE_ID,
- VIRTUAL_INTERNAL_MODULE_ID,
- VIRTUAL_MODULE_ID,
-} from './consts.js';
-
-export default function astroActions({
- fs = fsMod,
- settings,
-}: {
- fs?: typeof fsMod;
- settings: AstroSettings;
-}): AstroIntegration {
- return {
- name: VIRTUAL_MODULE_ID,
- hooks: {
- async 'astro:config:setup'(params) {
- if (!isServerLikeOutput(params.config)) {
- const error = new AstroError(ActionsWithoutServerOutputError);
- error.stack = undefined;
- throw error;
- }
-
- params.updateConfig({
- vite: {
- plugins: [vitePluginUserActions({ settings }), vitePluginActions(fs)],
- },
- });
-
- params.injectRoute({
- pattern: '/_actions/[...path]',
- entrypoint: 'astro/actions/runtime/route.js',
- prerender: false,
- });
-
- params.addMiddleware({
- entrypoint: 'astro/actions/runtime/middleware.js',
- order: 'post',
- });
- },
- 'astro:config:done': (params) => {
- const stringifiedActionsImport = JSON.stringify(
- viteID(new URL('./actions', params.config.srcDir)),
- );
- settings.injectedTypes.push({
- filename: ACTIONS_TYPES_FILE,
- content: `declare module "astro:actions" {
- type Actions = typeof import(${stringifiedActionsImport})["server"];
-
- export const actions: Actions;
-}`,
- });
- },
- },
- };
-}
-
-/**
- * This plugin is responsible to load the known file `actions/index.js` / `actions.js`
- * If the file doesn't exist, it returns an empty object.
- * @param settings
- */
-export function vitePluginUserActions({ settings }: { settings: AstroSettings }): VitePlugin {
- let resolvedActionsId: string;
- return {
- name: '@astro/plugin-actions',
- async resolveId(id) {
- if (id === NOOP_ACTIONS) {
- return NOOP_ACTIONS;
- }
- if (id === VIRTUAL_INTERNAL_MODULE_ID) {
- const resolvedModule = await this.resolve(
- `${decodeURI(new URL('actions', settings.config.srcDir).pathname)}`,
- );
-
- if (!resolvedModule) {
- return NOOP_ACTIONS;
- }
- resolvedActionsId = resolvedModule.id;
- return RESOLVED_VIRTUAL_INTERNAL_MODULE_ID;
- }
- },
-
- load(id) {
- if (id === NOOP_ACTIONS) {
- return 'export const server = {}';
- } else if (id === RESOLVED_VIRTUAL_INTERNAL_MODULE_ID) {
- return `export { server } from '${resolvedActionsId}';`;
- }
- },
- };
-}
-
-const vitePluginActions = (fs: typeof fsMod): VitePlugin => ({
- name: VIRTUAL_MODULE_ID,
- enforce: 'pre',
- resolveId(id) {
- if (id === VIRTUAL_MODULE_ID) {
- return RESOLVED_VIRTUAL_MODULE_ID;
- }
- },
- async load(id, opts) {
- if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
-
- let code = await fs.promises.readFile(
- new URL('../../templates/actions.mjs', import.meta.url),
- 'utf-8',
- );
- if (opts?.ssr) {
- code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`;
- } else {
- code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`;
- }
- return code;
- },
-});
diff --git a/packages/astro/src/actions/integration.ts b/packages/astro/src/actions/integration.ts
new file mode 100644
index 0000000000..624535b3e9
--- /dev/null
+++ b/packages/astro/src/actions/integration.ts
@@ -0,0 +1,52 @@
+import type { AstroIntegration, AstroSettings } from '../@types/astro.js';
+import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js';
+import { AstroError } from '../core/errors/errors.js';
+import { isServerLikeOutput, viteID } from '../core/util.js';
+import { ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js';
+
+/**
+ * This integration is applied when the user is using Actions in their project.
+ * It will inject the necessary routes and middlewares to handle actions.
+ */
+export default function astroIntegrationActionsRouteHandler({
+ settings,
+}: {
+ settings: AstroSettings;
+}): AstroIntegration {
+ return {
+ name: VIRTUAL_MODULE_ID,
+ hooks: {
+ async 'astro:config:setup'(params) {
+ params.injectRoute({
+ pattern: '/_actions/[...path]',
+ entrypoint: 'astro/actions/runtime/route.js',
+ prerender: false,
+ });
+
+ params.addMiddleware({
+ entrypoint: 'astro/actions/runtime/middleware.js',
+ order: 'post',
+ });
+ },
+ 'astro:config:done': async (params) => {
+ if (!isServerLikeOutput(params.config)) {
+ const error = new AstroError(ActionsWithoutServerOutputError);
+ error.stack = undefined;
+ throw error;
+ }
+
+ const stringifiedActionsImport = JSON.stringify(
+ viteID(new URL('./actions', params.config.srcDir)),
+ );
+ settings.injectedTypes.push({
+ filename: ACTIONS_TYPES_FILE,
+ content: `declare module "astro:actions" {
+ type Actions = typeof import(${stringifiedActionsImport})["server"];
+
+ export const actions: Actions;
+}`,
+ });
+ },
+ },
+ };
+}
diff --git a/packages/astro/src/actions/plugins.ts b/packages/astro/src/actions/plugins.ts
new file mode 100644
index 0000000000..1323eec626
--- /dev/null
+++ b/packages/astro/src/actions/plugins.ts
@@ -0,0 +1,91 @@
+import type fsMod from 'node:fs';
+import type { Plugin as VitePlugin } from 'vite';
+import type { AstroSettings } from '../@types/astro.js';
+import {
+ NOOP_ACTIONS,
+ RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
+ RESOLVED_VIRTUAL_MODULE_ID,
+ VIRTUAL_INTERNAL_MODULE_ID,
+ VIRTUAL_MODULE_ID,
+} from './consts.js';
+import { isActionsFilePresent } from './utils.js';
+
+/**
+ * This plugin is responsible to load the known file `actions/index.js` / `actions.js`
+ * If the file doesn't exist, it returns an empty object.
+ * @param settings
+ */
+export function vitePluginUserActions({ settings }: { settings: AstroSettings }): VitePlugin {
+ let resolvedActionsId: string;
+ return {
+ name: '@astro/plugin-actions',
+ async resolveId(id) {
+ if (id === NOOP_ACTIONS) {
+ return NOOP_ACTIONS;
+ }
+ if (id === VIRTUAL_INTERNAL_MODULE_ID) {
+ const resolvedModule = await this.resolve(
+ `${decodeURI(new URL('actions', settings.config.srcDir).pathname)}`,
+ );
+
+ if (!resolvedModule) {
+ return NOOP_ACTIONS;
+ }
+ resolvedActionsId = resolvedModule.id;
+ return RESOLVED_VIRTUAL_INTERNAL_MODULE_ID;
+ }
+ },
+
+ load(id) {
+ if (id === NOOP_ACTIONS) {
+ return 'export const server = {}';
+ } else if (id === RESOLVED_VIRTUAL_INTERNAL_MODULE_ID) {
+ return `export { server } from '${resolvedActionsId}';`;
+ }
+ },
+ };
+}
+
+export function vitePluginActions({
+ fs,
+ settings,
+}: {
+ fs: typeof fsMod;
+ settings: AstroSettings;
+}): VitePlugin {
+ return {
+ name: VIRTUAL_MODULE_ID,
+ enforce: 'pre',
+ resolveId(id) {
+ if (id === VIRTUAL_MODULE_ID) {
+ return RESOLVED_VIRTUAL_MODULE_ID;
+ }
+ },
+ async configureServer(server) {
+ const filePresentOnStartup = await isActionsFilePresent(fs, settings.config.srcDir);
+ // Watch for the actions file to be created.
+ async function watcherCallback() {
+ const filePresent = await isActionsFilePresent(fs, settings.config.srcDir);
+ if (filePresentOnStartup !== filePresent) {
+ server.restart();
+ }
+ }
+ server.watcher.on('add', watcherCallback);
+ server.watcher.on('change', watcherCallback);
+ },
+ async load(id, opts) {
+ if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
+
+ let code = await fs.promises.readFile(
+ new URL('../../templates/actions.mjs', import.meta.url),
+ 'utf-8',
+ );
+ if (opts?.ssr) {
+ code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`;
+ } else {
+ code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`;
+ }
+ return code;
+ },
+ };
+}
diff --git a/packages/astro/src/actions/runtime/middleware.ts b/packages/astro/src/actions/runtime/middleware.ts
index 4f0a732b6c..c12b64b6d5 100644
--- a/packages/astro/src/actions/runtime/middleware.ts
+++ b/packages/astro/src/actions/runtime/middleware.ts
@@ -1,7 +1,5 @@
import { yellow } from 'kleur/colors';
import type { APIContext, MiddlewareNext } from '../../@types/astro.js';
-import { ActionQueryStringInvalidError } from '../../core/errors/errors-data.js';
-import { AstroError } from '../../core/errors/errors.js';
import { defineMiddleware } from '../../core/middleware/index.js';
import { ACTION_QUERY_PARAMS } from '../consts.js';
import { formContentTypes, hasContentType } from './utils.js';
@@ -54,10 +52,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
return handlePost({ context, next, actionName });
}
- if (context.request.method === 'POST') {
- return handlePostLegacy({ context, next });
- }
-
return next();
});
@@ -98,14 +92,7 @@ async function handlePost({
actionName: string;
}) {
const { request } = context;
-
const baseAction = await getAction(actionName);
- if (!baseAction) {
- throw new AstroError({
- ...ActionQueryStringInvalidError,
- message: ActionQueryStringInvalidError.message(actionName),
- });
- }
const contentType = request.headers.get('content-type');
let formData: FormData | undefined;
@@ -153,38 +140,6 @@ async function redirectWithResult({
return context.redirect(context.url.pathname);
}
-async function handlePostLegacy({ context, next }: { context: APIContext; next: MiddlewareNext }) {
- const { request } = context;
-
- // We should not run a middleware handler for fetch()
- // requests directly to the /_actions URL.
- // Otherwise, we may handle the result twice.
- if (context.url.pathname.startsWith('/_actions')) return next();
-
- const contentType = request.headers.get('content-type');
- let formData: FormData | undefined;
- if (contentType && hasContentType(contentType, formContentTypes)) {
- formData = await request.clone().formData();
- }
-
- if (!formData) return next();
-
- const actionName = formData.get(ACTION_QUERY_PARAMS.actionName) as string;
- if (!actionName) return next();
-
- const baseAction = await getAction(actionName);
- if (!baseAction) {
- throw new AstroError({
- ...ActionQueryStringInvalidError,
- message: ActionQueryStringInvalidError.message(actionName),
- });
- }
-
- const action = baseAction.bind(context);
- const actionResult = await action(formData);
- return redirectWithResult({ context, actionName, actionResult });
-}
-
function isActionPayload(json: unknown): json is ActionPayload {
if (typeof json !== 'object' || json == null) return false;
diff --git a/packages/astro/src/actions/runtime/route.ts b/packages/astro/src/actions/runtime/route.ts
index e4e2ad1ce5..16b53f945b 100644
--- a/packages/astro/src/actions/runtime/route.ts
+++ b/packages/astro/src/actions/runtime/route.ts
@@ -5,9 +5,14 @@ import { serializeActionResult } from './virtual/shared.js';
export const POST: APIRoute = async (context) => {
const { request, url } = context;
- const baseAction = await getAction(url.pathname);
- if (!baseAction) {
- return new Response(null, { status: 404 });
+ let baseAction;
+ try {
+ baseAction = await getAction(url.pathname);
+ } catch (e) {
+ if (import.meta.env.DEV) throw e;
+ // eslint-disable-next-line no-console
+ console.error(e);
+ return new Response(e instanceof Error ? e.message : null, { status: 404 });
}
const contentType = request.headers.get('Content-Type');
const contentLength = request.headers.get('Content-Length');
diff --git a/packages/astro/src/actions/runtime/virtual/client.ts b/packages/astro/src/actions/runtime/virtual/client.ts
index 424552a9fe..c80e6778ae 100644
--- a/packages/astro/src/actions/runtime/virtual/client.ts
+++ b/packages/astro/src/actions/runtime/virtual/client.ts
@@ -3,12 +3,3 @@ export * from './shared.js';
export function defineAction() {
throw new Error('[astro:action] `defineAction()` unexpectedly used on the client.');
}
-
-export const z = new Proxy(
- {},
- {
- get() {
- throw new Error('[astro:action] `z` unexpectedly used on the client.');
- },
- },
-);
diff --git a/packages/astro/src/actions/runtime/virtual/get-action.ts b/packages/astro/src/actions/runtime/virtual/get-action.ts
index cb9addd9dd..a0906732b1 100644
--- a/packages/astro/src/actions/runtime/virtual/get-action.ts
+++ b/packages/astro/src/actions/runtime/virtual/get-action.ts
@@ -1,5 +1,7 @@
import type { ZodType } from 'zod';
import type { ActionAccept, ActionClient } from './server.js';
+import { ActionNotFoundError } from '../../../core/errors/errors-data.js';
+import { AstroError } from '../../../core/errors/errors.js';
/**
* Get server-side action based on the route path.
@@ -8,19 +10,30 @@ import type { ActionAccept, ActionClient } from './server.js';
*/
export async function getAction(
path: string,
-): Promise | undefined> {
+): Promise> {
const pathKeys = path.replace('/_actions/', '').split('.');
// @ts-expect-error virtual module
let { server: actionLookup } = await import('astro:internal-actions');
+ if (actionLookup == null || !(typeof actionLookup === 'object')) {
+ throw new TypeError(
+ `Expected \`server\` export in actions file to be an object. Received ${typeof actionLookup}.`,
+ );
+ }
+
for (const key of pathKeys) {
if (!(key in actionLookup)) {
- return undefined;
+ throw new AstroError({
+ ...ActionNotFoundError,
+ message: ActionNotFoundError.message(pathKeys.join('.')),
+ });
}
actionLookup = actionLookup[key];
}
if (typeof actionLookup !== 'function') {
- return undefined;
+ throw new TypeError(
+ `Expected handler for action ${pathKeys.join('.')} to be a function. Received ${typeof actionLookup}.`,
+ );
}
return actionLookup;
}
diff --git a/packages/astro/src/actions/runtime/virtual/server.ts b/packages/astro/src/actions/runtime/virtual/server.ts
index fcb0dc6030..cd1b4269ed 100644
--- a/packages/astro/src/actions/runtime/virtual/server.ts
+++ b/packages/astro/src/actions/runtime/virtual/server.ts
@@ -6,8 +6,6 @@ import { ActionError, ActionInputError, type SafeResult, callSafely } from './sh
export * from './shared.js';
-export { z } from 'zod';
-
export type ActionAccept = 'form' | 'json';
export type ActionHandler = TInputSchema extends z.ZodType
diff --git a/packages/astro/src/actions/runtime/virtual/shared.ts b/packages/astro/src/actions/runtime/virtual/shared.ts
index 01f9bd4e68..8367710b94 100644
--- a/packages/astro/src/actions/runtime/virtual/shared.ts
+++ b/packages/astro/src/actions/runtime/virtual/shared.ts
@@ -181,26 +181,6 @@ export function getActionQueryString(name: string) {
return `?${searchParams.toString()}`;
}
-/**
- * @deprecated You can now pass action functions
- * directly to the `action` attribute on a form.
- *
- * Example: ``
- */
-export function getActionProps MaybePromise>(action: T) {
- const params = new URLSearchParams(action.toString());
- const actionName = params.get('_astroAction');
- if (!actionName) {
- // No need for AstroError. `getActionProps()` will be removed for stable.
- throw new Error('Invalid actions function was passed to getActionProps()');
- }
- return {
- type: 'hidden',
- name: '_astroAction',
- value: actionName,
- } as const;
-}
-
export type SerializedActionResult =
| {
type: 'data';
@@ -269,10 +249,22 @@ export function serializeActionResult(res: SafeResult): SerializedActi
export function deserializeActionResult(res: SerializedActionResult): SafeResult {
if (res.type === 'error') {
+ let json;
+ try {
+ json = JSON.parse(res.body);
+ } catch {
+ return {
+ data: undefined,
+ error: new ActionError({
+ message: res.body,
+ code: 'INTERNAL_SERVER_ERROR',
+ }),
+ };
+ }
if (import.meta.env?.PROD) {
- return { error: ActionError.fromJson(JSON.parse(res.body)), data: undefined };
+ return { error: ActionError.fromJson(json), data: undefined };
} else {
- const error = ActionError.fromJson(JSON.parse(res.body));
+ const error = ActionError.fromJson(json);
error.stack = actionResultErrorStack.get();
return {
error,
diff --git a/packages/astro/src/actions/utils.ts b/packages/astro/src/actions/utils.ts
index eddac615b7..e9673d618c 100644
--- a/packages/astro/src/actions/utils.ts
+++ b/packages/astro/src/actions/utils.ts
@@ -1,3 +1,5 @@
+import * as eslexer from 'es-module-lexer';
+import type fsMod from 'node:fs';
import type { APIContext } from '../@types/astro.js';
import type { Locals } from './runtime/middleware.js';
import type { ActionAPIContext } from './runtime/utils.js';
@@ -25,3 +27,53 @@ export function createCallAction(context: ActionAPIContext): APIContext['callAct
return action(input) as any;
};
}
+
+let didInitLexer = false;
+
+/**
+ * Check whether the Actions config file is present.
+ */
+export async function isActionsFilePresent(fs: typeof fsMod, srcDir: URL) {
+ if (!didInitLexer) await eslexer.init;
+
+ const actionsFile = search(fs, srcDir);
+ if (!actionsFile) return false;
+
+ let contents: string;
+ try {
+ contents = fs.readFileSync(actionsFile, 'utf-8');
+ } catch {
+ return false;
+ }
+
+ // Check if `server` export is present.
+ // If not, the user may have an empty `actions` file,
+ // or may be using the `actions` file for another purpose
+ // (possible since actions are non-breaking for v4.X).
+ const [, exports] = eslexer.parse(contents, actionsFile.pathname);
+ for (const exp of exports) {
+ if (exp.n === 'server') {
+ return true;
+ }
+ }
+ return false;
+}
+
+function search(fs: typeof fsMod, srcDir: URL) {
+ const paths = [
+ 'actions.mjs',
+ 'actions.js',
+ 'actions.mts',
+ 'actions.ts',
+ 'actions/index.mjs',
+ 'actions/index.js',
+ 'actions/index.mts',
+ 'actions/index.ts',
+ ].map((p) => new URL(p, srcDir));
+ for (const file of paths) {
+ if (fs.existsSync(file)) {
+ return file;
+ }
+ }
+ return undefined;
+}
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 8634e0c0f0..ad10f725ac 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -83,7 +83,6 @@ export const ASTRO_CONFIG_DEFAULTS = {
redirects: {},
security: {},
experimental: {
- actions: false,
directRenderScript: false,
contentCollectionCache: false,
clientPrerender: false,
@@ -510,7 +509,6 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.security),
experimental: z
.object({
- actions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.actions),
directRenderScript: z
.boolean()
.optional()
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 7fbfe60ddd..28ce9810aa 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -39,6 +39,7 @@ import { vitePluginMiddleware } from './middleware/vite-plugin.js';
import { joinPaths } from './path.js';
import { vitePluginServerIslands } from './server-islands/vite-plugin-server-islands.js';
import { isObject } from './util.js';
+import { vitePluginActions, vitePluginUserActions } from '../actions/plugins.js';
interface CreateViteOptions {
settings: AstroSettings;
@@ -153,6 +154,8 @@ export async function createVite(
astroDevToolbar({ settings, logger }),
vitePluginFileURL(),
astroInternationalization({ settings }),
+ vitePluginActions({ fs, settings }),
+ vitePluginUserActions({ settings }),
settings.config.experimental.serverIslands && vitePluginServerIslands({ settings }),
astroContainer(),
],
@@ -191,6 +194,10 @@ export async function createVite(
find: 'astro:middleware',
replacement: 'astro/virtual-modules/middleware.js',
},
+ {
+ find: 'astro:schema',
+ replacement: 'astro/zod',
+ },
{
find: 'astro:components',
replacement: 'astro/components',
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index 1abd7ba1b1..a7e44c682d 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -1667,23 +1667,7 @@ export const ActionsWithoutServerOutputError = {
/**
* @docs
* @see
- * - [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',
- title: 'An invalid Action query string was passed by a form.',
- message: (actionName: string) =>
- `Action ${actionName} 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.`,
- hint: 'Actions are experimental. Visit the RFC for usage instructions: https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md',
-} satisfies ErrorData;
-
-/**
- * @docs
- * @see
- * - [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md)
+ * - [Actions handler reference](https://docs.astro.build/en/reference/api-reference/#handler-property)
* @description
* Action handler returned invalid data. Handlers should return serializable data types, and cannot return a Response object.
*/
@@ -1697,29 +1681,30 @@ export const ActionsReturnedInvalidDataError = {
/**
* @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.
+ * The server received a request for an action but could not find a match with the same name.
*/
-export const ActionQueryStringInvalidError = {
- name: 'ActionQueryStringInvalidError',
- title: 'An invalid Action query string was passed by a form.',
+export const ActionNotFoundError = {
+ name: 'ActionNotFoundError',
+ title: 'Action not found.',
message: (actionName: string) =>
- `The server received the query string \`?_astroAction=${actionName}\`, but could not find an action with that name. If you changed an action's name in development, remove this query param from your URL and refresh.`,
- hint: 'Actions are experimental. Visit the RFC for usage instructions: https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md',
+ `The server received a request for an action named \`${actionName}\` but could not find a match. If you renamed an action, check that you've updated your \`actions/index\` file and your calling code to match.`,
+ hint: 'You can run `astro check` to detect type errors caused by mismatched action names.',
} satisfies ErrorData;
/**
* @docs
+ * @see
+ * - [`Astro.callAction()` reference](https://docs.astro.build/en/reference/api-reference/#astrocallaction)
* @description
* Action called from a server page or endpoint without using `Astro.callAction()`.
*/
export const ActionCalledFromServerError = {
name: 'ActionCalledFromServerError',
title: 'Action unexpected called from the server.',
- message: 'Action called from a server page or endpoint without using `Astro.callAction()`.',
- hint: 'See the RFC section on server calls for usage instructions: https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md#call-actions-directly-from-server-code',
+ message:
+ 'Action called from a server page or endpoint without using `Astro.callAction()`. This wrapper must be used to call actions from server code.',
+ hint: 'See the `Astro.callAction()` reference for usage examples: https://docs.astro.build/en/reference/api-reference/#astrocallaction',
} satisfies ErrorData;
// Generic catch-all - Only use this in extreme cases, like if there was a cosmic ray bit flip.
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 3b15e0d97e..a8f1293835 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -22,6 +22,8 @@ import { mergeConfig } from '../core/config/index.js';
import type { AstroIntegrationLogger, Logger } from '../core/logger/core.js';
import { isServerLikeOutput } from '../core/util.js';
import { validateSupportedFeatures } from './features-validation.js';
+import { isActionsFilePresent } from '../actions/utils.js';
+import astroIntegrationActionsRouteHandler from '../actions/integration.js';
async function withTakingALongTimeMsg({
name,
@@ -130,9 +132,8 @@ export async function runHookConfigSetup({
if (settings.config.adapter) {
settings.config.integrations.push(settings.config.adapter);
}
- if (settings.config.experimental?.actions) {
- const { default: actionsIntegration } = await import('../actions/index.js');
- settings.config.integrations.push(actionsIntegration({ fs, settings }));
+ if (await isActionsFilePresent(fs, settings.config.srcDir)) {
+ settings.config.integrations.push(astroIntegrationActionsRouteHandler({ settings }));
}
let updatedConfig: AstroConfig = { ...settings.config };
diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js
index 3c803972ce..334e07a173 100644
--- a/packages/astro/test/actions.test.js
+++ b/packages/astro/test/actions.test.js
@@ -306,45 +306,6 @@ describe('Astro Actions', () => {
assert.equal(data?.age, '42');
});
- describe('legacy', () => {
- it('Response middleware fallback', async () => {
- const formData = new FormData();
- formData.append('_astroAction', 'getUser');
- const req = new Request('http://example.com/user', {
- method: 'POST',
- body: formData,
- headers: {
- Referer: 'http://example.com/user',
- },
- });
- const res = await followExpectedRedirect(req, app);
- assert.equal(res.ok, true);
-
- const html = await res.text();
- let $ = cheerio.load(html);
- assert.equal($('#user').text(), 'Houston');
- });
-
- it('Respects custom errors', async () => {
- const formData = new FormData();
- formData.append('_astroAction', 'getUserOrThrow');
- const req = new Request('http://example.com/user-or-throw', {
- method: 'POST',
- body: formData,
- headers: {
- Referer: 'http://example.com/user-or-throw',
- },
- });
- const res = await followExpectedRedirect(req, app);
- assert.equal(res.status, 401);
-
- const html = await res.text();
- let $ = cheerio.load(html);
- assert.equal($('#error-message').text(), 'Not logged in');
- assert.equal($('#error-code').text(), 'UNAUTHORIZED');
- });
- });
-
it('Sets status to 204 when content-length is 0', async () => {
const req = new Request('http://example.com/_actions/fireAndForget', {
method: 'POST',
diff --git a/packages/astro/test/fixtures/actions/astro.config.mjs b/packages/astro/test/fixtures/actions/astro.config.mjs
index fc6477578b..2f849f7f15 100644
--- a/packages/astro/test/fixtures/actions/astro.config.mjs
+++ b/packages/astro/test/fixtures/actions/astro.config.mjs
@@ -3,7 +3,4 @@ import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: 'server',
- experimental: {
- actions: true,
- },
});
diff --git a/packages/astro/test/fixtures/actions/src/actions/index.ts b/packages/astro/test/fixtures/actions/src/actions/index.ts
index 881656994f..ed76927993 100644
--- a/packages/astro/test/fixtures/actions/src/actions/index.ts
+++ b/packages/astro/test/fixtures/actions/src/actions/index.ts
@@ -1,4 +1,5 @@
-import { defineAction, ActionError, z } from 'astro:actions';
+import { defineAction, ActionError } from 'astro:actions';
+import { z } from 'astro:schema';
const passwordSchema = z
.string()
diff --git a/packages/astro/test/units/integrations/api.test.js b/packages/astro/test/units/integrations/api.test.js
index 6122ba6408..6f2438ae32 100644
--- a/packages/astro/test/units/integrations/api.test.js
+++ b/packages/astro/test/units/integrations/api.test.js
@@ -8,10 +8,16 @@ import {
} from '../../../dist/integrations/hooks.js';
import { defaultLogger } from '../test-utils.js';
+const defaultConfig = {
+ root: new URL('./', import.meta.url),
+ srcDir: new URL('src/', import.meta.url),
+};
+
describe('Integration API', () => {
it('runHookBuildSetup should work', async () => {
const updatedViteConfig = await runHookBuildSetup({
config: {
+ ...defaultConfig,
integrations: [
{
name: 'test',
@@ -39,6 +45,7 @@ describe('Integration API', () => {
let updatedInternalConfig;
const updatedViteConfig = await runHookBuildSetup({
config: {
+ ...defaultConfig,
integrations: [
{
name: 'test',
@@ -68,6 +75,7 @@ describe('Integration API', () => {
logger: defaultLogger,
settings: {
config: {
+ ...defaultConfig,
integrations: [
{
name: 'test',
@@ -90,6 +98,7 @@ describe('Integration API', () => {
logger: defaultLogger,
settings: {
config: {
+ ...defaultConfig,
integrations: [
{
name: 'test',
From 4215d6ef673d4c73635ce9cf0d64f23dbb046c90 Mon Sep 17 00:00:00 2001
From: Ben Holmes
Date: Thu, 29 Aug 2024 10:14:41 +0000
Subject: [PATCH 2/2] [ci] format
---
packages/astro/src/actions/runtime/virtual/get-action.ts | 2 +-
packages/astro/src/actions/utils.ts | 2 +-
packages/astro/src/core/create-vite.ts | 2 +-
packages/astro/src/integrations/hooks.ts | 4 ++--
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/astro/src/actions/runtime/virtual/get-action.ts b/packages/astro/src/actions/runtime/virtual/get-action.ts
index a0906732b1..b547b57c10 100644
--- a/packages/astro/src/actions/runtime/virtual/get-action.ts
+++ b/packages/astro/src/actions/runtime/virtual/get-action.ts
@@ -1,7 +1,7 @@
import type { ZodType } from 'zod';
-import type { ActionAccept, ActionClient } from './server.js';
import { ActionNotFoundError } from '../../../core/errors/errors-data.js';
import { AstroError } from '../../../core/errors/errors.js';
+import type { ActionAccept, ActionClient } from './server.js';
/**
* Get server-side action based on the route path.
diff --git a/packages/astro/src/actions/utils.ts b/packages/astro/src/actions/utils.ts
index e9673d618c..0e7c6fb621 100644
--- a/packages/astro/src/actions/utils.ts
+++ b/packages/astro/src/actions/utils.ts
@@ -1,5 +1,5 @@
-import * as eslexer from 'es-module-lexer';
import type fsMod from 'node:fs';
+import * as eslexer from 'es-module-lexer';
import type { APIContext } from '../@types/astro.js';
import type { Locals } from './runtime/middleware.js';
import type { ActionAPIContext } from './runtime/utils.js';
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 28ce9810aa..f3174b5d32 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -4,6 +4,7 @@ import glob from 'fast-glob';
import * as vite from 'vite';
import { crawlFrameworkPkgs } from 'vitefu';
import type { AstroSettings } from '../@types/astro.js';
+import { vitePluginActions, vitePluginUserActions } from '../actions/plugins.js';
import { getAssetsPrefix } from '../assets/utils/getAssetsPrefix.js';
import astroAssetsPlugin from '../assets/vite-plugin-assets.js';
import astroContainer from '../container/vite-plugin-container.js';
@@ -39,7 +40,6 @@ import { vitePluginMiddleware } from './middleware/vite-plugin.js';
import { joinPaths } from './path.js';
import { vitePluginServerIslands } from './server-islands/vite-plugin-server-islands.js';
import { isObject } from './util.js';
-import { vitePluginActions, vitePluginUserActions } from '../actions/plugins.js';
interface CreateViteOptions {
settings: AstroSettings;
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index a8f1293835..d7e37b5fc5 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -15,6 +15,8 @@ import type {
RouteData,
RouteOptions,
} from '../@types/astro.js';
+import astroIntegrationActionsRouteHandler from '../actions/integration.js';
+import { isActionsFilePresent } from '../actions/utils.js';
import type { SerializedSSRManifest } from '../core/app/types.js';
import type { PageBuildData } from '../core/build/types.js';
import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js';
@@ -22,8 +24,6 @@ import { mergeConfig } from '../core/config/index.js';
import type { AstroIntegrationLogger, Logger } from '../core/logger/core.js';
import { isServerLikeOutput } from '../core/util.js';
import { validateSupportedFeatures } from './features-validation.js';
-import { isActionsFilePresent } from '../actions/utils.js';
-import astroIntegrationActionsRouteHandler from '../actions/integration.js';
async function withTakingALongTimeMsg({
name,