astro 4.5.5 → 4.5.7

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.
@@ -2478,6 +2478,10 @@ export type SSRComponentMetadata = {
2478
2478
  containsHead: boolean;
2479
2479
  };
2480
2480
  export interface SSRResult {
2481
+ /**
2482
+ * Whether the page has failed with a non-recoverable error, or the client disconnected.
2483
+ */
2484
+ cancelled: boolean;
2481
2485
  styles: Set<SSRElement>;
2482
2486
  scripts: Set<SSRElement>;
2483
2487
  links: Set<SSRElement>;
@@ -360,7 +360,7 @@ async function writeContentFiles({
360
360
  };
361
361
  `;
362
362
  if (settings.config.experimental.contentCollectionJsonSchema && collectionConfig?.schema) {
363
- let zodSchemaForJson = collectionConfig.schema;
363
+ let zodSchemaForJson = typeof collectionConfig.schema === "function" ? collectionConfig.schema({ image: () => z.string() }) : collectionConfig.schema;
364
364
  if (zodSchemaForJson instanceof z.ZodObject) {
365
365
  zodSchemaForJson = zodSchemaForJson.extend({
366
366
  $schema: z.string().optional()
@@ -382,7 +382,7 @@ async function writeContentFiles({
382
382
  } catch (err) {
383
383
  logger.warn(
384
384
  "content",
385
- `An error was encountered while creating the JSON schema. Proceeding without it. Error: ${err}`
385
+ `An error was encountered while creating the JSON schema for the ${entryKey} entry in ${collectionKey} collection. Proceeding without it. Error: ${err}`
386
386
  );
387
387
  }
388
388
  }
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "4.5.5";
1
+ const ASTRO_VERSION = "4.5.7";
2
2
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
3
3
  const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
4
4
  const DEFAULT_404_COMPONENT = "astro-default-404";
@@ -23,7 +23,7 @@ async function dev(inlineConfig) {
23
23
  base: restart.container.settings.config.base
24
24
  })
25
25
  );
26
- const currentVersion = "4.5.5";
26
+ const currentVersion = "4.5.7";
27
27
  if (currentVersion.includes("-")) {
28
28
  logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
29
29
  }
@@ -697,6 +697,28 @@ export declare const MiddlewareNotAResponse: {
697
697
  title: string;
698
698
  message: string;
699
699
  };
700
+ /**
701
+ * @docs
702
+ * @description
703
+ * Thrown when an endpoint does not return anything or returns an object that is not a `Response` object.
704
+ *
705
+ * An endpoint must return either a `Response`, or a `Promise` that resolves with a `Response`. For example:
706
+ * ```ts
707
+ * import type { APIContext } from 'astro';
708
+ *
709
+ * export async function GET({ request, url, cookies }: APIContext): Promise<Response> {
710
+ * return Response.json({
711
+ * success: true,
712
+ * result: 'Data from Astro Endpoint!'
713
+ * })
714
+ * }
715
+ * ```
716
+ */
717
+ export declare const EndpointDidNotReturnAResponse: {
718
+ name: string;
719
+ title: string;
720
+ message: string;
721
+ };
700
722
  /**
701
723
  * @docs
702
724
  * @description
@@ -250,6 +250,11 @@ const MiddlewareNotAResponse = {
250
250
  title: "The middleware returned something that is not a `Response` object.",
251
251
  message: "Any data returned from middleware must be a valid `Response` object."
252
252
  };
253
+ const EndpointDidNotReturnAResponse = {
254
+ name: "EndpointDidNotReturnAResponse",
255
+ title: "The endpoint did not return a `Response`.",
256
+ message: "An endpoint must return either a `Response`, or a `Promise` that resolves with a `Response`."
257
+ };
253
258
  const LocalsNotAnObject = {
254
259
  name: "LocalsNotAnObject",
255
260
  title: "Value assigned to `locals` is not accepted.",
@@ -501,6 +506,7 @@ export {
501
506
  CouldNotTransformImage,
502
507
  DataCollectionEntryParseError,
503
508
  DuplicateContentEntrySlugError,
509
+ EndpointDidNotReturnAResponse,
504
510
  ExpectedImage,
505
511
  ExpectedImageOptions,
506
512
  FailedToFetchRemoteImageDimensions,
@@ -36,7 +36,7 @@ function serverStart({
36
36
  host,
37
37
  base
38
38
  }) {
39
- const version = "4.5.5";
39
+ const version = "4.5.7";
40
40
  const localPrefix = `${dim("\u2503")} Local `;
41
41
  const networkPrefix = `${dim("\u2503")} Network `;
42
42
  const emptyPrefix = " ".repeat(11);
@@ -261,7 +261,7 @@ function printHelp({
261
261
  message.push(
262
262
  linebreak(),
263
263
  ` ${bgGreen(black(` ${commandName} `))} ${green(
264
- `v${"4.5.5"}`
264
+ `v${"4.5.7"}`
265
265
  )} ${headline}`
266
266
  );
267
267
  }
@@ -74,24 +74,38 @@ class RenderContext {
74
74
  serverLike
75
75
  });
76
76
  const apiContext = this.createAPIContext(props);
77
- const { type } = routeData;
78
- const lastNext = type === "endpoint" ? () => renderEndpoint(componentInstance, apiContext, serverLike, logger) : type === "redirect" ? () => renderRedirect(this) : type === "page" ? async () => {
79
- const result = await this.createResult(componentInstance);
80
- const response2 = await renderPage(
81
- result,
82
- componentInstance?.default,
83
- props,
84
- {},
85
- streaming,
86
- routeData
87
- );
88
- response2.headers.set(ROUTE_TYPE_HEADER, "page");
89
- if (routeData.route === "/404" || routeData.route === "/500") {
90
- response2.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
77
+ const lastNext = async () => {
78
+ switch (routeData.type) {
79
+ case "endpoint":
80
+ return renderEndpoint(componentInstance, apiContext, serverLike, logger);
81
+ case "redirect":
82
+ return renderRedirect(this);
83
+ case "page": {
84
+ const result = await this.createResult(componentInstance);
85
+ let response2;
86
+ try {
87
+ response2 = await renderPage(
88
+ result,
89
+ componentInstance?.default,
90
+ props,
91
+ {},
92
+ streaming,
93
+ routeData
94
+ );
95
+ } catch (e) {
96
+ result.cancelled = true;
97
+ throw e;
98
+ }
99
+ response2.headers.set(ROUTE_TYPE_HEADER, "page");
100
+ if (routeData.route === "/404" || routeData.route === "/500") {
101
+ response2.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
102
+ }
103
+ return response2;
104
+ }
105
+ case "fallback": {
106
+ return new Response(null, { status: 500, headers: { [ROUTE_TYPE_HEADER]: "fallback" } });
107
+ }
91
108
  }
92
- return response2;
93
- } : type === "fallback" ? () => new Response(null, { status: 500, headers: { [ROUTE_TYPE_HEADER]: "fallback" } }) : () => {
94
- throw new Error("Unknown type of route: " + type);
95
109
  };
96
110
  const response = await callMiddleware(middleware, apiContext, lastNext);
97
111
  if (response.headers.get(ROUTE_TYPE_HEADER)) {
@@ -159,6 +173,7 @@ class RenderContext {
159
173
  }
160
174
  };
161
175
  const result = {
176
+ cancelled: false,
162
177
  clientDirectives,
163
178
  inlinedScripts,
164
179
  componentMetadata,
@@ -1,5 +1,7 @@
1
1
  import { bold } from "kleur/colors";
2
2
  import { REROUTABLE_STATUS_CODES, REROUTE_DIRECTIVE_HEADER } from "../../core/constants.js";
3
+ import { EndpointDidNotReturnAResponse } from "../../core/errors/errors-data.js";
4
+ import { AstroError } from "../../core/errors/errors.js";
3
5
  async function renderEndpoint(mod, context, ssr, logger) {
4
6
  const { request, url } = context;
5
7
  const method = request.method.toUpperCase();
@@ -30,6 +32,9 @@ Found handlers: ${Object.keys(mod).map((exp) => JSON.stringify(exp)).join(", ")}
30
32
  return new Response(null, { status: 500 });
31
33
  }
32
34
  const response = await handler.call(mod, context);
35
+ if (!response || response instanceof Response === false) {
36
+ throw new AstroError(EndpointDidNotReturnAResponse);
37
+ }
33
38
  if (REROUTABLE_STATUS_CODES.includes(response.status)) {
34
39
  response.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
35
40
  }
@@ -80,6 +80,9 @@ async function renderToReadableStream(result, componentFactory, props, children,
80
80
  setTimeout(() => controller.error(e), 0);
81
81
  }
82
82
  })();
83
+ },
84
+ cancel() {
85
+ result.cancelled = true;
83
86
  }
84
87
  });
85
88
  }
@@ -127,11 +130,10 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
127
130
  }
128
131
  let error = null;
129
132
  let next = promiseWithResolvers();
130
- let cancelled = false;
131
133
  const buffer = [];
132
134
  const iterator = {
133
135
  async next() {
134
- if (cancelled)
136
+ if (result.cancelled)
135
137
  return { done: true, value: void 0 };
136
138
  await next.promise;
137
139
  if (error) {
@@ -157,7 +159,7 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
157
159
  return returnValue;
158
160
  },
159
161
  async return() {
160
- cancelled = true;
162
+ result.cancelled = true;
161
163
  return { done: true, value: void 0 };
162
164
  }
163
165
  };
@@ -345,19 +345,27 @@ function renderAstroComponent(result, displayName, Component, props, slots = {})
345
345
  }
346
346
  async function renderComponent(result, displayName, Component, props, slots = {}) {
347
347
  if (isPromise(Component)) {
348
- Component = await Component;
348
+ Component = await Component.catch(handleCancellation);
349
349
  }
350
350
  if (isFragmentComponent(Component)) {
351
- return await renderFragmentComponent(result, slots);
351
+ return await renderFragmentComponent(result, slots).catch(handleCancellation);
352
352
  }
353
353
  props = normalizeProps(props);
354
354
  if (isHTMLComponent(Component)) {
355
- return await renderHTMLComponent(result, Component, props, slots);
355
+ return await renderHTMLComponent(result, Component, props, slots).catch(handleCancellation);
356
356
  }
357
357
  if (isAstroComponentFactory(Component)) {
358
358
  return renderAstroComponent(result, displayName, Component, props, slots);
359
359
  }
360
- return await renderFrameworkComponent(result, displayName, Component, props, slots);
360
+ return await renderFrameworkComponent(result, displayName, Component, props, slots).catch(
361
+ handleCancellation
362
+ );
363
+ function handleCancellation(e) {
364
+ if (result.cancelled)
365
+ return { render() {
366
+ } };
367
+ throw e;
368
+ }
361
369
  }
362
370
  function normalizeProps(props) {
363
371
  if (props["class:list"] !== void 0) {
@@ -99,7 +99,7 @@ function renderElement(name, { props: _props, children = "" }, shouldEscape = tr
99
99
  }
100
100
  }
101
101
  if ((children == null || children == "") && voidElementNames.test(name)) {
102
- return `<${name}${internalSpreadAttributes(props, shouldEscape)} />`;
102
+ return `<${name}${internalSpreadAttributes(props, shouldEscape)}>`;
103
103
  }
104
104
  return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
105
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "4.5.5",
3
+ "version": "4.5.7",
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",
@@ -162,8 +162,8 @@
162
162
  "zod": "^3.22.4",
163
163
  "zod-to-json-schema": "^3.22.4",
164
164
  "@astrojs/internal-helpers": "0.3.0",
165
- "@astrojs/telemetry": "3.0.4",
166
- "@astrojs/markdown-remark": "4.3.0"
165
+ "@astrojs/markdown-remark": "4.3.0",
166
+ "@astrojs/telemetry": "3.0.4"
167
167
  },
168
168
  "optionalDependencies": {
169
169
  "sharp": "^0.32.6"