astro 6.0.0-beta.17 → 6.0.0-beta.18

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 (74) hide show
  1. package/dist/actions/runtime/types.d.ts +1 -1
  2. package/dist/assets/utils/proxy.js +1 -1
  3. package/dist/assets/utils/svg.js +11 -1
  4. package/dist/assets/vite-plugin-assets.js +15 -1
  5. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  6. package/dist/config/entrypoint.d.ts +7 -0
  7. package/dist/config/entrypoint.js +8 -0
  8. package/dist/content/content-layer.js +3 -3
  9. package/dist/content/runtime.js +7 -1
  10. package/dist/content/vite-plugin-content-assets.js +7 -4
  11. package/dist/core/app/base.d.ts +11 -7
  12. package/dist/core/app/base.js +118 -54
  13. package/dist/core/app/dev/app.d.ts +1 -1
  14. package/dist/core/app/dev/app.js +11 -6
  15. package/dist/core/app/node.js +2 -1
  16. package/dist/core/app/types.d.ts +5 -0
  17. package/dist/core/base-pipeline.d.ts +12 -1
  18. package/dist/core/base-pipeline.js +19 -2
  19. package/dist/core/build/common.js +2 -1
  20. package/dist/core/build/generate.js +1 -1
  21. package/dist/core/build/plugins/plugin-manifest.js +5 -0
  22. package/dist/core/build/static-build.js +2 -1
  23. package/dist/core/cache/config.d.ts +29 -0
  24. package/dist/core/cache/config.js +20 -0
  25. package/dist/core/cache/memory-provider.d.ts +56 -0
  26. package/dist/core/cache/memory-provider.js +305 -0
  27. package/dist/core/cache/runtime/cache.d.ts +36 -0
  28. package/dist/core/cache/runtime/cache.js +98 -0
  29. package/dist/core/cache/runtime/noop.d.ts +19 -0
  30. package/dist/core/cache/runtime/noop.js +33 -0
  31. package/dist/core/cache/runtime/route-matching.d.ts +20 -0
  32. package/dist/core/cache/runtime/route-matching.js +28 -0
  33. package/dist/core/cache/runtime/utils.d.ts +8 -0
  34. package/dist/core/cache/runtime/utils.js +34 -0
  35. package/dist/core/cache/types.d.ts +64 -0
  36. package/dist/core/cache/types.js +0 -0
  37. package/dist/core/cache/utils.d.ts +16 -0
  38. package/dist/core/cache/utils.js +47 -0
  39. package/dist/core/cache/vite-plugin.d.ts +6 -0
  40. package/dist/core/cache/vite-plugin.js +56 -0
  41. package/dist/core/config/schemas/base.d.ts +14 -0
  42. package/dist/core/config/schemas/base.js +3 -0
  43. package/dist/core/config/schemas/relative.d.ts +42 -4
  44. package/dist/core/constants.js +1 -1
  45. package/dist/core/create-vite.js +2 -0
  46. package/dist/core/dev/container.js +3 -3
  47. package/dist/core/dev/dev.js +1 -1
  48. package/dist/core/errors/errors-data.d.ts +46 -3
  49. package/dist/core/errors/errors-data.js +26 -5
  50. package/dist/core/logger/core.d.ts +1 -1
  51. package/dist/core/messages/runtime.js +1 -1
  52. package/dist/core/middleware/index.js +2 -0
  53. package/dist/core/redirects/render.js +5 -2
  54. package/dist/core/render-context.d.ts +2 -0
  55. package/dist/core/render-context.js +36 -1
  56. package/dist/core/routing/generator.js +2 -1
  57. package/dist/core/server-islands/endpoint.d.ts +6 -0
  58. package/dist/core/server-islands/endpoint.js +1 -0
  59. package/dist/core/session/runtime.js +20 -1
  60. package/dist/i18n/fallback.js +1 -1
  61. package/dist/i18n/index.js +1 -1
  62. package/dist/integrations/hooks.js +2 -1
  63. package/dist/manifest/serialized.js +9 -0
  64. package/dist/prerender/utils.js +5 -1
  65. package/dist/runtime/server/astro-global.js +3 -0
  66. package/dist/runtime/server/render/util.js +0 -12
  67. package/dist/types/public/config.d.ts +70 -14
  68. package/dist/types/public/context.d.ts +10 -0
  69. package/dist/types/public/index.d.ts +1 -0
  70. package/dist/types/public/integrations.d.ts +8 -0
  71. package/dist/vite-plugin-app/app.d.ts +1 -1
  72. package/dist/vite-plugin-app/app.js +11 -6
  73. package/package.json +6 -5
  74. package/tsconfigs/base.json +3 -1
