astro 7.0.0-beta.3 → 7.0.0-beta.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.
Files changed (57) hide show
  1. package/dist/assets/build/generate.js +4 -3
  2. package/dist/cli/add/index.js +1 -0
  3. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  4. package/dist/container/index.d.ts +3 -3
  5. package/dist/content/content-layer.js +3 -3
  6. package/dist/content/runtime.d.ts +1 -1
  7. package/dist/content/runtime.js +1 -0
  8. package/dist/content/vite-plugin-content-virtual-mod.js +27 -0
  9. package/dist/core/app/base.js +7 -15
  10. package/dist/core/app/prepare-response.d.ts +2 -3
  11. package/dist/core/app/prepare-response.js +1 -6
  12. package/dist/core/build/generate.js +1 -1
  13. package/dist/core/build/plugins/plugin-css.js +1 -0
  14. package/dist/core/config/schemas/base.d.ts +4 -4
  15. package/dist/core/config/schemas/base.js +4 -4
  16. package/dist/core/config/validate.js +10 -2
  17. package/dist/core/constants.d.ts +0 -41
  18. package/dist/core/constants.js +1 -18
  19. package/dist/core/dev/dev.js +1 -1
  20. package/dist/core/errors/default-handler.js +22 -8
  21. package/dist/core/fetch/fetch-state.d.ts +11 -0
  22. package/dist/core/fetch/fetch-state.js +21 -2
  23. package/dist/core/fetch/index.d.ts +8 -3
  24. package/dist/core/fetch/index.js +2 -2
  25. package/dist/core/i18n/handler.js +7 -13
  26. package/dist/core/messages/runtime.js +1 -1
  27. package/dist/core/middleware/astro-middleware.d.ts +19 -0
  28. package/dist/core/middleware/astro-middleware.js +43 -0
  29. package/dist/core/middleware/noop-middleware.js +0 -2
  30. package/dist/core/middleware/vite-plugin.d.ts +1 -0
  31. package/dist/core/middleware/vite-plugin.js +5 -1
  32. package/dist/core/pages/handler.d.ts +13 -0
  33. package/dist/core/pages/handler.js +35 -14
  34. package/dist/core/routing/handler.js +6 -8
  35. package/dist/core/routing/rewrite.js +2 -1
  36. package/dist/core/util/normalized-url.js +2 -5
  37. package/dist/core/util/pathname.d.ts +17 -10
  38. package/dist/core/util/pathname.js +14 -7
  39. package/dist/i18n/index.js +11 -9
  40. package/dist/runtime/server/endpoint.d.ts +2 -1
  41. package/dist/runtime/server/endpoint.js +4 -13
  42. package/dist/runtime/server/jsx.js +2 -1
  43. package/dist/runtime/server/render/head.js +2 -1
  44. package/dist/runtime/server/render/util.js +4 -0
  45. package/dist/types/public/config.d.ts +68 -13
  46. package/dist/virtual-modules/container.d.ts +1 -1
  47. package/dist/vite-plugin-head/index.js +5 -11
  48. package/dist/vite-plugin-hmr-reload/index.js +19 -6
  49. package/dist/vite-plugin-html/transform/slots.js +4 -1
  50. package/dist/vite-plugin-pages/pages.d.ts +11 -0
  51. package/dist/vite-plugin-pages/pages.js +1 -3
  52. package/package.json +12 -6
  53. package/templates/content/module.mjs +0 -1
  54. package/dist/core/head-propagation/hint.d.ts +0 -4
  55. package/dist/core/head-propagation/hint.js +0 -7
  56. package/dist/jsx/rehype.d.ts +0 -5
  57. package/dist/jsx/rehype.js +0 -241
@@ -1,11 +1,11 @@
1
1
  import fs, { readFileSync } from "node:fs";
2
2
  import { basename } from "node:path/posix";
3
3
  import colors from "piccolore";
4
- import { getOutDirWithinCwd } from "../../core/build/common.js";
5
4
  import { getTimeStat } from "../../core/build/util.js";
6
5
  import { AstroError } from "../../core/errors/errors.js";
7
6
  import { AstroErrorData } from "../../core/errors/index.js";
8
7
  import { isRemotePath, removeLeadingForwardSlash } from "../../core/path.js";
8
+ import { getClientOutputDirectory } from "../../prerender/utils.js";
9
9
  import { getConfiguredImageService } from "../internal.js";
10
10
  import { isESMImportedImage } from "../utils/imageKind.js";
11
11
  import { loadRemoteImage, revalidateRemoteImage } from "./remote.js";
