astro 7.0.0-alpha.1 → 7.0.0-alpha.2

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 (153) hide show
  1. package/components/Code.astro +1 -1
  2. package/dist/assets/endpoint/dev.js +1 -1
  3. package/dist/assets/endpoint/generic.js +7 -16
  4. package/dist/assets/endpoint/loadImage.d.ts +11 -0
  5. package/dist/assets/endpoint/loadImage.js +19 -0
  6. package/dist/assets/fonts/config.d.ts +4 -4
  7. package/dist/assets/fonts/core/optimize-fallbacks.js +38 -13
  8. package/dist/assets/fonts/definitions.d.ts +2 -2
  9. package/dist/assets/fonts/infra/system-fallbacks-provider.d.ts +2 -2
  10. package/dist/assets/fonts/infra/system-fallbacks-provider.js +46 -9
  11. package/dist/assets/fonts/types.d.ts +1 -0
  12. package/dist/assets/internal.js +20 -16
  13. package/dist/assets/services/service.d.ts +1 -1
  14. package/dist/assets/services/service.js +9 -9
  15. package/dist/assets/services/sharp.js +48 -28
  16. package/dist/assets/utils/generateImageStylesCSS.js +26 -6
  17. package/dist/assets/utils/inferSourceFormat.d.ts +8 -3
  18. package/dist/assets/utils/inferSourceFormat.js +15 -4
  19. package/dist/assets/utils/metadata.js +1 -1
  20. package/dist/assets/utils/vendor/image-size/types/svg.js +1 -1
  21. package/dist/cli/dev/background.d.ts +16 -0
  22. package/dist/cli/dev/background.js +116 -0
  23. package/dist/cli/dev/index.js +82 -3
  24. package/dist/cli/dev/logs.d.ts +6 -0
  25. package/dist/cli/dev/logs.js +72 -0
  26. package/dist/cli/dev/status.d.ts +15 -0
  27. package/dist/cli/dev/status.js +27 -0
  28. package/dist/cli/dev/stop.d.ts +12 -0
  29. package/dist/cli/dev/stop.js +43 -0
  30. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  31. package/dist/content/content-layer.js +16 -10
  32. package/dist/content/data-store.d.ts +1 -1
  33. package/dist/content/runtime-assets.d.ts +2 -2
  34. package/dist/content/runtime.d.ts +1 -1
  35. package/dist/content/runtime.js +9 -4
  36. package/dist/content/types-generator.js +5 -1
  37. package/dist/content/utils.d.ts +1 -1
  38. package/dist/content/utils.js +1 -1
  39. package/dist/core/app/base.d.ts +9 -0
  40. package/dist/core/app/base.js +47 -67
  41. package/dist/core/app/entrypoints/node.d.ts +1 -1
  42. package/dist/core/app/entrypoints/node.js +2 -0
  43. package/dist/core/app/node.d.ts +16 -0
  44. package/dist/core/app/node.js +59 -13
  45. package/dist/core/base-pipeline.d.ts +10 -0
  46. package/dist/core/base-pipeline.js +13 -1
  47. package/dist/core/build/generate.js +8 -1
  48. package/dist/core/build/index.d.ts +0 -11
  49. package/dist/core/build/index.js +0 -3
  50. package/dist/core/build/internal.d.ts +7 -0
  51. package/dist/core/build/plugins/plugin-chunk-imports.d.ts +6 -0
  52. package/dist/core/build/plugins/plugin-chunk-imports.js +29 -17
  53. package/dist/core/build/plugins/plugin-css.js +33 -0
  54. package/dist/core/build/plugins/plugin-internals.js +1 -1
  55. package/dist/core/build/static-build.js +22 -155
  56. package/dist/core/build/types.d.ts +0 -1
  57. package/dist/core/build/util.js +8 -1
  58. package/dist/core/build/vite-build-config.d.ts +28 -0
  59. package/dist/core/build/vite-build-config.js +165 -0
  60. package/dist/core/config/merge.js +4 -0
  61. package/dist/core/config/schemas/base.d.ts +19 -11
  62. package/dist/core/config/schemas/base.js +29 -4
  63. package/dist/core/config/schemas/relative.d.ts +69 -45
  64. package/dist/core/config/validate.js +59 -0
  65. package/dist/core/constants.js +1 -1
  66. package/dist/core/create-vite.js +3 -1
  67. package/dist/core/csp/config.js +17 -5
  68. package/dist/core/dev/dev.d.ts +1 -0
  69. package/dist/core/dev/dev.js +4 -1
  70. package/dist/core/dev/lockfile.d.ts +54 -0
  71. package/dist/core/dev/lockfile.js +93 -0
  72. package/dist/core/errors/errors-data.d.ts +43 -38
  73. package/dist/core/errors/errors-data.js +79 -73
  74. package/dist/core/errors/zod-error-map.js +3 -1
  75. package/dist/core/fetch/fetch-state.d.ts +12 -26
  76. package/dist/core/fetch/fetch-state.js +137 -20
  77. package/dist/core/fetch/types.d.ts +19 -0
  78. package/dist/core/fetch/vite-plugin.js +11 -4
  79. package/dist/core/hono/index.d.ts +1 -1
  80. package/dist/core/hono/index.js +6 -3
  81. package/dist/core/i18n/domain.d.ts +12 -0
  82. package/dist/core/i18n/domain.js +66 -0
  83. package/dist/core/i18n/handler.js +3 -0
  84. package/dist/core/logger/core.d.ts +1 -1
  85. package/dist/core/logger/core.js +1 -1
  86. package/dist/core/messages/runtime.js +1 -1
  87. package/dist/core/middleware/astro-middleware.js +3 -5
  88. package/dist/core/module-loader/vite.js +1 -2
  89. package/dist/core/pages/handler.js +1 -0
  90. package/dist/core/preview/index.js +6 -5
  91. package/dist/core/preview/static-preview-server.js +5 -2
  92. package/dist/core/render/params-and-props.js +1 -1
  93. package/dist/core/render/route-cache.d.ts +1 -0
  94. package/dist/core/render/route-cache.js +4 -4
  95. package/dist/core/routing/create-manifest.js +11 -1
  96. package/dist/core/routing/handler.js +5 -6
  97. package/dist/core/routing/parse-route.js +1 -1
  98. package/dist/core/routing/rewrite.js +1 -1
  99. package/dist/core/routing/router.d.ts +8 -0
  100. package/dist/core/routing/router.js +28 -0
  101. package/dist/core/routing/validation.js +1 -1
  102. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +6 -1
  103. package/dist/core/server-islands/vite-plugin-server-islands.js +13 -3
  104. package/dist/core/session/config.d.ts +1 -1
  105. package/dist/core/util/normalized-url.js +5 -2
  106. package/dist/core/util/pathname.d.ts +10 -1
  107. package/dist/core/util/pathname.js +13 -4
  108. package/dist/environments.js +1 -1
  109. package/dist/events/session.d.ts +8 -0
  110. package/dist/events/session.js +11 -0
  111. package/dist/jsx/rehype.d.ts +1 -1
  112. package/dist/manifest/virtual-module.js +3 -1
  113. package/dist/markdown/index.d.ts +4 -0
  114. package/dist/markdown/index.js +14 -0
  115. package/dist/prerender/utils.js +5 -1
  116. package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +9 -0
  117. package/dist/runtime/server/jsx.js +1 -1
  118. package/dist/runtime/server/render/component.js +8 -6
  119. package/dist/runtime/server/render/head.js +1 -5
  120. package/dist/runtime/server/render/util.js +2 -2
  121. package/dist/runtime/server/transition.d.ts +1 -6
  122. package/dist/runtime/server/transition.js +0 -8
  123. package/dist/transitions/events.d.ts +0 -14
  124. package/dist/transitions/events.js +0 -14
  125. package/dist/transitions/index.d.ts +0 -1
  126. package/dist/transitions/index.js +0 -2
  127. package/dist/transitions/vite-plugin-transitions.js +2 -4
  128. package/dist/types/public/config.d.ts +66 -18
  129. package/dist/types/public/content.d.ts +1 -1
  130. package/dist/types/public/index.d.ts +2 -1
  131. package/dist/types/public/integrations.d.ts +11 -3
  132. package/dist/types/public/manifest.d.ts +1 -1
  133. package/dist/virtual-modules/i18n.d.ts +2 -2
  134. package/dist/virtual-modules/i18n.js +1 -1
  135. package/dist/vite-plugin-app/app.d.ts +9 -1
  136. package/dist/vite-plugin-app/app.js +31 -21
  137. package/dist/vite-plugin-app/createAstroServerApp.d.ts +3 -1
  138. package/dist/vite-plugin-app/createAstroServerApp.js +4 -3
  139. package/dist/vite-plugin-astro-server/plugin.js +11 -5
  140. package/dist/vite-plugin-astro-server/route-guard.d.ts +33 -0
  141. package/dist/vite-plugin-astro-server/route-guard.js +42 -23
  142. package/dist/vite-plugin-dev-status/index.d.ts +2 -0
  143. package/dist/vite-plugin-dev-status/index.js +15 -0
  144. package/dist/vite-plugin-hmr-reload/index.d.ts +1 -1
  145. package/dist/vite-plugin-hmr-reload/index.js +23 -1
  146. package/dist/vite-plugin-integrations-container/index.js +15 -6
  147. package/dist/vite-plugin-markdown/content-entry-type.js +7 -4
  148. package/dist/vite-plugin-markdown/images.js +9 -11
  149. package/dist/vite-plugin-markdown/index.js +12 -11
  150. package/dist/vite-plugin-utils/index.js +7 -1
  151. package/package.json +13 -13
  152. package/templates/content/types.d.ts +1 -0
  153. package/types/transitions.d.ts +0 -7