@@ -49,7 +49,7 @@ export interface ActionsLocals {
49
49
  actionName: string;
50
50
  };
51
51
  }
52
- export type ActionAPIContext = Pick<APIContext, 'request' | 'url' | 'isPrerendered' | 'locals' | 'clientAddress' | 'cookies' | 'currentLocale' | 'generator' | 'routePattern' | 'site' | 'params' | 'preferredLocale' | 'preferredLocaleList' | 'originPathname' | 'session' | 'csp'>;
52
+ export type ActionAPIContext = Pick<APIContext, 'request' | 'url' | 'isPrerendered' | 'locals' | 'clientAddress' | 'cookies' | 'currentLocale' | 'generator' | 'routePattern' | 'site' | 'params' | 'preferredLocale' | 'preferredLocaleList' | 'originPathname' | 'session' | 'cache' | 'csp'>;
53
53
  export type MaybePromise<T> = T | Promise<T>;
54
54
  /**
55
55
  * Used to preserve the input schema type in the error object.
@@ -9,7 +9,7 @@ function getProxyCode(options, isSSR) {
9
9
  if (name === 'fsPath') {
10
10
  return ${stringifiedFSPath};
11
11
  }
12
- ${!isSSR ? `if (target[name] !== undefined && globalThis.astroAsset) globalThis.astroAsset?.referencedImages.add(${stringifiedFSPath});` : ""}
12
+ ${!isSSR ? `if (target[name] !== undefined && globalThis.astroAsset) globalThis.astroAsset?.referencedImages?.add(${stringifiedFSPath});` : ""}
13
13
  return target[name];
14
14
  }
15
15
  })
@@ -49,6 +49,16 @@ function makeSvgComponent(meta, contents, svgoConfig) {
49
49
  return `import { createSvgComponent } from 'astro/assets/runtime';
50
50
  export default createSvgComponent(${JSON.stringify(props)})`;
51
51
  }
52
+ function parseSvgComponentData(meta, contents, svgoConfig) {
53
+ const file = typeof contents === "string" ? contents : contents.toString("utf-8");
54
+ const { attributes, body: children } = parseSvg({
55
+ path: meta.fsPath,
56
+ contents: file,
57
+ svgoConfig
58
+ });
59
+ return { attributes: dropAttributes(attributes), children };
60
+ }
52
61
  export {
53
- makeSvgComponent
62
+ makeSvgComponent,
63
+ parseSvgComponentData
54
64
  };
@@ -29,7 +29,7 @@ import { hashTransform, propsToFilename } from "./utils/hash.js";
29
29
  import { emitImageMetadata } from "./utils/node.js";
30
30
  import { CONTENT_IMAGE_FLAG } from "../content/consts.js";
31
31
  import { getProxyCode } from "./utils/proxy.js";
32
- import { makeSvgComponent } from "./utils/svg.js";
32
+ import { makeSvgComponent, parseSvgComponentData } from "./utils/svg.js";
33
33
  import { createPlaceholderURL, stringifyPlaceholderURL } from "./utils/url.js";
34
34
  const assetRegex = new RegExp(`\\.(${VALID_INPUT_FORMATS.join("|")})`, "i");
35
35
  const assetRegexEnds = new RegExp(`\\.(${VALID_INPUT_FORMATS.join("|")})$`, "i");
@@ -252,6 +252,20 @@ function assets({ fs, settings, sync, logger }) {
252
252
  if (isSSROnlyEnvironment) {
253
253
  globalThis.astroAsset.referencedImages.add(imageMetadata.fsPath);
254
254
  }
255
+ if (id.endsWith(".svg") && isContentImage) {
256
+ const contents = await fs.promises.readFile(imageMetadata.fsPath, {
257
+ encoding: "utf8"
258
+ });
259
+ const svgData = parseSvgComponentData(
260
+ imageMetadata,
261
+ contents,
262
+ settings.config.experimental.svgo
263
+ );
264
+ const metadataWithSvg = { ...imageMetadata, __svgData: svgData };
265
+ return {
266
+ code: `export default ${getProxyCode(metadataWithSvg, isSSROnlyEnvironment)}`
267
+ };
268
+ }
255
269
  return {
256
270
  code: `export default ${getProxyCode(imageMetadata, isSSROnlyEnvironment)}`
257
271
  };
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.0.0-beta.17";
3
+ version = "6.0.0-beta.18";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -1,4 +1,6 @@
1
1
  import type { SharpImageServiceConfig } from '../assets/services/sharp.js';
2
+ import type { MemoryCacheProviderOptions } from '../core/cache/memory-provider.js';
3
+ import type { CacheProviderConfig } from '../core/cache/types.js';
2
4
  import type { ImageServiceConfig } from '../types/public/index.js';
3
5
  export { fontProviders } from '../assets/fonts/providers/index.js';
4
6
  export { mergeConfig } from '../core/config/merge.js';
@@ -17,3 +19,8 @@ export declare function sharpImageService(config?: SharpImageServiceConfig): Ima
17
19
  * See: https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service
18
20
  */
