astro 4.13.2 → 4.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1842,25 +1842,29 @@ export interface AstroUserConfig {
1842
1842
  * @version 4.10.0
1843
1843
  * @description
1844
1844
  *
1845
- * Enables experimental `astro:env` features .
1845
+ * Enables experimental `astro:env` features.
1846
1846
  *
1847
1847
  * The `astro:env` API lets you configure a type-safe schema for your environment variables, and indicate whether they should be available on the server or the client. Import and use your defined variables from the appropriate `/client` or `/server` module:
1848
1848
  *
1849
1849
  * ```astro
1850
1850
  * ---
1851
- * import { APP_ID } from "astro:env/client"
1852
- * import { API_URL, API_TOKEN, getSecret } from "astro:env/server"
1853
- * const NODE_ENV = getSecret("NODE_ENV")
1851
+ * import { API_URL } from "astro:env/client"
1852
+ * import { API_SECRET_TOKEN } from "astro:env/server"
1854
1853
  *
1855
1854
  * const data = await fetch(`${API_URL}/users`, {
1856
- * method: "POST",
1855
+ * method: "GET",
1857
1856
  * headers: {
1858
1857
  * "Content-Type": "application/json",
1859
- * "Authorization": `Bearer ${API_TOKEN}`
1858
+ * "Authorization": `Bearer ${API_SECRET_TOKEN}`
1860
1859
  * },
1861
- * body: JSON.stringify({ appId: APP_ID, nodeEnv: NODE_ENV })
1862
1860
  * })
1863
1861
  * ---
1862
+ *
1863
+ * <script>
1864
+ * import { API_URL } from "astro:env/client"
1865
+ *
1866
+ * fetch(`${API_URL}/ping`)
1867
+ * </script>
1864
1868
  * ```
1865
1869
  *
1866
1870
  * To define the data type and properties of your environment variables, declare a schema in your Astro config in `experimental.env.schema`. The `envField` helper allows you define your variable as a string, number, or boolean and pass properties in an object:
@@ -1898,7 +1902,7 @@ export interface AstroUserConfig {
1898
1902
  * import { PORT } from "astro:env/server"
1899
1903
  * ```
1900
1904
  *
1901
- * - **Secret server variables**: These variables are not part of your final bundle and can be accessed on the server through the `astro:env/server` module. The `getSecret()` helper function can be used to retrieve secrets not specified in the schema:
1905
+ * - **Secret server variables**: These variables are not part of your final bundle and can be accessed on the server through the `astro:env/server` module. The `getSecret()` helper function can be used to retrieve secrets not specified in the schema. Its implementation is provided by your adapter and defaults to `process.env`:
1902
1906
  *
1903
1907
  * ```js
1904
1908
  * import { API_SECRET, getSecret } from "astro:env/server"
@@ -4,3 +4,8 @@ export declare const ACTIONS_TYPES_FILE = "actions.d.ts";
4
4
  export declare const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
5
5
  export declare const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
6
6
  export declare const NOOP_ACTIONS = "\0noop-actions";
7
+ export declare const ACTION_QUERY_PARAMS: {
8
+ actionName: string;
9
+ actionPayload: string;
10
+ actionRedirect: string;
11
+ };
@@ -4,8 +4,14 @@ const ACTIONS_TYPES_FILE = "actions.d.ts";
4
4
  const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
5
5
  const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
6
6
  const NOOP_ACTIONS = "\0noop-actions";
7
+ const ACTION_QUERY_PARAMS = {
8
+ actionName: "_astroAction",
9
+ actionPayload: "_astroActionPayload",
10
+ actionRedirect: "_astroActionRedirect"
11
+ };
7
12
  export {
8
13
  ACTIONS_TYPES_FILE,
14
+ ACTION_QUERY_PARAMS,
9
15
  NOOP_ACTIONS,
10
16
  RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
11
17
  RESOLVED_VIRTUAL_MODULE_ID,
@@ -1,8 +1,9 @@
1
1
  import { type SerializedActionResult } from './virtual/shared.js';
2
+ export type ActionPayload = {
3
+ actionResult: SerializedActionResult;
4
+ actionName: string;
5
+ };
2
6
  export type Locals = {
3
- _actionsInternal: {
4
- actionResult: SerializedActionResult;
5
- actionName: string;
6
- };
7
+ _actionPayload: ActionPayload;
7
8
  };
8
9
  export declare const onRequest: import("../../@types/astro.js").MiddlewareHandler;
@@ -2,6 +2,7 @@ import { yellow } from "kleur/colors";
2
2
  import { ActionQueryStringInvalidError } from "../../core/errors/errors-data.js";
3
3
  import { AstroError } from "../../core/errors/errors.js";
4
4
  import { defineMiddleware } from "../../core/middleware/index.js";
5
+ import { ACTION_QUERY_PARAMS } from "../consts.js";
5
6
  import { formContentTypes, hasContentType } from "./utils.js";
6
7
  import { getAction } from "./virtual/get-action.js";
7
8
  import {
@@ -10,7 +11,14 @@ import {
10
11
  const onRequest = defineMiddleware(async (context, next) => {
11
12
  const locals = context.locals;
12
13
  const { request } = context;
13
- if (locals._actionsInternal) return next();
14
+ if (locals._actionPayload) return next();
15
+ const actionPayload = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.json();
16
+ if (actionPayload) {
17
+ if (!isActionPayload(actionPayload)) {
18
+ throw new Error("Internal: Invalid action payload in cookie.");
19
+ }
20
+ return renderResult({ context, next, ...actionPayload });
21
+ }
14
22
  if (import.meta.env.DEV && request.method === "POST" && request.body === null) {
15
23
  console.warn(
16
24
  yellow("[astro:actions]"),
@@ -18,7 +26,7 @@ const onRequest = defineMiddleware(async (context, next) => {
18
26
  );
19
27
  return next();
20
28
  }
21
- const actionName = context.url.searchParams.get("_astroAction");
29
+ const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
22
30
  if (context.request.method === "POST" && actionName) {
23
31
  return handlePost({ context, next, actionName });
24
32
  }
@@ -27,6 +35,25 @@ const onRequest = defineMiddleware(async (context, next) => {
27
35
  }
28
36
  return next();
29
37
  });
38
+ async function renderResult({
39
+ context,
40
+ next,
41
+ actionResult,
42
+ actionName
43
+ }) {
44
+ const locals = context.locals;
45
+ locals._actionPayload = { actionResult, actionName };
46
+ const response = await next();
47
+ context.cookies.delete(ACTION_QUERY_PARAMS.actionPayload);
48
+ if (actionResult.type === "error") {
49
+ return new Response(response.body, {
50
+ status: actionResult.status,
51
+ statusText: actionResult.type,
52
+ headers: response.headers
53
+ });
54
+ }
55
+ return response;
56
+ }
30
57
  async function handlePost({
31
58
  context,
32
59
  next,
@@ -47,28 +74,33 @@ async function handlePost({
47
74
  }
48
75
  const action = baseAction.bind(context);
49
76
  const actionResult = await action(formData);
50
- return handleResult({ context, next, actionName, actionResult });
77
+ if (context.url.searchParams.get(ACTION_QUERY_PARAMS.actionRedirect) === "false") {
78
+ return renderResult({
79
+ context,
80
+ next,
81
+ actionName,
82
+ actionResult: serializeActionResult(actionResult)
83
+ });
84
+ }
85
+ return redirectWithResult({ context, actionName, actionResult });
51
86
  }
52
- async function handleResult({
87
+ async function redirectWithResult({
53
88
  context,
54
- next,
55
89
  actionName,
56
90
  actionResult
57
91
  }) {
58
- const locals = context.locals;
59
- locals._actionsInternal = {
92
+ context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, {
60
93
  actionName,
61
94
  actionResult: serializeActionResult(actionResult)
62
- };
63
- const response = await next();
95
+ });
64
96
  if (actionResult.error) {
65
- return new Response(response.body, {
66
- status: actionResult.error.status,
67
- statusText: actionResult.error.type,
68
- headers: response.headers
69
- });
97
+ const referer = context.request.headers.get("Referer");
98
+ if (!referer) {
99
+ throw new Error("Internal: Referer unexpectedly missing from Action POST request.");
100
+ }
101
+ return context.redirect(referer);
70
102
  }
71
- return response;
103
+ return context.redirect(context.url.pathname);
72
104
  }
73
105
  async function handlePostLegacy({ context, next }) {
74
106
  const { request } = context;
@@ -79,7 +111,7 @@ async function handlePostLegacy({ context, next }) {
79
111
  formData = await request.clone().formData();
80
112
  }
81
113
  if (!formData) return next();
82
- const actionName = formData.get("_astroAction");
114
+ const actionName = formData.get(ACTION_QUERY_PARAMS.actionName);
83
115
  if (!actionName) return next();
84
116
  const baseAction = await getAction(actionName);
85
117
  if (!baseAction) {
@@ -90,7 +122,13 @@ async function handlePostLegacy({ context, next }) {
90
122
  }
91
123
  const action = baseAction.bind(context);
92
124
  const actionResult = await action(formData);
93
- return handleResult({ context, next, actionName, actionResult });
125
+ return redirectWithResult({ context, actionName, actionResult });
126
+ }
127
+ function isActionPayload(json) {
128
+ if (typeof json !== "object" || json == null) return false;
129
+ if (!("actionResult" in json) || typeof json.actionResult !== "object") return false;
130
+ if (!("actionName" in json) || typeof json.actionName !== "string") return false;
131
+ return true;
94
132
  }
95
133
  export {
96
134
  onRequest
@@ -10,7 +10,7 @@ export type ActionReturnType<T extends ActionHandler<any, any>> = Awaited<Return
10
10
  export type ActionClient<TOutput, TAccept extends ActionAccept | undefined, TInputSchema extends ActionInputSchema<TAccept> | undefined> = TInputSchema extends z.ZodType ? ((input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<SafeResult<z.input<TInputSchema> extends ErrorInferenceObject ? z.input<TInputSchema> : ErrorInferenceObject, Awaited<TOutput>>>) & {
11
11
  queryString: string;
12
12
  orThrow: (input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<Awaited<TOutput>>;
13
- } : (input?: any) => Promise<SafeResult<never, Awaited<TOutput>>> & {
13
+ } : ((input?: any) => Promise<SafeResult<never, Awaited<TOutput>>>) & {
14
14
  orThrow: (input?: any) => Promise<Awaited<TOutput>>;
15
15
  };
16
16
  export declare function defineAction<TOutput, TAccept extends ActionAccept | undefined = undefined, TInputSchema extends ActionInputSchema<ActionAccept> | undefined = TAccept extends 'form' ? z.ZodType<FormData> : undefined>({ accept, input: inputSchema, handler, }: {
@@ -1,5 +1,10 @@
1
1
  import type { z } from 'zod';
2
2
  import type { ErrorInferenceObject, MaybePromise } from '../utils.js';
3
+ export declare const ACTION_QUERY_PARAMS: {
4
+ actionName: string;
5
+ actionPayload: string;
6
+ actionRedirect: string;
7
+ };
3
8
  export declare const ACTION_ERROR_CODES: readonly ["BAD_REQUEST", "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "TIMEOUT", "CONFLICT", "PRECONDITION_FAILED", "PAYLOAD_TOO_LARGE", "UNSUPPORTED_MEDIA_TYPE", "UNPROCESSABLE_CONTENT", "TOO_MANY_REQUESTS", "CLIENT_CLOSED_REQUEST", "INTERNAL_SERVER_ERROR"];
4
9
  export type ActionErrorCode = (typeof ACTION_ERROR_CODES)[number];
5
10
  export declare class ActionError<T extends ErrorInferenceObject = ErrorInferenceObject> extends Error {
@@ -1,4 +1,6 @@
1
1
  import { parse as devalueParse, stringify as devalueStringify } from "devalue";
2
+ import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from "../../consts.js";
3
+ const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS;
2
4
  const ACTION_ERROR_CODES = [
3
5
  "BAD_REQUEST",
4
6
  "UNAUTHORIZED",
@@ -113,7 +115,7 @@ async function callSafely(handler) {
113
115
  }
114
116
  }
115
117
  function getActionQueryString(name) {
116
- const searchParams = new URLSearchParams({ _astroAction: name });
118
+ const searchParams = new URLSearchParams({ [_ACTION_QUERY_PARAMS.actionName]: name });
117
119
  return `?${searchParams.toString()}`;
118
120
  }
119
121
  function getActionProps(action) {
@@ -173,6 +175,7 @@ function deserializeActionResult(res) {
173
175
  }
174
176
  export {
175
177
  ACTION_ERROR_CODES,
178
+ ACTION_QUERY_PARAMS,
176
179
  ActionError,
177
180
  ActionInputError,
178
181
  callSafely,
@@ -1,6 +1,6 @@
1
1
  import type { APIContext } from '../@types/astro.js';
2
2
  import type { Locals } from './runtime/middleware.js';
3
3
  import { type ActionAPIContext } from './runtime/utils.js';
4
- export declare function hasActionsInternal(locals: APIContext['locals']): locals is Locals;
4
+ export declare function hasActionPayload(locals: APIContext['locals']): locals is Locals;
5
5
  export declare function createGetActionResult(locals: APIContext['locals']): APIContext['getActionResult'];
6
6
  export declare function createCallAction(context: ActionAPIContext): APIContext['callAction'];
@@ -1,14 +1,14 @@
1
1
  import {} from "./runtime/utils.js";
2
2
  import { deserializeActionResult, getActionQueryString } from "./runtime/virtual/shared.js";
3
- function hasActionsInternal(locals) {
4
- return "_actionsInternal" in locals;
3
+ function hasActionPayload(locals) {
4
+ return "_actionPayload" in locals;
5
5
  }
6
6
  function createGetActionResult(locals) {
7
7
  return (actionFn) => {
8
- if (!hasActionsInternal(locals) || actionFn.toString() !== getActionQueryString(locals._actionsInternal.actionName)) {
8
+ if (!hasActionPayload(locals) || actionFn.toString() !== getActionQueryString(locals._actionPayload.actionName)) {
9
9
  return void 0;
10
10
  }
11
- return deserializeActionResult(locals._actionsInternal.actionResult);
11
+ return deserializeActionResult(locals._actionPayload.actionResult);
12
12
  };
13
13
  }
14
14
  function createCallAction(context) {
@@ -20,5 +20,5 @@ function createCallAction(context) {
20
20
  export {
21
21
  createCallAction,
22
22
  createGetActionResult,
23
- hasActionsInternal
23
+ hasActionPayload
24
24
  };
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.13.2";
1
+ const ASTRO_VERSION = "4.13.3";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
4
4
  const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
@@ -19,7 +19,7 @@ async function dev(inlineConfig) {
19
19
  await telemetry.record([]);
20
20
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
21
21
  const logger = restart.container.logger;
22
- const currentVersion = "4.13.2";
22
+ const currentVersion = "4.13.3";
23
23
  const isPrerelease = currentVersion.includes("-");
24
24
  if (!isPrerelease) {
25
25
  try {
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "4.13.2";
41
+ const version = "4.13.3";
42
42
  const localPrefix = `${dim("\u2503")} Local `;
43
43
  const networkPrefix = `${dim("\u2503")} Network `;
44
44
  const emptyPrefix = " ".repeat(11);
@@ -270,7 +270,7 @@ function printHelp({
270
270
  message.push(
271
271
  linebreak(),
272
272
  ` ${bgGreen(black(` ${commandName} `))} ${green(
273
- `v${"4.13.2"}`
273
+ `v${"4.13.3"}`
274
274
  )} ${headline}`
275
275
  );
276
276
  }
@@ -1,5 +1,5 @@
1
1
  import { deserializeActionResult } from "../actions/runtime/virtual/shared.js";
2
- import { createCallAction, createGetActionResult, hasActionsInternal } from "../actions/utils.js";
2
+ import { createCallAction, createGetActionResult, hasActionPayload } from "../actions/utils.js";
3
3
  import {
4
4
  computeCurrentLocale,
5
5
  computePreferredLocale,
@@ -256,7 +256,7 @@ class RenderContext {
256
256
  throw new AstroError(AstroErrorData.AstroResponseHeadersReassigned);
257
257
  }
258
258
  };
259
- const actionResult = hasActionsInternal(this.locals) ? deserializeActionResult(this.locals._actionsInternal.actionResult) : void 0;
259
+ const actionResult = hasActionPayload(this.locals) ? deserializeActionResult(this.locals._actionPayload.actionResult) : void 0;
260
260
  const result = {
261
261
  base: manifest.base,
262
262
  cancelled: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.13.2",
3
+ "version": "4.13.3",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -166,8 +166,8 @@
166
166
  "zod": "^3.23.8",
167
167
  "zod-to-json-schema": "^3.23.2",
168
168
  "@astrojs/internal-helpers": "0.4.1",
169
- "@astrojs/telemetry": "3.1.0",
170
- "@astrojs/markdown-remark": "5.2.0"
169
+ "@astrojs/markdown-remark": "5.2.0",
170
+ "@astrojs/telemetry": "3.1.0"
171
171
  },
172
172
  "optionalDependencies": {
173
173
  "sharp": "^0.33.3"
@@ -1,4 +1,9 @@
1
- import { ActionError, deserializeActionResult, getActionQueryString } from 'astro:actions';
1
+ import {
2
+ ACTION_QUERY_PARAMS,
3
+ ActionError,
4
+ deserializeActionResult,
5
+ getActionQueryString,
6
+ } from 'astro:actions';
2
7
 
3
8
  function toActionProxy(actionCallback = {}, aggregatedPath = '') {
4
9
  return new Proxy(actionCallback, {
@@ -16,13 +21,18 @@ function toActionProxy(actionCallback = {}, aggregatedPath = '') {
16
21
  toString: () => action.queryString,
17
22
  // Progressive enhancement info for React.
18
23
  $$FORM_ACTION: function () {
24
+ const searchParams = new URLSearchParams(action.toString());
25
+ // Astro will redirect with a GET request by default.
26
+ // Disable this behavior to preserve form state
27
+ // for React's progressive enhancement.
28
+ searchParams.set(ACTION_QUERY_PARAMS.actionRedirect, 'false');
19
29
  return {
20
30
  method: 'POST',
21
31
  // `name` creates a hidden input.
22
32
  // It's unused by Astro, but we can't turn this off.
23
33
  // At least use a name that won't conflict with a user's formData.
24
34
  name: '_astroAction',
25
- action: action.toString(),
35
+ action: '?' + searchParams.toString(),
26
36
  };
27
37
  },
28
38
  // Note: `orThrow` does not have progressive enhancement info.