@@ -29,8 +29,9 @@ async function prepareAssetsGenerationEnv(options, totalCount) {
29
29
  serverRoot = new URL(".prerender/", settings.config.build.server);
30
30
  clientRoot = settings.config.build.client;
31
31
  } else {
32
- serverRoot = getOutDirWithinCwd(settings.config.outDir);
33
- clientRoot = settings.config.outDir;
32
+ const clientOutputDir = getClientOutputDirectory(settings);
33
+ serverRoot = clientOutputDir;
34
+ clientRoot = clientOutputDir;
34
35
  }
35
36
  return {
36
37
  logger,
@@ -48,6 +48,7 @@ export default {
48
48
  public-hoist-pattern[]=*lit*
49
49
  `,
50
50
  CLOUDFLARE_WRANGLER_CONFIG: (name, compatibilityDate) => `{
51
+ "$schema": "./node_modules/wrangler/config-schema.json",
51
52
  "compatibility_date": ${JSON.stringify(compatibilityDate)},
52
53
  "compatibility_flags": ["global_fetch_strictly_public"],
53
54
  "name": ${JSON.stringify(name)},
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "7.0.0-beta.3";
3
+ version = "7.0.0-beta.5";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -67,7 +67,7 @@ export type ContainerRenderOptions = {
67
67
  */
68
68
  routeType?: RouteType;
69
69
  /**
70
- * Allows to pass `Astro.props` to an Astro component:
70
+ * Allows passing `Astro.props` to an Astro component:
71
71
  *
72
72
  * ```js
73
73
  * container.renderToString(Endpoint, { props: { "lorem": "ipsum" } });
@@ -75,9 +75,9 @@ export type ContainerRenderOptions = {
75
75
  */
76
76
  props?: Props;
77
77
  /**
78
- * When `false`, it forces to render the component as it was a full-fledged page.
78
+ * When `false`, it forces the component to render as if it were a full-fledged page.
79
79
  *
80
- * By default, the container API render components as [partials](https://docs.astro.build/en/basics/astro-pages/#page-partials).
80
+ * By default, the container API renders components as [partials](https://docs.astro.build/en/basics/astro-pages/#page-partials).
81
81
  *
82
82
  */
83
83
  partial?: boolean;
@@ -197,7 +197,7 @@ ${contentConfig.error.message}`
197
197
  logger.info("Content config changed");
198
198
  shouldClear = true;
199
199
  }
200
- if (previousAstroVersion && previousAstroVersion !== "7.0.0-beta.3") {
200
+ if (previousAstroVersion && previousAstroVersion !== "7.0.0-beta.5") {
201
201
  logger.info("Astro version changed");
202
202
  shouldClear = true;
203
203
  }
@@ -205,8 +205,8 @@ ${contentConfig.error.message}`
205
205
  logger.info("Clearing content store");
206
206
  this.#store.clearAll();
207
207
  }
208
- if ("7.0.0-beta.3") {
209
- this.#store.metaStore().set("astro-version", "7.0.0-beta.3");
208
+ if ("7.0.0-beta.5") {
209
+ this.#store.metaStore().set("astro-version", "7.0.0-beta.5");
210
210
  }
211
211
  if (currentConfigDigest) {
212
212
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -70,7 +70,7 @@ type RenderResult = {
70
70
  };
71
71
  export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>): T;
72
72
  export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
73
- export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
73
+ export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<string, number>>, z.ZodString, z.ZodObject<{
74
74
  id: z.ZodString;
75
75
  collection: z.ZodString;
76
76
  }, z.core.$strip>, z.ZodObject<{
@@ -484,6 +484,7 @@ async function render({
484
484
  function createReference() {
485
485
  return function reference(collection) {
486
486
  return z.union([
487
+ z.number().transform((num) => num.toString(10)),
487
488
  z.string(),
488
489
  z.object({
489
490
  id: z.string(),
@@ -23,6 +23,25 @@ import {
23
23
  } from "./consts.js";
24
24
  import { getDataStoreFile } from "./content-layer.js";
25
25
  import { getContentPaths, isDeferredModule } from "./utils.js";
26
+ function invalidateAssetImports(viteServer, filePath) {
27
+ const timestamp = Date.now();
28
+ for (const environment of Object.values(viteServer.environments)) {
29
+ const modules = environment.moduleGraph.getModulesByFile(filePath);
30
+ if (modules) {
31
+ for (const module of modules) {
32
+ environment.moduleGraph.invalidateModule(module, void 0, timestamp, true);
33
+ }
34
+ }
35
+ if (isRunnableDevEnvironment(environment)) {
36
+ const runnerModules = environment.runner.evaluatedModules.getModulesByFile(filePath);
37
+ if (runnerModules) {
38
+ for (const runnerModule of runnerModules) {
39
+ environment.runner.evaluatedModules.invalidateModule(runnerModule);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
26
45
  function invalidateDataStore(viteServer) {
27
46
  const environment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
28
47
  const module = environment.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
@@ -67,8 +86,11 @@ function astroContentVirtualModPlugin({
67
86
  },
68
87
  buildStart() {
69
88
  if (devServer) {
89
+ const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir));
70
90
  devServer.watcher.add(fileURLToPath(dataStoreFile));
91
+ devServer.watcher.add(assetImportsPath);
71
92
  invalidateDataStore(devServer);
93
+ invalidateAssetImports(devServer, assetImportsPath);
72
94
  }
73
95
  },
74
96
  resolveId: {
@@ -175,14 +197,19 @@ function astroContentVirtualModPlugin({
175
197
  configureServer(server) {
176
198
  devServer = server;
177
199
  const dataStorePath = fileURLToPath(dataStoreFile);
200
+ const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir));
178
201
  server.watcher.on("add", (addedPath) => {
179
202
  if (addedPath === dataStorePath) {
180
203
  invalidateDataStore(server);
204
+ invalidateAssetImports(server, assetImportsPath);
181
205
  }
182
206
  });
183
207
  server.watcher.on("change", (changedPath) => {
184
208
  if (changedPath === dataStorePath) {
185
209
  invalidateDataStore(server);
210
+ invalidateAssetImports(server, assetImportsPath);
211
+ } else if (changedPath === assetImportsPath) {
212
+ invalidateAssetImports(server, assetImportsPath);
186
213
  }
187
214
  });
188
215
  }
@@ -14,7 +14,6 @@ import { DefaultFetchHandler } from "../fetch/default-handler.js";
14
14
  import { appSymbol } from "../constants.js";
15
15
  import { DefaultErrorHandler } from "../errors/default-handler.js";
16
16
  import { setRenderOptions } from "./render-options.js";
17
- import { MultiLevelEncodingError } from "../util/pathname.js";
18
17
  class BaseApp {
19
18
  manifest;
20
19
  manifestData;
@@ -237,20 +236,13 @@ class BaseApp {
237
236
  waitUntil
238
237
  };
239
238
  let response;
240
- try {
241
- if (this.#fetchHandler instanceof DefaultFetchHandler) {
242
- Reflect.set(request, appSymbol, this);
243
- response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
244
- } else {
245
- setRenderOptions(request, resolvedOptions);
246
- Reflect.set(request, appSymbol, this);
247
- response = await this.#fetchHandler.fetch(request);
248
- }
249
- } catch (err) {
250
- if (err instanceof MultiLevelEncodingError) {
251
- return new Response("Bad Request", { status: 400 });
252
- }
253
- throw err;
239
+ if (this.#fetchHandler instanceof DefaultFetchHandler) {
240
+ Reflect.set(request, appSymbol, this);
241
+ response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
242
+ } else {
243
+ setRenderOptions(request, resolvedOptions);
244
+ Reflect.set(request, appSymbol, this);
245
+ response = await this.#fetchHandler.fetch(request);
254
246
  }
255
247
  this.#warnMissingFeatures();
256
248
  if (response.headers.get(ASTRO_ERROR_HEADER)) {
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Strips internal-only headers from the response before sending it to the
3
- * user agent, and optionally appends cookies written via `Astro.cookie.set()`
4
- * to the `Set-Cookie` header.
2
+ * Appends cookies written via `Astro.cookie.set()` to the `Set-Cookie` header
3
+ * and marks the response as sent.
5
4
  *
6
5
  * This is a pure function with no dependencies on the app; it is shared by
7
6
  * `AstroHandler` and the various error handlers.
@@ -1,11 +1,6 @@
1
- import { INTERNAL_RESPONSE_HEADERS, responseSentSymbol } from "../constants.js";
1
+ import { responseSentSymbol } from "../constants.js";
2
2
  import { getSetCookiesFromResponse } from "../cookies/index.js";
3
3
  function prepareResponse(response, { addCookieHeader }) {
4
- for (const headerName of INTERNAL_RESPONSE_HEADERS) {
5
- if (response.headers.has(headerName)) {
6
- response.headers.delete(headerName);
7
- }
8
- }
9
4
  if (addCookieHeader) {
10
5
  for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) {
11
6
  response.headers.append("set-cookie", setCookieHeaderValue);
@@ -267,7 +267,7 @@ async function renderPath({
267
267
  relativeLocation: locationSite,
268
268
  from: fromPath
269
269
  });
270
- if (config.compressHTML === true) {
270
+ if (config.compressHTML) {
271
271
  body = body.replaceAll("\n", "");
272
272
  }
273
273
  if (route.type !== "redirect") {
@@ -107,6 +107,7 @@ function rollupPluginAstroBuildCSS(options) {
107
107
  if (meta.importedCss.size < 1) continue;
108
108
  if (this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) {
109
109
  for (const id of Object.keys(chunk.modules)) {
110
+ if (!isCSSRequest(id)) continue;
110
111
  for (const pageData of getParentClientOnlys(id, this, internals)) {
111
112
  for (const importedCssImport of meta.importedCss) {
112
113
  const cssToInfoRecord = pagesToCss[pageData.moduleSpecifier] ??= {};
@@ -49,7 +49,7 @@ export declare const ASTRO_CONFIG_DEFAULTS: {
49
49
  devToolbar: {
50
50
  enabled: true;
51
51
  };
52
- compressHTML: true;
52
+ compressHTML: "jsx";
53
53
  server: {
54
54
  host: false;
55
55
  port: number;
@@ -57,7 +57,7 @@ export declare const ASTRO_CONFIG_DEFAULTS: {
57
57
  allowedHosts: never[];
58
58
  };
59
59
  integrations: never[];
60
- markdown: Required<Omit<import("@astrojs/markdown-remark").AstroMarkdownOptions, "image">>;
60
+ markdown: Required<Omit<import("@astrojs/internal-helpers/markdown").AstroMarkdownOptions, "image">>;
61
61
  vite: {};
62
62
  legacy: {
63
63
  collectionsBackwardsCompat: false;
@@ -329,8 +329,8 @@ export declare const AstroConfigSchema: z.ZodObject<{
329
329
  processor: z.ZodDefault<z.ZodObject<{
330
330
  name: z.ZodString;
331
331
  options: z.ZodDefault<z.ZodCustom<object, object>>;
332
- createRenderer: z.ZodCustom<(shared: import("@astrojs/markdown-remark").AstroMarkdownOptions) => Promise<import("@astrojs/markdown-remark").MarkdownRenderer>, (shared: import("@astrojs/markdown-remark").AstroMarkdownOptions) => Promise<import("@astrojs/markdown-remark").MarkdownRenderer>>;
333
- createMdxRenderer: z.ZodOptional<z.ZodCustom<((shared: import("@astrojs/markdown-remark").AstroMarkdownOptions, mdx: import("@astrojs/internal-helpers/markdown").MdxRendererOptions) => Promise<import("@astrojs/internal-helpers/markdown").MdxRenderer>) | undefined, ((shared: import("@astrojs/markdown-remark").AstroMarkdownOptions, mdx: import("@astrojs/internal-helpers/markdown").MdxRendererOptions) => Promise<import("@astrojs/internal-helpers/markdown").MdxRenderer>) | undefined>>;
332
+ createRenderer: z.ZodCustom<(shared: import("@astrojs/internal-helpers/markdown").AstroMarkdownOptions) => Promise<import("@astrojs/internal-helpers/markdown").MarkdownRenderer>, (shared: import("@astrojs/internal-helpers/markdown").AstroMarkdownOptions) => Promise<import("@astrojs/internal-helpers/markdown").MarkdownRenderer>>;
333
+ createMdxRenderer: z.ZodOptional<z.ZodCustom<((shared: import("@astrojs/internal-helpers/markdown").AstroMarkdownOptions, mdx: import("@astrojs/internal-helpers/markdown").MdxRendererOptions) => Promise<import("@astrojs/internal-helpers/markdown").MdxRenderer>) | undefined, ((shared: import("@astrojs/internal-helpers/markdown").AstroMarkdownOptions, mdx: import("@astrojs/internal-helpers/markdown").MdxRendererOptions) => Promise<import("@astrojs/internal-helpers/markdown").MdxRenderer>) | undefined>>;
334
334
  }, z.core.$strip>>;
335
335
  }, z.core.$strip>>;
336
336
  vite: z.ZodDefault<z.ZodCustom<ViteUserConfig, ViteUserConfig>>;
@@ -2,7 +2,7 @@ import {
2
2
  markdownConfigDefaults,
3
3
  syntaxHighlightDefaults
4
4
  } from "@astrojs/internal-helpers/markdown";
5
- import { unified } from "@astrojs/markdown-remark";
5
+ import { satteri } from "@astrojs/markdown-satteri";
6
6
  import { bundledThemes } from "shiki";
7
7
  import * as z from "zod/v4";
8
8
  import { FontFamilySchema } from "../../../assets/fonts/config.js";
@@ -38,7 +38,7 @@ const ASTRO_CONFIG_DEFAULTS = {
38
38
  devToolbar: {
39
39
  enabled: true
40
40
  },
41
- compressHTML: true,
41
+ compressHTML: "jsx",
42
42
  server: {
43
43
  host: false,
44
44
  port: 4321,
@@ -238,7 +238,7 @@ const AstroConfigSchema = z.object({
238
238
  remarkRehype: z.custom((data) => data instanceof Object && !Array.isArray(data)).default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype),
239
239
  // Deprecated: left undefined unless the user explicitly sets them, so the
240
240
  // deprecation warning only fires when actually used. The active processor
241
- // (`unified()`) supplies the real default (`gfm`/smart punctuation on) when
241
+ // (`satteri()`) supplies the real default (`gfm`/smart punctuation on) when
242
242
  // these are absent.
243
243
  gfm: z.boolean().optional(),
244
244
  smartypants: z.union([z.boolean(), smartypantsOptionsSchema]).transform((val) => {
@@ -256,7 +256,7 @@ const AstroConfigSchema = z.object({
256
256
  createMdxRenderer: z.custom(
257
257
  (v) => v === void 0 || typeof v === "function"
258
258
  ).optional()
259
- }).default(() => unified())
259
+ }).default(() => satteri())
260
260
  }).prefault({}),
261
261
  vite: z.custom((data) => data instanceof Object && !Array.isArray(data)).default(ASTRO_CONFIG_DEFAULTS.vite),
262
262
  i18n: z.optional(
@@ -30,7 +30,7 @@ function warnDeprecatedMarkdownOptions(config) {
30
30
  const names = deprecated.map((key) => `\`markdown.${key}\``).join(" and ");
31
31
  const isPlural = deprecated.length > 1;
32
32
  console.warn(
33
- `[astro] ${names} ${isPlural ? "are" : "is"} deprecated. Move ${isPlural ? "them" : "it"} onto your processor instead (e.g. \`unified({ gfm: false, smartypants: false })\`). Will be removed in a future major.`
33
+ `[astro] ${names} ${isPlural ? "are" : "is"} deprecated. Move ${isPlural ? "them" : "it"} onto your processor instead (e.g. \`satteri({ features: { gfm: false, smartPunctuation: false } })\`, or \`unified({ gfm: false, smartypants: false })\` from \`@astrojs/markdown-remark\`). Will be removed in a future major.`
34
34
  );
35
35
  }
36
36
  let didWarnAboutLegacyMarkdownPlugins = false;
@@ -45,7 +45,15 @@ async function coerceLegacyMarkdownPlugins(config) {
45
45
  if (remarkPlugins.length === 0 && rehypePlugins.length === 0 && Object.keys(remarkRehype).length === 0) {
46
46
  return;
47
47
  }
48
- const { unified, isUnifiedProcessor } = await import("@astrojs/markdown-remark");
48
+ let unified;
49
+ let isUnifiedProcessor;
50
+ try {
51
+ ({ unified, isUnifiedProcessor } = await import("@astrojs/markdown-remark"));
52
+ } catch {
53
+ throw new Error(
54
+ "`markdown.remarkPlugins`, `markdown.rehypePlugins`, and `markdown.remarkRehype` run on the `unified` processor from `@astrojs/markdown-remark`, which is no longer installed by default now that S\xE4tteri is the default Markdown processor. Install it with:\n npm install @astrojs/markdown-remark"
55
+ );
56
+ }
49
57
  const current = md.processor;
50
58
  if (!current || isUnifiedProcessor(current)) {
51
59
  const target = current ?? (md.processor = unified());
@@ -1,46 +1,5 @@
1
1
  export declare const ASTRO_VERSION: string;
2
2
  export declare const ASTRO_GENERATOR: string;
3
- /**
4
- * The name for the header used to help rerouting behavior.
5
- * When set to "no", astro will NOT try to reroute an error response to the corresponding error page, which is the default behavior that can sometimes lead to loops.
6
- *
7
- * ```ts
8
- * const response = new Response("keep this content as-is", {
9
- * status: 404,
10
- * headers: {
11
- * // note that using a variable name as the key of an object needs to be wrapped in square brackets in javascript
12
- * // without them, the header name will be interpreted as "REROUTE_DIRECTIVE_HEADER" instead of "X-Astro-Reroute"
13
- * [REROUTE_DIRECTIVE_HEADER]: 'no',
14
- * }
15
- * })
16
- * ```
17
- * Alternatively...
18
- * ```ts
19
- * response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
20
- * ```
21
- */
22
- export declare const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
23
- /**
24
- * Header and value that are attached to a Response object when a **user rewrite** occurs.
25
- *
26
- * This metadata is used to determine the origin of a Response. If a rewrite has occurred, it should be prioritised over other logic.
27
- */
28
- export declare const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
29
- export declare const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
30
- /**
31
- * This header is set by the no-op Astro middleware.
32
- */
33
- export declare const NOOP_MIDDLEWARE_HEADER = "X-Astro-Noop";
34
- /**
35
- * The name for the header used to help i18n middleware, which only needs to act on "page" and "fallback" route types.
36
- */
37
- export declare const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
38
- /**
39
- * Internal headers that should be stripped from the response before
40
- * sending it to the user agent. Add new internal headers here so
41
- * `prepareResponse` removes them automatically.
42
- */
43
- export declare const INTERNAL_RESPONSE_HEADERS: readonly ["X-Astro-Reroute", "X-Astro-Rewrite", "X-Astro-Noop", "X-Astro-Route-Type"];
44
3
  /**
45
4
  * Set by internal handlers (e.g. PagesHandler) to signal that a
46
5
  * response should be replaced with the corresponding error page.
@@ -1,16 +1,5 @@
1
- const ASTRO_VERSION = "7.0.0-beta.3";
1
+ const ASTRO_VERSION = "7.0.0-beta.5";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
- const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
4
- const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
5
- const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
6
- const NOOP_MIDDLEWARE_HEADER = "X-Astro-Noop";
7
- const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
8
- const INTERNAL_RESPONSE_HEADERS = [
9
- REROUTE_DIRECTIVE_HEADER,
10
- REWRITE_DIRECTIVE_HEADER_KEY,
11
- NOOP_MIDDLEWARE_HEADER,
12
- ROUTE_TYPE_HEADER
13
- ];
14
3
  const ASTRO_ERROR_HEADER = "X-Astro-Error";
15
4
  const DEFAULT_404_COMPONENT = "astro-default-404.astro";
16
5
  const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308, 300, 304];
@@ -57,15 +46,9 @@ export {
57
46
  ASTRO_VERSION,
58
47
  ASTRO_VITE_ENVIRONMENT_NAMES,
59
48
  DEFAULT_404_COMPONENT,
60
- INTERNAL_RESPONSE_HEADERS,
61
49
  MIDDLEWARE_PATH_SEGMENT_NAME,
62
- NOOP_MIDDLEWARE_HEADER,
63
50
  REDIRECT_STATUS_CODES,
64
51
  REROUTABLE_STATUS_CODES,
65
- REROUTE_DIRECTIVE_HEADER,
66
- REWRITE_DIRECTIVE_HEADER_KEY,
67
- REWRITE_DIRECTIVE_HEADER_VALUE,
68
- ROUTE_TYPE_HEADER,
69
52
  SUPPORTED_MARKDOWN_FILE_EXTENSIONS,
70
53
  appSymbol,
71
54
  clientAddressSymbol,
@@ -26,7 +26,7 @@ async function dev(inlineConfig) {
26
26
  await telemetry.record([]);
27
27
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
28
28
  const logger = restart.container.logger;
29
- const currentVersion = "7.0.0-beta.3";
29
+ const currentVersion = "7.0.0-beta.5";
30
30
  const isPrerelease = currentVersion.includes("-");
31
31
  if (!isPrerelease) {
32
32
  try {
@@ -6,6 +6,7 @@ import { AstroMiddleware } from "../middleware/astro-middleware.js";
6
6
  import { PagesHandler } from "../pages/handler.js";
7
7
  import { matchRoute } from "../routing/match.js";
8
8
  import { provideSession } from "../session/handler.js";
9
+ import { validateHost } from "../app/validate-headers.js";
9
10
  class DefaultErrorHandler {
10
11
  #app;
11
12
  #astroMiddleware;
@@ -31,15 +32,27 @@ class DefaultErrorHandler {
31
32
  if (errorRouteData) {
32
33
  if (errorRouteData.prerender) {
33
34
  const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? ".html" : "";
34
- const statusURL = new URL(`${app.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`, url);
35
+ const allowedDomains = app.manifest.allowedDomains;
36
+ const validatedHost = validateHost(url.host, url.protocol.replace(":", ""), allowedDomains);
37
+ const safeOrigin = validatedHost ? url.origin : `${url.protocol}//localhost`;
38
+ const statusURL = new URL(
39
+ `${app.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`,
40
+ safeOrigin
41
+ );
35
42
  if (statusURL.toString() !== request.url && resolvedRenderOptions.prerenderedErrorPageFetch) {
36
- const response2 = await resolvedRenderOptions.prerenderedErrorPageFetch(
37
- statusURL.toString()
38
- );
39
- const override = { status, removeContentEncodingHeaders: true };
40
- const newResponse = mergeResponses(response2, originalResponse, override);
41
- prepareResponse(newResponse, resolvedRenderOptions);
42
- return newResponse;
43
+ try {
44
+ const response2 = await resolvedRenderOptions.prerenderedErrorPageFetch(
45
+ statusURL.toString()
46
+ );
47
+ const override = { status, removeContentEncodingHeaders: true };
48
+ const newResponse = mergeResponses(response2, originalResponse, override);
49
+ prepareResponse(newResponse, resolvedRenderOptions);
50
+ return newResponse;
51
+ } catch {
52
+ const response2 = mergeResponses(new Response(null, { status }), originalResponse);
53
+ prepareResponse(response2, resolvedRenderOptions);
54
+ return response2;
55
+ }
43
56
  }
44
57
  }
45
58
  const mod = await app.pipeline.getComponentByRoute(errorRouteData);
@@ -66,6 +79,7 @@ class DefaultErrorHandler {
66
79
  return this.renderError(request, {
67
80
  ...resolvedRenderOptions,
68
81
  status,
82
+ error,
69
83
  response: originalResponse,
70
84
  skipMiddleware: true,
71
85
  pathname: resolvedPathname
@@ -108,6 +108,12 @@ export declare class FetchState implements AstroFetchState {
108
108
  status: number;
109
109
  /** Whether user middleware should be skipped for this request. */
110
110
  skipMiddleware: boolean;
111
+ /**
112
+ * Set to `true` when the request path was encoded too many times to fully
113
+ * decode (see {@link validateAndDecodePathname}). These requests are
114
+ * rejected with a `400` before middleware or routing run.
115
+ */
116
+ invalidEncoding: boolean;
111
117
  /** A flag that tells the render content if the rewriting was triggered. */
112
118
  isRewriting: boolean;
113
119
  /** A safety net in case of loops (rewrite counter). */
@@ -122,6 +128,10 @@ export declare class FetchState implements AstroFetchState {
122
128
  clientAddress: string | undefined;
123
129
  /** Whether this is a partial render (container API). */
124
130
  partial: boolean | undefined;
131
+ /** Internal metadata about the current response route type. */
132
+ responseRouteType: 'page' | 'fallback' | undefined;
133
+ /** Internal flag to prevent rerouting this response to an error page. */
134
+ skipErrorReroute: boolean;
125
135
  /** Whether to inject CSP meta tags. */
126
136
  shouldInjectCspMetaTags: boolean | undefined;
127
137
  /** Request-scoped locals object, shared with user middleware. */
@@ -227,4 +237,5 @@ export declare class FetchState implements AstroFetchState {
227
237
  * after an in-flight rewrite swaps the route / request / params.
228
238
  */
229
239
  invalidateContexts(): void;
240
+ resetResponseMetadata(): void;
230
241
  }
@@ -29,6 +29,7 @@ import { getParams, getProps } from "../render/index.js";
29
29
  import { Rewrites } from "../rewrites/handler.js";
30
30
  import { isRoute404or500, isRouteServerIsland } from "../routing/match.js";
31
31
  import { normalizeUrl } from "../util/normalized-url.js";
32
+ import { MultiLevelEncodingError, validateAndDecodePathname } from "../util/pathname.js";
32
33
  import { getOriginPathname, setOriginPathname } from "../routing/rewrite.js";
33
34
  import { computePathnameFromDomain } from "../i18n/domain.js";
34
35
  import { getCustom404Route, routeHasHtmlExtension } from "../routing/helpers.js";
@@ -87,6 +88,12 @@ class FetchState {
87
88
  status = 200;
88
89
  /** Whether user middleware should be skipped for this request. */
89
90
  skipMiddleware = false;
91
+ /**
92
+ * Set to `true` when the request path was encoded too many times to fully
93
+ * decode (see {@link validateAndDecodePathname}). These requests are
94
+ * rejected with a `400` before middleware or routing run.
95
+ */
96
+ invalidEncoding = false;
90
97
  /** A flag that tells the render content if the rewriting was triggered. */
91
98
  isRewriting = false;
92
99
  /** A safety net in case of loops (rewrite counter). */
@@ -110,6 +117,10 @@ class FetchState {
110
117
  clientAddress;
111
118
  /** Whether this is a partial render (container API). */
112
119
  partial;
120
+ /** Internal metadata about the current response route type. */
121
+ responseRouteType;
122
+ /** Internal flag to prevent rerouting this response to an error page. */
123
+ skipErrorReroute = false;
113
124
  /** Whether to inject CSP meta tags. */
114
125
  shouldInjectCspMetaTags;
115
126
  /** Request-scoped locals object, shared with user middleware. */
@@ -190,7 +201,7 @@ class FetchState {
190
201
  this.locals = options?.locals ?? {};
191
202
  this.url = normalizeUrl(url);
192
203
  this.cookies = new AstroCookies(request);
193
- if (pipeline.manifest.allowedDomains && pipeline.manifest.allowedDomains.length > 0) {
204
+ if (pipeline.manifest.allowedDomains && pipeline.manifest.allowedDomains.length > 0 && !this.routeData?.prerender) {
194
205
  this.#applyForwardedHeaders();
195
206
  }
196
207
  if (!Reflect.get(this.request, originPathnameSymbol)) {
@@ -694,8 +705,12 @@ class FetchState {
694
705
  }
695
706
  pathname = prependForwardSlash(pathname);
696
707
  try {
697
- return decodeURI(pathname);
708
+ return validateAndDecodePathname(pathname);
698
709
  } catch (e) {
710
+ if (e instanceof MultiLevelEncodingError) {
711
+ this.invalidEncoding = true;
712
+ return pathname;
713
+ }
699
714
  this.pipeline.logger.error(null, e.toString());
700
715
  return pathname;
701
716
  }
@@ -875,6 +890,10 @@ class FetchState {
875
890
  this.actionApiContext = null;
876
891
  this.apiContext = null;
877
892
  }
893
+ resetResponseMetadata() {
894
+ this.responseRouteType = void 0;
895
+ this.skipErrorReroute = false;
896
+ }
878
897
  }
879
898
  export {
880
899
  FetchState,
@@ -13,13 +13,18 @@ export declare function astro(state: FetchState): Promise<Response>;
13
13
  export declare function trailingSlash(state: FetchState): Response | undefined;
14
14
  /**
15
15
  * Runs Astro's middleware chain for the given state, calling `next` at
16
- * the bottom of the chain to produce the response. Lazily creates
17
- * the render context if needed.
16
+ * the bottom of the chain to produce the response. Lazily creates the
17
+ * render context if needed. Unmatched routes render the 404 error page;
18
+ * errors thrown by user middleware are logged and render the 500 error
19
+ * page; errors surfaced through `next` (the host framework's downstream
20
+ * chain) propagate to the host instead.
18
21
  */
19
22
  export declare function middleware(state: FetchState, next: (state: FetchState) => Promise<Response>): Promise<Response>;
20
23
  /**
21
24
  * Dispatches the request to the matched route (endpoint, page, redirect,
22
- * or fallback). Lazily creates the render context if needed.
25
+ * or fallback). Lazily creates the render context if needed. Unmatched
26
+ * routes render the 404 error page; render-time errors are logged and
27
+ * render the 500 error page.
23
28
  */
24
29
  export declare function pages(state: FetchState): Promise<Response>;
25
30
  /**
@@ -51,7 +51,7 @@ function middleware(state, next) {
51
51
  mw = new AstroMiddleware(app.pipeline);
52
52
  middlewareInstances.set(app, mw);
53
53
  }
54
- return mw.handle(state, (s, _ctx) => next(s));
54
+ return mw.handleWithErrorFallback(app, state, (s, _ctx) => next(s));
55
55
  }
56
56
  const pagesHandlers = /* @__PURE__ */ new WeakMap();
57
57
  function pages(state) {
@@ -61,7 +61,7 @@ function pages(state) {
61
61
  handler = new PagesHandler(app.pipeline);
62
62
  pagesHandlers.set(app, handler);
63
63
  }
64
- return handler.handle(state, state.getAPIContext());
64
+ return handler.handleWithErrorFallback(app, state);
65
65
  }
66
66
  function sessions(state) {
67
67
  return provideSession(state);