@@ -1,12 +1,10 @@
1
1
  import {
2
- appendForwardSlash,
3
2
  collapseDuplicateLeadingSlashes,
4
- joinPaths,
5
3
  prependForwardSlash,
6
4
  removeTrailingForwardSlash
7
5
  } from "@astrojs/internal-helpers/path";
8
6
  import { matchPattern } from "@astrojs/internal-helpers/remote";
9
- import { normalizeTheLocale } from "../../i18n/index.js";
7
+ import { computePathnameFromDomain } from "../i18n/domain.js";
10
8
  import { PipelineFeatures } from "../base-pipeline.js";
11
9
  import { ASTRO_ERROR_HEADER, clientAddressSymbol } from "../constants.js";
12
10
  import { getSetCookiesFromResponse } from "../cookies/index.js";
@@ -16,6 +14,7 @@ import { DefaultFetchHandler } from "../fetch/default-handler.js";
16
14
  import { appSymbol } from "../constants.js";
17
15
  import { DefaultErrorHandler } from "../errors/default-handler.js";
18
16
  import { setRenderOptions } from "./render-options.js";
17
+ import { MultiLevelEncodingError } from "../util/pathname.js";
19
18
  class BaseApp {
20
19
  manifest;
21
20
  manifestData;
@@ -116,19 +115,30 @@ class BaseApp {
116
115
  return pathname;
117
116
  }
118
117
  /**
119
- * Extracts the base-stripped, decoded pathname from a request.
120
- * Used by adapters to compute the pathname for dev-mode route matching.
118
+ * Decodes a pathname with `decodeURI`, falling back to the raw pathname when it
119
+ * contains an invalid percent-sequence (e.g. `%C0%AF`, an overlong-UTF-8 encoding of
120
+ * `/` commonly sent by path-traversal scanners). A raw `decodeURI()` would throw
121
+ * `URIError: URI malformed`, and because `match()` runs before `render()` that error
122
+ * escapes the adapter's request handler as an uncaught exception (HTTP 500) that user
123
+ * middleware can't catch.
121
124
  */
122
- getPathnameFromRequest(request) {
123
- const url = new URL(request.url);
124
- const pathname = prependForwardSlash(this.removeBase(url.pathname));
125
+ safeDecodeURI(pathname) {
125
126
  try {
126
127
  return decodeURI(pathname);
127
128
  } catch (e) {
128
- this.adapterLogger.error(e.toString());
129
+ this.adapterLogger.debug(e.toString());
129
130
  return pathname;
130
131
  }
131
132
  }
133
+ /**
134
+ * Extracts the base-stripped, decoded pathname from a request.
135
+ * Used by adapters to compute the pathname for dev-mode route matching.
136
+ */
137
+ getPathnameFromRequest(request) {
138
+ const url = new URL(request.url);
139
+ const pathname = prependForwardSlash(this.removeBase(url.pathname));
140
+ return this.safeDecodeURI(pathname);
141
+ }
132
142
  /**
133
143
  * Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
134
144
  * routes aren't returned, even if they are matched.
@@ -144,12 +154,16 @@ class BaseApp {
144
154
  if (!pathname) {
145
155
  pathname = prependForwardSlash(this.removeBase(url.pathname));
146
156
  }
147
- const routeData = this.pipeline.matchRoute(decodeURI(pathname));
157
+ const routeData = this.pipeline.matchRoute(this.safeDecodeURI(pathname));
148
158
  if (!routeData) return void 0;
149
159
  if (allowPrerenderedRoutes) {
150
160
  return routeData;
151
161
  }
152
162
  if (routeData.prerender) {
163
+ if (routeData.params.length > 0) {
164
+ const allMatches = this.pipeline.matchAllRoutes(this.safeDecodeURI(pathname));
165
+ return allMatches.find((r) => !r.prerender);
166
+ }
153
167
  return void 0;
154
168
  }
155
169
  return routeData;
@@ -165,55 +179,14 @@ class BaseApp {
165
179
  return void 0;
166
180
  }
167
181
  computePathnameFromDomain(request) {
168
- let pathname = void 0;
169
- const url = new URL(request.url);
170
- if (this.manifest.i18n && (this.manifest.i18n.strategy === "domains-prefix-always" || this.manifest.i18n.strategy === "domains-prefix-other-locales" || this.manifest.i18n.strategy === "domains-prefix-always-no-redirect")) {
171
- let host = request.headers.get("X-Forwarded-Host");
172
- let protocol = request.headers.get("X-Forwarded-Proto");
173
- if (protocol) {
174
- protocol = protocol + ":";
175
- } else {
176
- protocol = url.protocol;
177
- }
178
- if (!host) {
179
- host = request.headers.get("Host");
180
- }
181
- if (host && protocol) {
182
- host = host.split(":")[0];
183
- try {
184
- let locale;
185
- const hostAsUrl = new URL(`${protocol}//${host}`);
186
- for (const [domainKey, localeValue] of Object.entries(
187
- this.manifest.i18n.domainLookupTable
188
- )) {
189
- const domainKeyAsUrl = new URL(domainKey);
190
- if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
191
- locale = localeValue;
192
- break;
193
- }
194
- }
195
- if (locale) {
196
- pathname = prependForwardSlash(
197
- joinPaths(normalizeTheLocale(locale), this.removeBase(url.pathname))
198
- );
199
- if (this.manifest.trailingSlash === "always") {
200
- pathname = appendForwardSlash(pathname);
201
- } else if (this.manifest.trailingSlash === "never") {
202
- pathname = removeTrailingForwardSlash(pathname);
203
- } else if (url.pathname.endsWith("/")) {
204
- pathname = appendForwardSlash(pathname);
205
- }
206
- }
207
- } catch (e) {
208
- this.logger.error(
209
- "router",
210
- `Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
211
- );
212
- this.logger.error("router", `Error: ${e}`);
213
- }
214
- }
215
- }
216
- return pathname;
182
+ return computePathnameFromDomain(
183
+ request,
184
+ new URL(request.url),
185
+ this.manifest.i18n,
186
+ this.manifest.base,
187
+ this.manifest.trailingSlash,
188
+ this.logger
189
+ );
217
190
  }
218
191
  async render(request, {
219
192
  addCookieHeader = false,
@@ -254,7 +227,7 @@ class BaseApp {
254
227
  if (!routeData) {
255
228
  const domainPathname = this.computePathnameFromDomain(request);
256
229
  if (domainPathname) {
257
- routeData = this.pipeline.matchRoute(decodeURI(domainPathname));
230
+ routeData = this.pipeline.matchRoute(this.safeDecodeURI(domainPathname));
258
231
  }
259
232
  }
260
233
  const resolvedOptions = {
@@ -266,13 +239,20 @@ class BaseApp {
266
239
  waitUntil
267
240
  };
268
241
  let response;
269
- if (this.#fetchHandler instanceof DefaultFetchHandler) {
270
- Reflect.set(request, appSymbol, this);
271
- response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
272
- } else {
273
- setRenderOptions(request, resolvedOptions);
274
- Reflect.set(request, appSymbol, this);
275
- response = await this.#fetchHandler.fetch(request);
242
+ try {
243
+ if (this.#fetchHandler instanceof DefaultFetchHandler) {
244
+ Reflect.set(request, appSymbol, this);
245
+ response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
246
+ } else {
247
+ setRenderOptions(request, resolvedOptions);
248
+ Reflect.set(request, appSymbol, this);
249
+ response = await this.#fetchHandler.fetch(request);
250
+ }
251
+ } catch (err) {
252
+ if (err instanceof MultiLevelEncodingError) {
253
+ return new Response("Bad Request", { status: 400 });
254
+ }
255
+ throw err;
276
256
  }
277
257
  this.#warnMissingFeatures();
278
258
  if (response.headers.get(ASTRO_ERROR_HEADER)) {
@@ -1 +1 @@
1
- export { NodeApp, loadApp, loadManifest, createRequest, writeResponse, getAbortControllerCleanup, } from '../node.js';
1
+ export { NodeApp, loadApp, loadManifest, createRequest, createRequestFromNodeRequest, writeResponse, getAbortControllerCleanup, } from '../node.js';
@@ -3,12 +3,14 @@ import {
3
3
  loadApp,
4
4
  loadManifest,
5
5
  createRequest,
6
+ createRequestFromNodeRequest,
6
7
  writeResponse,
7
8
  getAbortControllerCleanup
8
9
  } from "../node.js";
9
10
  export {
10
11
  NodeApp,
11
12
  createRequest,
13
+ createRequestFromNodeRequest,
12
14
  getAbortControllerCleanup,
13
15
  loadApp,
14
16
  loadManifest,
@@ -25,6 +25,22 @@ interface NodeRequest extends IncomingMessage {
25
25
  * })
26
26
  * ```
27
27
  */
28
+ /**
29
+ * Internal version of `createRequest` that skips X-Forwarded-* header
30
+ * validation. Forwarded headers are resolved later inside `FetchState`,
31
+ * so doing it here too would duplicate work on every request.
32
+ *
33
+ * Use this for all internal call sites that go through `app.render()`
34
+ * (which creates a `FetchState`). The public `createRequest` keeps the
35
+ * forwarded header logic for external adapters that may not use
36
+ * `FetchState`.
37
+ */
38
+ export declare function createRequestFromNodeRequest(req: NodeRequest, { skipBody, allowedDomains, bodySizeLimit, port: serverPort, }?: {
39
+ skipBody?: boolean;
40
+ allowedDomains?: Partial<RemotePattern>[];
41
+ bodySizeLimit?: number;
42
+ port?: number;
43
+ }): Request;
28
44
  export declare function createRequest(req: NodeRequest, { skipBody, allowedDomains, bodySizeLimit, port: serverPort, }?: {
29
45
  skipBody?: boolean;
30
46
  allowedDomains?: Partial<RemotePattern>[];
@@ -9,6 +9,48 @@ import {
9
9
  validateForwardedHeaders,
10
10
  validateHost
11
11
  } from "./validate-headers.js";
12
+ function createRequestFromNodeRequest(req, {
13
+ skipBody = false,
14
+ allowedDomains = [],
15
+ bodySizeLimit,
16
+ port: serverPort
17
+ } = {}) {
18
+ const controller = new AbortController();
19
+ const isEncrypted = "encrypted" in req.socket && req.socket.encrypted;
20
+ const protocol = isEncrypted ? "https" : "http";
21
+ const hostname = typeof req.headers.host === "string" ? req.headers.host : typeof req.headers[":authority"] === "string" ? req.headers[":authority"] : serverPort ? `localhost:${serverPort}` : "localhost";
22
+ let url;
23
+ try {
24
+ url = new URL(`${protocol}://${hostname}${req.url}`);
25
+ } catch {
26
+ url = new URL(`${protocol}://${hostname}`);
27
+ }
28
+ const options = {
29
+ method: req.method || "GET",
30
+ headers: makeRequestHeaders(req),
31
+ signal: controller.signal
32
+ };
33
+ const bodyAllowed = options.method !== "HEAD" && options.method !== "GET" && skipBody === false;
34
+ if (bodyAllowed) {
35
+ Object.assign(options, makeRequestBody(req, bodySizeLimit));
36
+ }
37
+ const request = new Request(url, options);
38
+ wireAbortController(req, controller);
39
+ const untrustedHostname = req.headers.host ?? req.headers[":authority"];
40
+ const validatedHostname = validateHost(
41
+ typeof untrustedHostname === "string" ? untrustedHostname : void 0,
42
+ protocol,
43
+ allowedDomains
44
+ );
45
+ const forwardedHost = getFirstForwardedValue(req.headers["x-forwarded-host"]);
46
+ const hostValidated = validatedHostname !== void 0 || forwardedHost !== void 0 && allowedDomains.length > 0;
47
+ const forwardedClientIp = hostValidated ? getFirstForwardedValue(req.headers["x-forwarded-for"]) : void 0;
48
+ const clientIp = forwardedClientIp || req.socket?.remoteAddress;
49
+ if (clientIp) {
50
+ Reflect.set(request, clientAddressSymbol, clientIp);
51
+ }
52
+ return request;
53
+ }
12
54
  function createRequest(req, {
13
55
  skipBody = false,
14
56
  allowedDomains = [],
@@ -51,6 +93,16 @@ function createRequest(req, {
51
93
  Object.assign(options, makeRequestBody(req, bodySizeLimit));
52
94
  }
53
95
  const request = new Request(url, options);
96
+ wireAbortController(req, controller);
97
+ const hostValidated = validated.host !== void 0 || validatedHostname !== void 0;
98
+ const forwardedClientIp = hostValidated ? getFirstForwardedValue(req.headers["x-forwarded-for"]) : void 0;
99
+ const clientIp = forwardedClientIp || req.socket?.remoteAddress;
100
+ if (clientIp) {
101
+ Reflect.set(request, clientAddressSymbol, clientIp);
102
+ }
103
+ return request;
104
+ }
105
+ function wireAbortController(req, controller) {
54
106
  const socket = getRequestSocket(req);
55
107
  if (socket && typeof socket.on === "function") {
56
108
  const existingCleanup = getAbortControllerCleanup(req);
@@ -85,13 +137,6 @@ function createRequest(req, {
85
137
  onSocketClose();
86
138
  }
87
139
  }
88
- const hostValidated = validated.host !== void 0 || validatedHostname !== void 0;
89
- const forwardedClientIp = hostValidated ? getFirstForwardedValue(req.headers["x-forwarded-for"]) : void 0;
90
- const clientIp = forwardedClientIp || req.socket?.remoteAddress;
91
- if (clientIp) {
92
- Reflect.set(request, clientAddressSymbol, clientIp);
93
- }
94
- return request;
95
140
  }
96
141
  async function writeResponse(source, destination) {
97
142
  const { status, headers, body, statusText } = source;
@@ -147,18 +192,15 @@ class NodeApp extends App {
147
192
  }
148
193
  match(req, allowPrerenderedRoutes = false) {
149
194
  if (!(req instanceof Request)) {
150
- req = createRequest(req, {
151
- skipBody: true,
152
- allowedDomains: this.manifest.allowedDomains
195
+ req = createRequestFromNodeRequest(req, {
196
+ skipBody: true
153
197
  });
154
198
  }
155
199
  return super.match(req, allowPrerenderedRoutes);
156
200
  }
157
201
  render(request, options) {
158
202
  if (!(request instanceof Request)) {
159
- request = createRequest(request, {
160
- allowedDomains: this.manifest.allowedDomains
161
- });
203
+ request = createRequestFromNodeRequest(request);
162
204
  }
163
205
  return super.render(request, options);
164
206
  }
@@ -219,6 +261,9 @@ function makeRequestBody(req, bodySizeLimit) {
219
261
  if (typeof req.body === "string" && req.body.length > 0) {
220
262
  return { body: Buffer.from(req.body) };
221
263
  }
264
+ if (req.body instanceof ArrayBuffer || ArrayBuffer.isView(req.body)) {
265
+ return { body: req.body };
266
+ }
222
267
  if (typeof req.body === "object" && req.body !== null && Object.keys(req.body).length > 0) {
223
268
  return { body: Buffer.from(JSON.stringify(req.body)) };
224
269
  }
@@ -280,6 +325,7 @@ async function loadApp(rootFolder) {
280
325
  export {
281
326
  NodeApp,
282
327
  createRequest,
328
+ createRequestFromNodeRequest,
283
329
  getAbortControllerCleanup,
284
330
  loadApp,
285
331
  loadManifest,
@@ -29,6 +29,9 @@ export declare const PipelineFeatures: {
29
29
  readonly i18n: number;
30
30
  readonly cache: number;
31
31
  };
32
+ /** All feature bits ORed together. Keep next to `PipelineFeatures` so
33
+ * new flags are hard to forget. */
34
+ export declare const ALL_PIPELINE_FEATURES: number;
32
35
  /**
33
36
  * The `Pipeline` represents the static parts of rendering that do not change between requests.
34
37
  * These are mostly known when the server first starts up and do not change.
@@ -117,6 +120,13 @@ export declare abstract class Pipeline {
117
120
  * routes or check public assets — use `BaseApp.match()` for that.
118
121
  */
119
122
  matchRoute(pathname: string): RouteData | undefined;
123
+ /**
124
+ * Returns all routes matching the given pathname, in priority order.
125
+ * Used when the first match cannot serve the request (e.g. a
126
+ * prerendered dynamic route that doesn't cover this specific path)
127
+ * and the caller needs to try subsequent matches.
128
+ */
129
+ matchAllRoutes(pathname: string): RouteData[];
120
130
  /**
121
131
  * Rebuilds the internal router after routes have been added or
122
132
  * removed (e.g. by the dev server on HMR).
@@ -21,6 +21,7 @@ const PipelineFeatures = {
21
21
  i18n: 1 << 4,
22
22
  cache: 1 << 5
23
23
  };
24
+ const ALL_PIPELINE_FEATURES = PipelineFeatures.redirects | PipelineFeatures.sessions | PipelineFeatures.actions | PipelineFeatures.middleware | PipelineFeatures.i18n | PipelineFeatures.cache;
24
25
  class Pipeline {
25
26
  internalMiddleware;
26
27
  resolvedMiddleware = void 0;
@@ -123,6 +124,15 @@ class Pipeline {
123
124
  if (match.type !== "match") return void 0;
124
125
  return match.route;
125
126
  }
127
+ /**
128
+ * Returns all routes matching the given pathname, in priority order.
129
+ * Used when the first match cannot serve the request (e.g. a
130
+ * prerendered dynamic route that doesn't cover this specific path)
131
+ * and the caller needs to try subsequent matches.
132
+ */
133
+ matchAllRoutes(pathname) {
134
+ return this.#router.matchAll(pathname, { allowWithoutBase: true });
135
+ }
126
136
  /**
127
137
  * Rebuilds the internal router after routes have been added or
128
138
  * removed (e.g. by the dev server on HMR).
@@ -182,7 +192,8 @@ class Pipeline {
182
192
  if (this.resolvedActions) {
183
193
  return this.resolvedActions;
184
194
  } else if (this.actions) {
185
- return this.actions();
195
+ this.resolvedActions = await this.actions();
196
+ return this.resolvedActions;
186
197
  }
187
198
  return NOOP_ACTIONS_MOD;
188
199
  }
@@ -285,6 +296,7 @@ class Pipeline {
285
296
  }
286
297
  }
287
298
  export {
299
+ ALL_PIPELINE_FEATURES,
288
300
  Pipeline,
289
301
  PipelineFeatures
290
302
  };
@@ -187,13 +187,20 @@ ${colors.bgGreen(colors.black(` ${verb} static routes `))}`);
187
187
  const cpuCount = os.cpus().length;
188
188
  const assetsCreationPipeline = await prepareAssetsGenerationEnv(options, totalCount);
189
189
  const queue = new PQueue({ concurrency: Math.max(cpuCount, 1) });
190
+ const errors = [];
190
191
  const assetsTimer = performance.now();
191
192
  for (const [originalPath, transforms] of staticImageList) {
192
193
  queue.add(() => generateImagesForPath(originalPath, transforms, assetsCreationPipeline)).catch((e) => {
193
- throw e;
194
+ logger.warn("build", `Unable to generate optimized image for ${originalPath}: ${e}`);
195
+ errors.push(new Error(`Error generating image for ${originalPath}: ${e}`, { cause: e }));
194
196
  });
195
197
  }
196
198
  await queue.onIdle();
199
+ if (errors.length === 1) {
200
+ throw errors[0];
201
+ } else if (errors.length > 1) {
202
+ throw new AggregateError(errors, `${errors.length} errors occurred during asset generation`);
203
+ }
197
204
  const assetsTimeEnd = performance.now();
198
205
  logger.info(null, colors.green(`\u2713 Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.
199
206
  `));
@@ -9,16 +9,6 @@ interface BuildOptions {
9
9
  * @default false
10
10
  */
11
11
  devOutput?: boolean;
12
- /**
13
- * Teardown the compiler WASM instance after build. This can improve performance when
14
- * building once, but may cause a performance hit if building multiple times in a row.
15
- *
16
- * When building multiple projects in the same execution (e.g. during tests), disabling
17
- * this option can greatly improve performance at the cost of some extra memory usage.
18
- *
19
- * @default true
20
- */
21
- teardownCompiler?: boolean;
22
12
  }
23
13
  /**
24
14
  * Builds your site for deployment. By default, this will generate static files and place them in a dist/ directory.
@@ -50,7 +40,6 @@ export declare class AstroBuilder {
50
40
  private origin;
51
41
  private routesList;
52
42
  private timer;
53
- private teardownCompiler;
54
43
  private sync;
55
44
  constructor(settings: AstroSettings, options: AstroBuilderOptions);
56
45
  /** Setup Vite and run any async setup logic that couldn't run inside of the constructor. */
@@ -55,14 +55,12 @@ class AstroBuilder {
55
55
  origin;
56
56
  routesList;
57
57
  timer;
58
- teardownCompiler;
59
58
  sync;
60
59
  constructor(settings, options) {
61
60
  this.mode = options.mode;
62
61
  this.runtimeMode = options.runtimeMode;
63
62
  this.settings = settings;
64
63
  this.logger = options.logger;
65
- this.teardownCompiler = options.teardownCompiler ?? true;
66
64
  this.sync = options.sync ?? true;
67
65
  this.origin = settings.config.site ? new URL(settings.config.site).origin : `http://localhost:${settings.config.server.port}`;
68
66
  this.routesList = options.routesList ?? { routes: [] };
@@ -151,7 +149,6 @@ class AstroBuilder {
151
149
  runtimeMode: this.runtimeMode,
152
150
  origin: this.origin,
153
151
  pageNames,
154
- teardownCompiler: this.teardownCompiler,
155
152
  viteConfig,
156
153
  key: keyPromise
157
154
  };
@@ -92,6 +92,13 @@ export interface BuildInternals {
92
92
  moduleIds: string[];
93
93
  prerender: boolean;
94
94
  }>;
95
+ /**
96
+ * Component exports that were rendered during the SSR build.
97
+ * Used by the client build's cssScopeTo recovery to distinguish between
98
+ * CSS that was tree-shaken because the component wasn't rendered in SSR
99
+ * vs CSS that was included in SSR.
100
+ */
101
+ ssrRenderedExports?: Map<string, Set<string>>;
95
102
  }
96
103
  /**
97
104
  * Creates internal maps used to coordinate the CSS and HTML plugins.
@@ -6,5 +6,11 @@ import type { StaticBuildOptions } from '../types.js';
6
6
  * bypass the HTML rendering pipeline and miss skew protection query params.
7
7
  *
8
8
  * Uses es-module-lexer to reliably parse both static and dynamic imports.
9
+ *
10
+ * This runs in `generateBundle` (not `renderChunk`) so that Vite's CSS plugin
11
+ * can first remove pure-CSS wrapper chunks and replace their imports with
12
+ * `/* empty css * /` comments. If we rewrote imports earlier (in `renderChunk`),
13
+ * the appended query params would break Vite's regex-based CSS chunk cleanup,
14
+ * leaving dangling imports to deleted chunks that 404 at runtime.
9
15
  */
10
16
  export declare function pluginChunkImports(options: StaticBuildOptions): VitePlugin | undefined;
@@ -1,5 +1,17 @@
1
1
  import { init, parse } from "es-module-lexer";
2
2
  import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js";
3
+ function getImportSpecifier(code, imp) {
4
+ if (imp.n != null) return imp.n;
5
+ if (imp.d > -1) {
6
+ const raw = code.slice(imp.s, imp.e);
7
+ const quote = raw[0];
8
+ if ((quote === "`" || quote === '"' || quote === "'") && raw.at(-1) === quote) {
9
+ const inner = raw.slice(1, -1);
10
+ if (!inner.includes("${")) return inner;
11
+ }
12
+ }
13
+ return void 0;
14
+ }
3
15
  function pluginChunkImports(options) {
4
16
  const assetQueryParams = options.settings.adapter?.client?.assetQueryParams;
5
17
  if (!assetQueryParams || assetQueryParams.toString() === "") {
@@ -12,25 +24,25 @@ function pluginChunkImports(options) {
12
24
  applyToEnvironment(environment) {
13
25
  return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client;
14
26
  },
15
- async renderChunk(code, _chunk) {
16
- if (!code.includes("./")) {
17
- return null;
18
- }
27
+ async generateBundle(_options, bundle) {
19
28
  await init;
20
- const [imports] = parse(code);
21
- const relativeImports = imports.filter(
22
- (imp) => imp.n && /^\.\.?\//.test(imp.n) && /\.(?:js|mjs)$/.test(imp.n)
23
- );
24
- if (relativeImports.length === 0) {
25
- return null;
26
- }
27
- let rewritten = code;
28
- for (let i = relativeImports.length - 1; i >= 0; i--) {
29
- const imp = relativeImports[i];
30
- const insertAt = imp.d > -1 ? imp.e - 1 : imp.e;
31
- rewritten = rewritten.slice(0, insertAt) + "?" + queryString + rewritten.slice(insertAt);
29
+ for (const [, chunk] of Object.entries(bundle)) {
30
+ if (chunk.type !== "chunk") continue;
31
+ if (!chunk.code.includes("./")) continue;
32
+ const [imports] = parse(chunk.code);
33
+ const relativeImports = imports.filter((imp) => {
34
+ const name = getImportSpecifier(chunk.code, imp);
35
+ return name != null && /^\.\.?\//.test(name) && /\.(?:js|mjs)$/.test(name);
36
+ });
37
+ if (relativeImports.length === 0) continue;
38
+ let rewritten = chunk.code;
39
+ for (let i = relativeImports.length - 1; i >= 0; i--) {
40
+ const imp = relativeImports[i];
41
+ const insertAt = imp.d > -1 ? imp.e - 1 : imp.e;
42
+ rewritten = rewritten.slice(0, insertAt) + "?" + queryString + rewritten.slice(insertAt);
43
+ }
44
+ chunk.code = rewritten;
32
45
  }
33
- return { code: rewritten, map: null };
34
46
  }
35
47
  };
36
48
  }
@@ -46,10 +46,25 @@ function rollupPluginAstroBuildCSS(options) {
46
46
  internals.cssModuleToChunkIdMap.set(moduleId, chunk.fileName);
47
47
  }
48
48
  }
49
+ for (const [moduleId, moduleInfo] of Object.entries(chunk.modules || {})) {
50
+ if (moduleInfo.renderedExports.length > 0) {
51
+ const existing = internals.ssrRenderedExports?.get(moduleId);
52
+ if (existing) {
53
+ for (const exp of moduleInfo.renderedExports) {
54
+ existing.add(exp);
55
+ }
56
+ } else {
57
+ internals.ssrRenderedExports ??= /* @__PURE__ */ new Map();
58
+ internals.ssrRenderedExports.set(moduleId, new Set(moduleInfo.renderedExports));
59
+ }
60
+ }
61
+ }
49
62
  }
50
63
  }
51
64
  const renderedComponentExports = /* @__PURE__ */ new Map();
52
65
  const componentToPages = /* @__PURE__ */ new Map();
66
+ const deletedCssAssets = /* @__PURE__ */ new Map();
67
+ const cssScopeToAddedCss = /* @__PURE__ */ new Set();
53
68
  if (this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) {
54
69
  for (const [, item] of Object.entries(bundle)) {
55
70
  if (item.type !== "chunk") continue;
@@ -76,6 +91,9 @@ function rollupPluginAstroBuildCSS(options) {
76
91
  );
77
92
  if (allCssInSSR && shouldDeleteCSSChunk(allModules, internals)) {
78
93
  for (const cssId of meta.importedCss) {
94
+ if (bundle[cssId]) {
95
+ deletedCssAssets.set(cssId, bundle[cssId]);
96
+ }
79
97
  delete bundle[cssId];
80
98
  }
81
99
  }
@@ -157,6 +175,14 @@ function rollupPluginAstroBuildCSS(options) {
157
175
  }
158
176
  }
159
177
  }
178
+ const ssrExports = internals.ssrRenderedExports?.get(scopedToModule);
179
+ if (!ssrExports || !ssrExports.has(scopedToExport)) {
180
+ for (const cssId of meta.importedCss) {
181
+ if (deletedCssAssets.has(cssId)) {
182
+ cssScopeToAddedCss.add(cssId);
183
+ }
184
+ }
185
+ }
160
186
  }
161
187
  }
162
188
  }
@@ -191,6 +217,13 @@ function rollupPluginAstroBuildCSS(options) {
191
217
  }
192
218
  }
193
219
  }
220
+ if (cssScopeToAddedCss.size > 0) {
221
+ for (const cssId of cssScopeToAddedCss) {
222
+ if (deletedCssAssets.has(cssId) && !bundle[cssId]) {
223
+ bundle[cssId] = deletedCssAssets.get(cssId);
224
+ }
225
+ }
226
+ }
194
227
  }
195
228
  };
196
229
  const singleCssPlugin = {
@@ -32,7 +32,7 @@ function pluginInternals(options, internals) {
32
32
  },
33
33
  resolve: {
34
34
  // Always bundle Astro runtime when building for SSR
35
- noExternal: ["astro"]
35
+ noExternal: ["astro", "@astrojs/internal-helpers"]
36
36
  }
37
37
  };
38
38
  }