astro 4.14.3 → 4.14.5

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.
@@ -4,7 +4,8 @@ import type { MarkdownHeading, MarkdownVFile, RehypePlugins, RemarkPlugins, Rema
4
4
  import type * as babel from '@babel/core';
5
5
  import type * as rollup from 'rollup';
6
6
  import type * as vite from 'vite';
7
- import type { ActionAccept, ActionClient, ActionInputSchema, ActionReturnType } from '../actions/runtime/virtual/server.js';
7
+ import type { z } from 'zod';
8
+ import type { ActionAccept, ActionClient, ActionReturnType } from '../actions/runtime/virtual/server.js';
8
9
  import type { RemotePattern } from '../assets/utils/remotePattern.js';
9
10
  import type { DataEntry, RenderedContent } from '../content/data-store.js';
10
11
  import type { AssetsPrefix, SSRManifest, SerializedSSRManifest } from '../core/app/types.js';
@@ -2128,7 +2129,7 @@ export interface AstroUserConfig {
2128
2129
  * ```
2129
2130
  *
2130
2131
  * :::note
2131
- * 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.
2132
+ * 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.
2132
2133
  * :::
2133
2134
  *
2134
2135
  * #### Querying and rendering with the Content Layer API
@@ -2757,11 +2758,11 @@ interface AstroSharedContext<Props extends Record<string, any> = Record<string,
2757
2758
  /**
2758
2759
  * Get action result on the server when using a form POST.
2759
2760
  */
2760
- getActionResult: <TAccept extends ActionAccept, TInputSchema extends ActionInputSchema<TAccept>, TAction extends ActionClient<unknown, TAccept, TInputSchema>>(action: TAction) => ActionReturnType<TAction> | undefined;
2761
+ getActionResult: <TAccept extends ActionAccept, TInputSchema extends z.ZodType, TAction extends ActionClient<unknown, TAccept, TInputSchema>>(action: TAction) => ActionReturnType<TAction> | undefined;
2761
2762
  /**
2762
2763
  * Call action handler from the server.
2763
2764
  */
2764
- callAction: <TAccept extends ActionAccept, TInputSchema extends ActionInputSchema<TAccept>, TOutput, TAction extends ActionClient<TOutput, TAccept, TInputSchema> | ActionClient<TOutput, TAccept, TInputSchema>['orThrow']>(action: TAction, input: Parameters<TAction>[0]) => Promise<ActionReturnType<TAction>>;
2765
+ callAction: <TAccept extends ActionAccept, TInputSchema extends z.ZodType, TOutput, TAction extends ActionClient<TOutput, TAccept, TInputSchema> | ActionClient<TOutput, TAccept, TInputSchema>['orThrow']>(action: TAction, input: Parameters<TAction>[0]) => Promise<ActionReturnType<TAction>>;
2765
2766
  /**
2766
2767
  * Route parameters for this request if this is a dynamic route.
2767
2768
  */
@@ -9,8 +9,16 @@ import {
9
9
  serializeActionResult
10
10
  } from "./virtual/shared.js";
11
11
  const onRequest = defineMiddleware(async (context, next) => {
12
+ if (context._isPrerendered) {
13
+ if (context.request.method === "POST") {
14
+ console.warn(
15
+ yellow("[astro:actions]"),
16
+ 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".'
17
+ );
18
+ }
19
+ return next();
20
+ }
12
21
  const locals = context.locals;
13
- const { request } = context;
14
22
  if (locals._actionPayload) return next();
15
23
  const actionPayload = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.json();
16
24
  if (actionPayload) {
@@ -19,13 +27,6 @@ const onRequest = defineMiddleware(async (context, next) => {
19
27
  }
20
28
  return renderResult({ context, next, ...actionPayload });
21
29
  }
22
- if (import.meta.env.DEV && request.method === "POST" && request.body === null) {
23
- console.warn(
24
- yellow("[astro:actions]"),
25
- 'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".'
26
- );
27
- return next();
28
- }
29
30
  const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
30
31
  if (context.request.method === "POST" && actionName) {
31
32
  return handlePost({ context, next, actionName });
@@ -4,16 +4,15 @@ import { type SafeResult } from './shared.js';
4
4
  export * from './shared.js';
5
5
  export { z } from 'zod';
6
6
  export type ActionAccept = 'form' | 'json';
7
- export type ActionInputSchema<T extends ActionAccept | undefined> = T extends 'form' ? z.AnyZodObject | z.ZodType<FormData> : z.ZodType;
8
7
  export type ActionHandler<TInputSchema, TOutput> = TInputSchema extends z.ZodType ? (input: z.infer<TInputSchema>, context: ActionAPIContext) => MaybePromise<TOutput> : (input: any, context: ActionAPIContext) => MaybePromise<TOutput>;
9
8
  export type ActionReturnType<T extends ActionHandler<any, any>> = Awaited<ReturnType<T>>;
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>>>) & {
9
+ export type ActionClient<TOutput, TAccept extends ActionAccept | undefined, TInputSchema extends z.ZodType | 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
10
  queryString: string;
12
11
  orThrow: (input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<Awaited<TOutput>>;
13
12
  } : ((input?: any) => Promise<SafeResult<never, Awaited<TOutput>>>) & {
14
13
  orThrow: (input?: any) => Promise<Awaited<TOutput>>;
15
14
  };
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, }: {
15
+ export declare function defineAction<TOutput, TAccept extends ActionAccept | undefined = undefined, TInputSchema extends z.ZodType | undefined = TAccept extends 'form' ? z.ZodType<FormData> : undefined>({ accept, input: inputSchema, handler, }: {
17
16
  input?: TInputSchema;
18
17
  accept?: TAccept;
19
18
  handler: ActionHandler<TInputSchema, TOutput>;
@@ -34,8 +34,11 @@ function getFormServerHandler(handler, inputSchema) {
34
34
  message: "This action only accepts FormData."
35
35
  });
36
36
  }
37
- if (!(inputSchema instanceof z.ZodObject)) return await handler(unparsedInput, context);
38
- const parsed = await inputSchema.safeParseAsync(formDataToObject(unparsedInput, inputSchema));
37
+ if (!inputSchema) return await handler(unparsedInput, context);
38
+ const baseSchema = unwrapSchemaEffects(inputSchema);
39
+ const parsed = await inputSchema.safeParseAsync(
40
+ baseSchema instanceof z.ZodObject ? formDataToObject(unparsedInput, baseSchema) : unparsedInput
41
+ );
39
42
  if (!parsed.success) {
40
43
  throw new ActionInputError(parsed.error.issues);
41
44
  }
@@ -59,7 +62,7 @@ function getJsonServerHandler(handler, inputSchema) {
59
62
  };
60
63
  }
61
64
  function formDataToObject(formData, schema) {
62
- const obj = {};
65
+ const obj = schema._def.unknownKeys === "passthrough" ? Object.fromEntries(formData.entries()) : {};
63
66
  for (const [key, baseValidator] of Object.entries(schema.shape)) {
64
67
  let validator = baseValidator;
65
68
  while (validator instanceof z.ZodOptional || validator instanceof z.ZodNullable || validator instanceof z.ZodDefault) {
@@ -98,6 +101,17 @@ function handleFormDataGet(key, formData, validator, baseValidator) {
98
101
  }
99
102
  return validator instanceof z.ZodNumber ? Number(value) : value;
100
103
  }
104
+ function unwrapSchemaEffects(schema) {
105
+ while (schema instanceof z.ZodEffects || schema instanceof z.ZodPipeline) {
106
+ if (schema instanceof z.ZodEffects) {
107
+ schema = schema._def.schema;
108
+ }
109
+ if (schema instanceof z.ZodPipeline) {
110
+ schema = schema._def.in;
111
+ }
112
+ }
113
+ return schema;
114
+ }
101
115
  export {
102
116
  defineAction,
103
117
  formDataToObject,
@@ -1,5 +1,6 @@
1
1
  import type { z } from 'zod';
2
- import type { ErrorInferenceObject, MaybePromise } from '../utils.js';
2
+ import type { ErrorInferenceObject, MaybePromise, ActionAPIContext as _ActionAPIContext } from '../utils.js';
3
+ export type ActionAPIContext = _ActionAPIContext;
3
4
  export declare const ACTION_QUERY_PARAMS: {
4
5
  actionName: string;
5
6
  actionPayload: string;
@@ -15,6 +15,7 @@ function file(fileName) {
15
15
  logger.debug(error.message);
16
16
  return;
17
17
  }
18
+ const normalizedFilePath = posixRelative(fileURLToPath(settings.config.root), filePath);
18
19
  if (Array.isArray(json)) {
19
20
  if (json.length === 0) {
20
21
  logger.warn(`No items found in ${fileName}`);
@@ -28,11 +29,7 @@ function file(fileName) {
28
29
  continue;
29
30
  }
30
31
  const data = await parseData({ id, data: rawItem, filePath });
31
- store.set({
32
- id,
33
- data,
34
- filePath: posixRelative(fileURLToPath(settings.config.root), filePath)
35
- });
32
+ store.set({ id, data, filePath: normalizedFilePath });
36
33
  }
37
34
  } else if (typeof json === "object") {
38
35
  const entries = Object.entries(json);
@@ -40,7 +37,7 @@ function file(fileName) {
40
37
  store.clear();
41
38
  for (const [id, rawItem] of entries) {
42
39
  const data = await parseData({ id, data: rawItem, filePath });
43
- store.set({ id, data });
40
+ store.set({ id, data, filePath: normalizedFilePath });
44
41
  }
45
42
  } else {
46
43
  logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.14.3";
1
+ const ASTRO_VERSION = "4.14.5";
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";
@@ -23,7 +23,7 @@ async function dev(inlineConfig) {
23
23
  await telemetry.record([]);
24
24
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
25
25
  const logger = restart.container.logger;
26
- const currentVersion = "4.14.3";
26
+ const currentVersion = "4.14.5";
27
27
  const isPrerelease = currentVersion.includes("-");
28
28
  if (!isPrerelease) {
29
29
  try {
@@ -1485,6 +1485,7 @@ export declare const ActionsWithoutServerOutputError: {
1485
1485
  * - [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md)
1486
1486
  * @description
1487
1487
  * 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.
1488
+ * @deprecated Deprecated since version 4.13.2.
1488
1489
  */
1489
1490
  export declare const ActionsUsedWithForGetError: {
1490
1491
  name: string;
@@ -38,7 +38,7 @@ function serverStart({
38
38
  host,
39
39
  base
40
40
  }) {
41
- const version = "4.14.3";
41
+ const version = "4.14.5";
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.14.3"}`
273
+ `v${"4.14.5"}`
274
274
  )} ${headline}`
275
275
  );
276
276
  }
@@ -42,7 +42,7 @@ export declare class RenderContext {
42
42
  * - fallback
43
43
  */
44
44
  render(componentInstance: ComponentInstance | undefined, slots?: Record<string, any>): Promise<Response>;
45
- createAPIContext(props: APIContext['props']): APIContext;
45
+ createAPIContext(props: APIContext['props'], isPrerendered: boolean): APIContext;
46
46
  createActionAPIContext(): ActionAPIContext;
47
47
  createResult(mod: ComponentInstance): Promise<SSRResult>;
48
48
  /**
@@ -87,6 +87,7 @@ class RenderContext {
87
87
  async render(componentInstance, slots = {}) {
88
88
  const { cookies, middleware, pipeline } = this;
89
89
  const { logger, serverLike, streaming } = pipeline;
90
+ const isPrerendered = !serverLike || this.routeData.prerender;
90
91
  const props = Object.keys(this.props).length > 0 ? this.props : await getProps({
91
92
  mod: componentInstance,
92
93
  routeData: this.routeData,
@@ -95,7 +96,7 @@ class RenderContext {
95
96
  logger,
96
97
  serverLike
97
98
  });
98
- const apiContext = this.createAPIContext(props);
99
+ const apiContext = this.createAPIContext(props, isPrerendered);
99
100
  this.counter++;
100
101
  if (this.counter === 4) {
101
102
  return new Response("Loop Detected", {
@@ -166,12 +167,17 @@ class RenderContext {
166
167
  attachCookiesToResponse(response, cookies);
167
168
  return response;
168
169
  }
169
- createAPIContext(props) {
170
+ createAPIContext(props, isPrerendered) {
170
171
  const context = this.createActionAPIContext();
171
172
  return Object.assign(context, {
172
173
  props,
173
174
  getActionResult: createGetActionResult(context.locals),
174
- callAction: createCallAction(context)
175
+ callAction: createCallAction(context),
176
+ // Used internally by Actions middleware.
177
+ // TODO: discuss exposing this information from APIContext.
178
+ // middleware runs on prerendered routes in the dev server,
179
+ // so this is useful information to have.
180
+ _isPrerendered: isPrerendered
175
181
  });
176
182
  }
177
183
  async #executeRewrite(reroutePayload) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.14.3",
3
+ "version": "4.14.5",
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",
@@ -93,7 +93,9 @@ async function handleAction(param, path, context) {
93
93
  body,
94
94
  headers,
95
95
  });
96
- if (rawResult.status === 204) return;
96
+ if (rawResult.status === 204) {
97
+ return deserializeActionResult({ type: 'empty', status: 204 });
98
+ }
97
99
 
98
100
  return deserializeActionResult({
99
101
  type: rawResult.ok ? 'data' : 'error',