19
21
  export declare function passthroughImageService(): ImageServiceConfig;
22
+ /**
23
+ * Return the configuration needed to use the built-in in-memory LRU cache provider.
24
+ * This is a runtime-agnostic provider suitable for single-instance deployments.
25
+ */
26
+ export declare function memoryCache(config?: MemoryCacheProviderOptions): CacheProviderConfig<MemoryCacheProviderOptions>;
@@ -16,11 +16,19 @@ function passthroughImageService() {
16
16
  config: {}
17
17
  };
18
18
  }
19
+ function memoryCache(config = {}) {
20
+ return {
21
+ name: "memory",
22
+ entrypoint: "astro/cache/memory",
23
+ config
24
+ };
25
+ }
19
26
  export {
20
27
  defineConfig,
21
28
  envField,
22
29
  fontProviders,
23
30
  getViteConfig,
31
+ memoryCache,
24
32
  mergeConfig,
25
33
  passthroughImageService,
26
34
  sessionDrivers,
@@ -189,7 +189,7 @@ ${contentConfig.error.message}`
189
189
  logger.info("Content config changed");
190
190
  shouldClear = true;
191
191
  }
192
- if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.17") {
192
+ if (previousAstroVersion && previousAstroVersion !== "6.0.0-beta.18") {
193
193
  logger.info("Astro version changed");
194
194
  shouldClear = true;
195
195
  }
@@ -197,8 +197,8 @@ ${contentConfig.error.message}`
197
197
  logger.info("Clearing content store");
198
198
  this.#store.clearAll();
199
199
  }
200
- if ("6.0.0-beta.17") {
201
- this.#store.metaStore().set("astro-version", "6.0.0-beta.17");
200
+ if ("6.0.0-beta.18") {
201
+ this.#store.metaStore().set("astro-version", "6.0.0-beta.18");
202
202
  }
203
203
  if (currentConfigDigest) {
204
204
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -1,6 +1,7 @@
1
1
  import { escape } from "html-escaper";
2
2
  import { Traverse } from "neotraverse/modern";
3
3
  import * as z from "zod/v4";
4
+ import { createSvgComponent } from "../assets/runtime.js";
4
5
  import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
5
6
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
6
7
  import { isRemotePath, prependForwardSlash } from "../core/path.js";
@@ -363,7 +364,12 @@ function updateImageReferencesInData(data, fileName, imageAssetMap) {
363
364
  }
364
365
  const imported = imageAssetMap?.get(id);
365
366
  if (imported) {
366
- ctx.update(imported);
367
+ if (imported.__svgData) {
368
+ const { __svgData: svgData, ...meta } = imported;
369
+ ctx.update(createSvgComponent({ meta, ...svgData }));
370
+ } else {
371
+ ctx.update(imported);
372
+ }
367
373
  } else {
368
374
  ctx.update(src);
369
375
  }
@@ -60,10 +60,12 @@ function astroContentAssetPropagationPlugin({
60
60
  }
61
61
  },
62
62
  configureServer(server) {
63
- if (!isRunnableDevEnvironment(server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr])) {
64
- return;
63
+ const ssrEnv = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
64
+ if (isRunnableDevEnvironment(ssrEnv)) {
65
+ environment = ssrEnv;
66
+ } else if (isRunnableDevEnvironment(server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro])) {
67
+ environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro];
65
68
  }
66
- environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
67
69
  },
68
70
  transform: {
69
71
  filter: {
@@ -75,7 +77,8 @@ function astroContentAssetPropagationPlugin({
75
77
  let stringifiedLinks, stringifiedStyles;
76
78
  if (isAstroServerEnvironment(this.environment) && environment) {
77
79
  if (!environment.moduleGraph.getModuleById(basePath)?.ssrModule) {
78
- await environment.runner.import(basePath);
80
+ await environment.runner.import(basePath).catch(() => {
81
+ });
79
82
  }
80
83
  const {
81
84
  styles,
@@ -51,9 +51,15 @@ export interface RenderOptions {
51
51
  */
52
52
  routeData?: RouteData;
53
53
  }
54
- export interface RenderErrorOptions {
55
- locals?: App.Locals;
56
- routeData?: RouteData;
54
+ type RequiredRenderOptions = Required<RenderOptions>;
55
+ interface ResolvedRenderOptions {
56
+ addCookieHeader: RequiredRenderOptions['addCookieHeader'];
57
+ clientAddress: RequiredRenderOptions['clientAddress'] | undefined;
58
+ prerenderedErrorPageFetch: RequiredRenderOptions['prerenderedErrorPageFetch'] | undefined;
59
+ locals: RequiredRenderOptions['locals'] | undefined;
60
+ routeData: RequiredRenderOptions['routeData'] | undefined;
61
+ }
62
+ export interface RenderErrorOptions extends ResolvedRenderOptions {
57
63
  response?: Response;
58
64
  status: 404 | 500;
59
65
  /**
@@ -64,8 +70,6 @@ export interface RenderErrorOptions {
64
70
  * Allows passing an error to 500.astro. It will be available through `Astro.props.error`.
65
71
  */
66
72
  error?: unknown;
67
- clientAddress: string | undefined;
68
- prerenderedErrorPageFetch: ((url: ErrorPagePath) => Promise<Response>) | undefined;
69
73
  }
70
74
  type ErrorPagePath = `${string}/404` | `${string}/500` | `${string}/404/` | `${string}/500/` | `${string}404.html` | `${string}500.html`;
71
75
  export declare abstract class BaseApp<P extends Pipeline = AppPipeline> {
@@ -120,7 +124,7 @@ export declare abstract class BaseApp<P extends Pipeline = AppPipeline> {
120
124
  devMatch(pathname?: string): Promise<DevMatch | undefined> | undefined;
121
125
  private computePathnameFromDomain;
122
126
  private redirectTrailingSlash;
123
- render(request: Request, renderOptions?: RenderOptions): Promise<Response>;
127
+ render(request: Request, { addCookieHeader, clientAddress, locals, prerenderedErrorPageFetch, routeData, }?: RenderOptions): Promise<Response>;
124
128
  setCookieHeaders(response: Response): Generator<string, string[], any>;
125
129
  /**
126
130
  * Reads all the cookies written by `Astro.cookie.set()` onto the passed response.
@@ -138,7 +142,7 @@ export declare abstract class BaseApp<P extends Pipeline = AppPipeline> {
138
142
  * If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
139
143
  * This also handles pre-rendered /404 or /500 routes
140
144
  */
141
- renderError(request: Request, { locals, status, response: originalResponse, skipMiddleware, error, clientAddress, prerenderedErrorPageFetch, }: RenderErrorOptions): Promise<Response>;
145
+ renderError(request: Request, { status, response: originalResponse, skipMiddleware, error, ...resolvedRenderOptions }: RenderErrorOptions): Promise<Response>;
142
146
  private mergeResponses;
143
147
  getDefaultStatusCode(routeData: RouteData, pathname: string): number;
144
148
  getManifest(): SSRManifest;
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  appendForwardSlash,
3
+ collapseDuplicateLeadingSlashes,
3
4
  collapseDuplicateTrailingSlashes,
4
5
  hasFileExtension,
5
6
  isInternalPath,
@@ -12,12 +13,19 @@ import { normalizeTheLocale } from "../../i18n/index.js";
12
13
  import {
13
14
  clientAddressSymbol,
14
15
  DEFAULT_404_COMPONENT,
16
+ NOOP_MIDDLEWARE_HEADER,
15
17
  REROUTABLE_STATUS_CODES,
16
18
  REROUTE_DIRECTIVE_HEADER,
17
19
  responseSentSymbol,
18
- REWRITE_DIRECTIVE_HEADER_KEY
20
+ REWRITE_DIRECTIVE_HEADER_KEY,
21
+ ROUTE_TYPE_HEADER
19
22
  } from "../constants.js";
20
- import { getSetCookiesFromResponse } from "../cookies/index.js";
23
+ import {
24
+ AstroCookies,
25
+ attachCookiesToResponse,
26
+ getSetCookiesFromResponse
27
+ } from "../cookies/index.js";
28
+ import { getCookiesFromResponse } from "../cookies/response.js";
21
29
  import { AstroError, AstroErrorData } from "../errors/index.js";
22
30
  import { consoleLogDestination } from "../logger/console.js";
23
31
  import { AstroIntegrationLogger, Logger } from "../logger/core.js";
@@ -25,6 +33,7 @@ import { RenderContext } from "../render-context.js";
25
33
  import { redirectTemplate } from "../routing/3xx.js";
26
34
  import { ensure404Route } from "../routing/astro-designed-error-pages.js";
27
35
  import { matchRoute } from "../routing/match.js";
36
+ import { applyCacheHeaders } from "../cache/runtime/cache.js";
28
37
  import { Router } from "../routing/router.js";
29
38
  import { PERSIST_SYMBOL } from "../session/runtime.js";
30
39
  class BaseApp {
@@ -78,6 +87,7 @@ class BaseApp {
78
87
  this.#router = this.createRouter(this.manifestData);
79
88
  }
80
89
  removeBase(pathname) {
90
+ pathname = collapseDuplicateLeadingSlashes(pathname);
81
91
  if (pathname.startsWith(this.manifest.base)) {
82
92
  return pathname.slice(this.baseWithoutTrailingSlash.length + 1);
83
93
  }
@@ -208,18 +218,19 @@ class BaseApp {
208
218
  }
209
219
  return pathname;
210
220
  }
211
- async render(request, renderOptions) {
221
+ async render(request, {
222
+ addCookieHeader = false,
223
+ clientAddress = Reflect.get(request, clientAddressSymbol),
224
+ locals,
225
+ prerenderedErrorPageFetch = fetch,
226
+ routeData
227
+ } = {}) {
212
228
  const timeStart = performance.now();
213
- let routeData = renderOptions?.routeData;
214
- let locals;
215
- let clientAddress;
216
- let addCookieHeader;
217
229
  const url = new URL(request.url);
218
230
  const redirect = this.redirectTrailingSlash(url.pathname);
219
- const prerenderedErrorPageFetch = renderOptions?.prerenderedErrorPageFetch ?? fetch;
220
231
  if (redirect !== url.pathname) {
221
232
  const status = request.method === "GET" ? 301 : 308;
222
- return new Response(
233
+ const response2 = new Response(
223
234
  redirectTemplate({
224
235
  status,
225
236
  relativeLocation: url.pathname,
@@ -233,11 +244,9 @@ class BaseApp {
233
244
  }
234
245
  }
235
246
  );
247
+ this.#prepareResponse(response2, { addCookieHeader });
248
+ return response2;
236
249
  }
237
- addCookieHeader = renderOptions?.addCookieHeader;
238
- clientAddress = renderOptions?.clientAddress ?? Reflect.get(request, clientAddressSymbol);
239
- routeData = renderOptions?.routeData;
240
- locals = renderOptions?.locals;
241
250
  if (routeData) {
242
251
  this.logger.debug(
243
252
  "router",
@@ -247,15 +256,24 @@ class BaseApp {
247
256
  this.logger.debug("router", "RouteData");
248
257
  this.logger.debug("router", routeData);
249
258
  }
259
+ const resolvedRenderOptions = {
260
+ addCookieHeader,
261
+ clientAddress,
262
+ prerenderedErrorPageFetch,
263
+ locals,
264
+ routeData
265
+ };
250
266
  if (locals) {
251
267
  if (typeof locals !== "object") {
252
268
  const error = new AstroError(AstroErrorData.LocalsNotAnObject);
253
269
  this.logger.error(null, error.stack);
254
270
  return this.renderError(request, {
271
+ ...resolvedRenderOptions,
272
+ // If locals are invalid, we don't want to include them when
273
+ // rendering the error page
274
+ locals: void 0,
255
275
  status: 500,
256
- error,
257
- clientAddress,
258
- prerenderedErrorPageFetch
276
+ error
259
277
  });
260
278
  }
261
279
  }
@@ -280,16 +298,15 @@ class BaseApp {
280
298
  this.logger.debug("router", "Astro hasn't found routes that match " + request.url);
281
299
  this.logger.debug("router", "Here's the available routes:\n", this.manifestData);
282
300
  return this.renderError(request, {
283
- locals,
284
- status: 404,
285
- clientAddress,
286
- prerenderedErrorPageFetch
301
+ ...resolvedRenderOptions,
302
+ status: 404
287
303
  });
288
304
  }
289
305
  const pathname = this.getPathnameFromRequest(request);
290
306
  const defaultStatus = this.getDefaultStatusCode(routeData, pathname);
291
307
  let response;
292
308
  let session;
309
+ let cache;
293
310
  try {
294
311
  const componentInstance = await this.pipeline.getComponentByRoute(routeData);
295
312
  const renderContext = await this.createRenderContext({
@@ -302,7 +319,30 @@ class BaseApp {
302
319
  clientAddress
303
320
  });
304
321
  session = renderContext.session;
305
- response = await renderContext.render(componentInstance);
322
+ cache = renderContext.cache;
323
+ if (this.pipeline.cacheProvider) {
324
+ const cacheProvider = await this.pipeline.getCacheProvider();
325
+ if (cacheProvider?.onRequest) {
326
+ response = await cacheProvider.onRequest(
327
+ {
328
+ request,
329
+ url: new URL(request.url)
330
+ },
331
+ async () => {
332
+ const res = await renderContext.render(componentInstance);
333
+ applyCacheHeaders(cache, res);
334
+ return res;
335
+ }
336
+ );
337
+ response.headers.delete("CDN-Cache-Control");
338
+ response.headers.delete("Cache-Tag");
339
+ } else {
340
+ response = await renderContext.render(componentInstance);
341
+ applyCacheHeaders(cache, response);
342
+ }
343
+ } else {
344
+ response = await renderContext.render(componentInstance);
345
+ }
306
346
  const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
307
347
  this.logThisRequest({
308
348
  pathname,
@@ -314,11 +354,9 @@ class BaseApp {
314
354
  } catch (err) {
315
355
  this.logger.error(null, err.stack || err.message || String(err));
316
356
  return this.renderError(request, {
317
- locals,
357
+ ...resolvedRenderOptions,
318
358
  status: 500,
319
- error: err,
320
- clientAddress,
321
- prerenderedErrorPageFetch
359
+ error: err
322
360
  });
323
361
  } finally {
324
362
  await session?.[PERSIST_SYMBOL]();
@@ -327,26 +365,34 @@ class BaseApp {
327
365
  // but uses the current route to handle the 404
328
366
  response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
329
367
  return this.renderError(request, {
330
- locals,
368
+ ...resolvedRenderOptions,
331
369
  response,
332
370
  status: response.status,
333
371
  // We don't have an error to report here. Passing null means we pass nothing intentionally
334
372
  // while undefined means there's no error
335
- error: response.status === 500 ? null : void 0,
336
- clientAddress,
337
- prerenderedErrorPageFetch
373
+ error: response.status === 500 ? null : void 0
338
374
  });
339
375
  }
340
- if (response.headers.has(REROUTE_DIRECTIVE_HEADER)) {
341
- response.headers.delete(REROUTE_DIRECTIVE_HEADER);
376
+ this.#prepareResponse(response, { addCookieHeader });
377
+ return response;
378
+ }
379
+ #prepareResponse(response, { addCookieHeader }) {
380
+ for (const headerName of [
381
+ REROUTE_DIRECTIVE_HEADER,
382
+ REWRITE_DIRECTIVE_HEADER_KEY,
383
+ NOOP_MIDDLEWARE_HEADER,
384
+ ROUTE_TYPE_HEADER
385
+ ]) {
386
+ if (response.headers.has(headerName)) {
387
+ response.headers.delete(headerName);
388
+ }
342
389
  }
343
390
  if (addCookieHeader) {
344
- for (const setCookieHeaderValue of BaseApp.getSetCookieFromResponse(response)) {
391
+ for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) {
345
392
  response.headers.append("set-cookie", setCookieHeaderValue);
346
393
  }
347
394
  }
348
395
  Reflect.set(response, responseSentSymbol, true);
349
- return response;
350
396
  }
351
397
  setCookieHeaders(response) {
352
398
  return getSetCookiesFromResponse(response);
@@ -368,13 +414,11 @@ class BaseApp {
368
414
  * This also handles pre-rendered /404 or /500 routes
369
415
  */
370
416
  async renderError(request, {
371
- locals,
372
417
  status,
373
418
  response: originalResponse,
374
419
  skipMiddleware = false,
375
420
  error,
376
- clientAddress,
377
- prerenderedErrorPageFetch
421
+ ...resolvedRenderOptions
378
422
  }) {
379
423
  const errorRoutePath = `/${status}${this.manifest.trailingSlash === "always" ? "/" : ""}`;
380
424
  const errorRouteData = matchRoute(errorRoutePath, this.manifestData);
@@ -383,17 +427,21 @@ class BaseApp {
383
427
  if (errorRouteData.prerender) {
384
428
  const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? ".html" : "";
385
429
  const statusURL = new URL(`${this.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`, url);
386
- if (statusURL.toString() !== request.url && prerenderedErrorPageFetch) {
387
- const response2 = await prerenderedErrorPageFetch(statusURL.toString());
430
+ if (statusURL.toString() !== request.url && resolvedRenderOptions.prerenderedErrorPageFetch) {
431
+ const response2 = await resolvedRenderOptions.prerenderedErrorPageFetch(
432
+ statusURL.toString()
433
+ );
388
434
  const override = { status, removeContentEncodingHeaders: true };
389
- return this.mergeResponses(response2, originalResponse, override);
435
+ const newResponse = this.mergeResponses(response2, originalResponse, override);
436
+ this.#prepareResponse(newResponse, resolvedRenderOptions);
437
+ return newResponse;
390
438
  }
391
439
  }
392
440
  const mod = await this.pipeline.getComponentByRoute(errorRouteData);
393
441
  let session;
394
442
  try {
395
443
  const renderContext = await this.createRenderContext({
396
- locals,
444
+ locals: resolvedRenderOptions.locals,
397
445
  pipeline: this.pipeline,
398
446
  skipMiddleware,
399
447
  pathname: this.getPathnameFromRequest(request),
@@ -401,20 +449,20 @@ class BaseApp {
401
449
  routeData: errorRouteData,
402
450
  status,
403
451
  props: { error },
404
- clientAddress
452
+ clientAddress: resolvedRenderOptions.clientAddress
405
453
  });
406
454
  session = renderContext.session;
407
455
  const response2 = await renderContext.render(mod);
408
- return this.mergeResponses(response2, originalResponse);
456
+ const newResponse = this.mergeResponses(response2, originalResponse);
457
+ this.#prepareResponse(newResponse, resolvedRenderOptions);
458
+ return newResponse;
409
459
  } catch {
410
460
  if (skipMiddleware === false) {
411
461
  return this.renderError(request, {
412
- locals,
462
+ ...resolvedRenderOptions,
413
463
  status,
414
464
  response: originalResponse,
415
- skipMiddleware: true,
416
- clientAddress,
417
- prerenderedErrorPageFetch
465
+ skipMiddleware: true
418
466
  });
419
467
  }
420
468
  } finally {
@@ -422,7 +470,7 @@ class BaseApp {
422
470
  }
423
471
  }
424
472
  const response = this.mergeResponses(new Response(null, { status }), originalResponse);
425
- Reflect.set(response, responseSentSymbol, true);
473
+ this.#prepareResponse(response, resolvedRenderOptions);
426
474
  return response;
427
475
  }
428
476
  mergeResponses(newResponse, originalResponse, override) {
@@ -447,15 +495,18 @@ class BaseApp {
447
495
  originalResponse.headers.delete("Content-type");
448
496
  } catch {
449
497
  }
450
- const mergedHeaders = new Map([
451
- ...Array.from(newResponseHeaders),
452
- ...Array.from(originalResponse.headers)
453
- ]);
454
498
  const newHeaders = new Headers();
455
- for (const [name, value] of mergedHeaders) {
456
- newHeaders.set(name, value);
499
+ const seen = /* @__PURE__ */ new Set();
500
+ for (const [name, value] of originalResponse.headers) {
501
+ newHeaders.append(name, value);
502
+ seen.add(name.toLowerCase());
457
503
  }
458
- return new Response(newResponse.body, {
504
+ for (const [name, value] of newResponseHeaders) {
505
+ if (!seen.has(name.toLowerCase())) {
506
+ newHeaders.append(name, value);
507
+ }
508
+ }
509
+ const mergedResponse = new Response(newResponse.body, {
459
510
  status,
460
511
  statusText: status === 200 ? newResponse.statusText : originalResponse.statusText,
461
512
  // If you're looking at here for possible bugs, it means that it's not a bug.
@@ -465,6 +516,19 @@ class BaseApp {
465
516
  // Although, we don't want it to replace the content-type, because the error page must return `text/html`
466
517
  headers: newHeaders
467
518
  });
519
+ const originalCookies = getCookiesFromResponse(originalResponse);
520
+ const newCookies = getCookiesFromResponse(newResponse);
521
+ if (originalCookies) {
522
+ if (newCookies) {
523
+ for (const cookieValue of AstroCookies.consume(newCookies)) {
524
+ originalResponse.headers.append("set-cookie", cookieValue);
525
+ }
526
+ }
527
+ attachCookiesToResponse(mergedResponse, originalCookies);
528
+ } else if (newCookies) {
529
+ attachCookiesToResponse(mergedResponse, newCookies);
530
+ }
531
+ return mergedResponse;
468
532
  }
469
533
  getDefaultStatusCode(routeData, pathname) {
470
534
  if (!routeData.pattern.test(pathname)) {
@@ -19,6 +19,6 @@ export declare class DevApp extends BaseApp<NonRunnablePipeline> {
19
19
  match(request: Request): RouteData | undefined;
20
20
  devMatch(pathname: string): Promise<DevMatch | undefined>;
21
21
  createRenderContext(payload: CreateRenderContext): Promise<RenderContext>;
22
- renderError(request: Request, { locals, skipMiddleware, error, clientAddress, status }: RenderErrorOptions): Promise<Response>;
22
+ renderError(request: Request, { skipMiddleware, error, status, response: _response, ...resolvedRenderOptions }: RenderErrorOptions): Promise<Response>;
23
23
  logRequest({ pathname, method, statusCode, isRewrite, reqTime }: LogRequestPayload): void;
24
24
  }
@@ -56,7 +56,13 @@ class DevApp extends BaseApp {
56
56
  pathname: this.resolvedPathname ?? payload.pathname
57
57
  });
58
58
  }
59
- async renderError(request, { locals, skipMiddleware = false, error, clientAddress, status }) {
59
+ async renderError(request, {
60
+ skipMiddleware = false,
61
+ error,
62
+ status,
63
+ response: _response,
64
+ ...resolvedRenderOptions
65
+ }) {
60
66
  if (isAstroError(error) && [MiddlewareNoDataOrNextCalled.name, MiddlewareNotAResponse.name].includes(error.name)) {
61
67
  throw error;
62
68
  }
@@ -64,13 +70,13 @@ class DevApp extends BaseApp {
64
70
  try {
65
71
  const preloadedComponent = await this.pipeline.getComponentByRoute(routeData);
66
72
  const renderContext = await this.createRenderContext({
67
- locals,
73
+ locals: resolvedRenderOptions.locals,
68
74
  pipeline: this.pipeline,
69
- pathname: await this.getPathnameFromRequest(request),
75
+ pathname: this.getPathnameFromRequest(request),
70
76
  skipMiddleware,
71
77
  request,
72
78
  routeData,
73
- clientAddress,
79
+ clientAddress: resolvedRenderOptions.clientAddress,
74
80
  status,
75
81
  shouldInjectCspMetaTags: false
76
82
  });
@@ -83,8 +89,7 @@ class DevApp extends BaseApp {
83
89
  } catch (_err) {
84
90
  if (skipMiddleware === false) {
85
91
  return this.renderError(request, {
86
- clientAddress: void 0,
87
- prerenderedErrorPageFetch: fetch,
92
+ ...resolvedRenderOptions,
88
93
  status: 500,
89
94
  skipMiddleware: true,
90
95
  error: _err
@@ -82,7 +82,8 @@ function createRequest(req, {
82
82
  onSocketClose();
83
83
  }
84
84
  }
85
- const forwardedClientIp = getFirstForwardedValue(req.headers["x-forwarded-for"]);
85
+ const hostValidated = validated.host !== void 0 || validatedHostname !== void 0;
86
+ const forwardedClientIp = hostValidated ? getFirstForwardedValue(req.headers["x-forwarded-for"]) : void 0;
86
87
  const clientIp = forwardedClientIp || req.socket?.remoteAddress;
87
88
  if (clientIp) {
88
89
  Reflect.set(request, clientAddressSymbol, clientIp);
@@ -7,6 +7,7 @@ import type { SinglePageBuiltModule } from '../build/types.js';
7
7
  import type { CspDirective } from '../csp/config.js';
8
8
  import type { LoggerLevel } from '../logger/core.js';
9
9
  import type { RoutingStrategies } from './common.js';
10
+ import type { CacheProviderFactory, SSRManifestCache } from '../cache/types.js';
10
11
  import type { BaseSessionConfig, SessionDriverFactory } from '../session/types.js';
11
12
  import type { DevToolbarPlacement } from '../../types/public/toolbar.js';
12
13
  import type { MiddlewareMode } from '../../types/public/integrations.js';
@@ -99,10 +100,14 @@ export type SSRManifest = {
99
100
  sessionDriver?: () => Promise<{
100
101
  default: SessionDriverFactory | null;
101
102
  }>;
103
+ cacheProvider?: () => Promise<{
104
+ default: CacheProviderFactory | null;
105
+ }>;
102
106
  checkOrigin: boolean;
103
107
  allowedDomains?: Partial<RemotePattern>[];
104
108
  actionBodySizeLimit: number;
105
109
  sessionConfig?: SSRManifestSession;
110
+ cacheConfig?: SSRManifestCache;
106
111
  cacheDir: URL;
107
112
  srcDir: URL;
108
113
  outDir: URL;