astro 6.2.2 → 6.3.1

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 (113) hide show
  1. package/dist/actions/handler.d.ts +32 -0
  2. package/dist/actions/handler.js +45 -0
  3. package/dist/actions/runtime/server.js +1 -1
  4. package/dist/assets/build/generate.js +1 -1
  5. package/dist/assets/build/remote.d.ts +3 -2
  6. package/dist/assets/build/remote.js +16 -9
  7. package/dist/assets/endpoint/generic.js +8 -20
  8. package/dist/assets/endpoint/loadImage.d.ts +11 -0
  9. package/dist/assets/endpoint/loadImage.js +19 -0
  10. package/dist/assets/endpoint/shared.js +7 -2
  11. package/dist/assets/index.d.ts +1 -0
  12. package/dist/assets/index.js +2 -0
  13. package/dist/assets/services/sharp.js +7 -0
  14. package/dist/assets/utils/index.d.ts +1 -0
  15. package/dist/assets/utils/index.js +2 -0
  16. package/dist/assets/utils/redirectValidation.d.ts +48 -0
  17. package/dist/assets/utils/redirectValidation.js +48 -0
  18. package/dist/assets/utils/remoteProbe.js +25 -2
  19. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  20. package/dist/container/index.js +18 -14
  21. package/dist/content/content-layer.js +3 -4
  22. package/dist/content/server-listeners.js +0 -4
  23. package/dist/content/vite-plugin-content-virtual-mod.js +9 -1
  24. package/dist/core/app/base.d.ts +33 -15
  25. package/dist/core/app/base.js +120 -324
  26. package/dist/core/app/dev/app.d.ts +3 -2
  27. package/dist/core/app/dev/app.js +4 -60
  28. package/dist/core/app/entrypoints/virtual/dev.js +2 -0
  29. package/dist/core/app/entrypoints/virtual/prod.js +4 -1
  30. package/dist/core/app/prepare-response.d.ts +11 -0
  31. package/dist/core/app/prepare-response.js +18 -0
  32. package/dist/core/app/render-options.d.ts +11 -0
  33. package/dist/core/app/render-options.js +11 -0
  34. package/dist/core/base-pipeline.d.ts +38 -1
  35. package/dist/core/base-pipeline.js +50 -7
  36. package/dist/core/build/app.d.ts +3 -4
  37. package/dist/core/build/app.js +3 -17
  38. package/dist/core/cache/handler.d.ts +29 -0
  39. package/dist/core/cache/handler.js +81 -0
  40. package/dist/core/config/schemas/base.d.ts +4 -0
  41. package/dist/core/config/schemas/base.js +4 -0
  42. package/dist/core/config/schemas/relative.d.ts +6 -0
  43. package/dist/core/constants.d.ts +27 -1
  44. package/dist/core/constants.js +14 -1
  45. package/dist/core/cookies/cookies.d.ts +7 -2
  46. package/dist/core/cookies/cookies.js +11 -4
  47. package/dist/core/cookies/response.d.ts +1 -1
  48. package/dist/core/cookies/response.js +1 -2
  49. package/dist/core/create-vite.js +15 -0
  50. package/dist/core/csp/runtime.js +6 -4
  51. package/dist/core/dev/dev.js +1 -1
  52. package/dist/core/errors/build-handler.d.ts +17 -0
  53. package/dist/core/errors/build-handler.js +22 -0
  54. package/dist/core/errors/default-handler.d.ts +14 -0
  55. package/dist/core/errors/default-handler.js +144 -0
  56. package/dist/core/errors/dev-handler.d.ts +21 -0
  57. package/dist/core/errors/dev-handler.js +82 -0
  58. package/dist/core/errors/handler.d.ts +9 -0
  59. package/dist/core/errors/handler.js +0 -0
  60. package/dist/core/fetch/default-handler.d.ts +17 -0
  61. package/dist/core/fetch/default-handler.js +45 -0
  62. package/dist/core/fetch/fetch-state.d.ts +244 -0
  63. package/dist/core/fetch/fetch-state.js +779 -0
  64. package/dist/core/fetch/index.d.ts +61 -0
  65. package/dist/core/fetch/index.js +121 -0
  66. package/dist/core/fetch/types.d.ts +6 -0
  67. package/dist/core/fetch/types.js +0 -0
  68. package/dist/core/fetch/vite-plugin.d.ts +5 -0
  69. package/dist/core/fetch/vite-plugin.js +69 -0
  70. package/dist/core/hono/index.d.ts +21 -0
  71. package/dist/core/hono/index.js +98 -0
  72. package/dist/core/i18n/handler.d.ts +18 -0
  73. package/dist/core/i18n/handler.js +119 -0
  74. package/dist/core/logger/core.d.ts +8 -0
  75. package/dist/core/logger/core.js +16 -0
  76. package/dist/core/messages/runtime.js +1 -1
  77. package/dist/core/middleware/astro-middleware.d.ts +27 -0
  78. package/dist/core/middleware/astro-middleware.js +53 -0
  79. package/dist/core/pages/handler.d.ts +20 -0
  80. package/dist/core/pages/handler.js +74 -0
  81. package/dist/core/preview/static-preview-server.js +3 -1
  82. package/dist/core/redirects/render.d.ts +2 -2
  83. package/dist/core/redirects/render.js +7 -8
  84. package/dist/core/rewrites/handler.d.ts +37 -0
  85. package/dist/core/rewrites/handler.js +67 -0
  86. package/dist/core/routing/3xx.js +8 -4
  87. package/dist/core/routing/handler.d.ts +17 -0
  88. package/dist/core/routing/handler.js +172 -0
  89. package/dist/core/routing/match.d.ts +0 -7
  90. package/dist/core/routing/match.js +0 -5
  91. package/dist/core/routing/trailing-slash-handler.d.ts +18 -0
  92. package/dist/core/routing/trailing-slash-handler.js +67 -0
  93. package/dist/core/session/drivers.d.ts +1 -1
  94. package/dist/core/session/handler.d.ts +11 -0
  95. package/dist/core/session/handler.js +33 -0
  96. package/dist/core/util/normalized-url.d.ts +10 -0
  97. package/dist/core/util/normalized-url.js +21 -0
  98. package/dist/i18n/middleware.d.ts +10 -0
  99. package/dist/i18n/middleware.js +4 -88
  100. package/dist/i18n/utils.js +2 -2
  101. package/dist/runtime/server/astro-island.js +57 -20
  102. package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
  103. package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
  104. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  105. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  106. package/dist/runtime/server/render/server-islands.js +2 -1
  107. package/dist/types/public/config.d.ts +46 -12
  108. package/dist/types/public/internal.d.ts +1 -1
  109. package/dist/vite-plugin-app/app.d.ts +4 -5
  110. package/dist/vite-plugin-app/app.js +20 -65
  111. package/package.json +11 -7
  112. package/dist/core/render-context.d.ts +0 -77
  113. package/dist/core/render-context.js +0 -826
@@ -0,0 +1,74 @@
1
+ import { renderEndpoint } from "../../runtime/server/endpoint.js";
2
+ import { renderPage } from "../../runtime/server/index.js";
3
+ import {
4
+ ASTRO_ERROR_HEADER,
5
+ REROUTE_DIRECTIVE_HEADER,
6
+ REWRITE_DIRECTIVE_HEADER_KEY,
7
+ REWRITE_DIRECTIVE_HEADER_VALUE,
8
+ ROUTE_TYPE_HEADER
9
+ } from "../constants.js";
10
+ import { getCookiesFromResponse } from "../cookies/response.js";
11
+ const EMPTY_SLOTS = Object.freeze({});
12
+ class PagesHandler {
13
+ #pipeline;
14
+ constructor(pipeline) {
15
+ this.#pipeline = pipeline;
16
+ }
17
+ async handle(state, ctx) {
18
+ const pipeline = this.#pipeline;
19
+ const { logger, streaming } = pipeline;
20
+ let response;
21
+ const componentInstance = await state.loadComponentInstance();
22
+ switch (state.routeData.type) {
23
+ case "endpoint": {
24
+ response = await renderEndpoint(
25
+ componentInstance,
26
+ ctx,
27
+ state.routeData.prerender,
28
+ logger
29
+ );
30
+ break;
31
+ }
32
+ case "page": {
33
+ const props = await state.getProps();
34
+ const actionApiContext = state.getActionAPIContext();
35
+ const result = await state.createResult(componentInstance, actionApiContext);
36
+ try {
37
+ response = await renderPage(
38
+ result,
39
+ componentInstance?.default,
40
+ props,
41
+ state.slots ?? EMPTY_SLOTS,
42
+ streaming,
43
+ state.routeData
44
+ );
45
+ } catch (e) {
46
+ result.cancelled = true;
47
+ throw e;
48
+ }
49
+ response.headers.set(ROUTE_TYPE_HEADER, "page");
50
+ if (state.routeData.route === "/404" || state.routeData.route === "/500") {
51
+ response.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
52
+ }
53
+ if (state.isRewriting) {
54
+ response.headers.set(REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE);
55
+ }
56
+ break;
57
+ }
58
+ case "redirect": {
59
+ return new Response(null, { status: 404, headers: { [ASTRO_ERROR_HEADER]: "true" } });
60
+ }
61
+ case "fallback": {
62
+ return new Response(null, { status: 500, headers: { [ROUTE_TYPE_HEADER]: "fallback" } });
63
+ }
64
+ }
65
+ const responseCookies = getCookiesFromResponse(response);
66
+ if (responseCookies) {
67
+ state.cookies.merge(responseCookies);
68
+ }
69
+ return response;
70
+ }
71
+ }
72
+ export {
73
+ PagesHandler
74
+ };
@@ -67,9 +67,11 @@ async function createStaticPreviewServer(settings, logger) {
67
67
  previewServer.httpServer.addListener("error", reject);
68
68
  });
69
69
  }
70
+ const address = previewServer.httpServer.address();
71
+ const actualPort = address && typeof address === "object" ? address.port : settings.config.server.port;
70
72
  return {
71
73
  host: getResolvedHostForHttpServer(settings.config.server.host),
72
- port: settings.config.server.port,
74
+ port: actualPort,
73
75
  closed,
74
76
  server: previewServer.httpServer,
75
77
  stop: previewServer.close.bind(previewServer)
@@ -1,7 +1,7 @@
1
1
  import type { Params } from '../../types/public/common.js';
2
2
  import type { RedirectConfig } from '../../types/public/index.js';
3
3
  import type { RouteData } from '../../types/public/internal.js';
4
- import type { RenderContext } from '../render-context.js';
4
+ import type { FetchState } from '../fetch/fetch-state.js';
5
5
  export declare function redirectIsExternal(redirect: RedirectConfig): boolean;
6
6
  /**
7
7
  * Computes the HTTP status code for a redirect response.
@@ -18,4 +18,4 @@ export declare function computeRedirectStatus(method: string, redirect: Redirect
18
18
  * are substituted manually into the string redirect target.
19
19
  */
20
20
  export declare function resolveRedirectTarget(params: Params, redirect: RedirectConfig | undefined, redirectRoute: RouteData | undefined, trailingSlash: 'always' | 'never' | 'ignore'): string;
21
- export declare function renderRedirect(renderContext: RenderContext): Promise<Response>;
21
+ export declare function renderRedirect(state: FetchState): Promise<Response>;
@@ -1,3 +1,4 @@
1
+ import { PipelineFeatures } from "../base-pipeline.js";
1
2
  import { getRouteGenerator } from "../routing/generator.js";
2
3
  function isExternalURL(url) {
3
4
  return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//");
@@ -32,20 +33,18 @@ function resolveRedirectTarget(params, redirect, redirectRoute, trailingSlash) {
32
33
  }
33
34
  return redirect.destination;
34
35
  }
35
- async function renderRedirect(renderContext) {
36
- const {
37
- request: { method },
38
- routeData
39
- } = renderContext;
36
+ async function renderRedirect(state) {
37
+ state.pipeline.usedFeatures |= PipelineFeatures.redirects;
38
+ const routeData = state.routeData;
40
39
  const { redirect, redirectRoute } = routeData;
41
- const status = computeRedirectStatus(method, redirect, redirectRoute);
40
+ const status = computeRedirectStatus(state.request.method, redirect, redirectRoute);
42
41
  const headers = {
43
42
  location: encodeURI(
44
43
  resolveRedirectTarget(
45
- renderContext.params,
44
+ state.params,
46
45
  redirect,
47
46
  redirectRoute,
48
- renderContext.pipeline.manifest.trailingSlash
47
+ state.pipeline.manifest.trailingSlash
49
48
  )
50
49
  )
51
50
  };
@@ -0,0 +1,37 @@
1
+ import type { ComponentInstance } from '../../types/astro.js';
2
+ import type { FetchState } from '../fetch/fetch-state.js';
3
+ import type { RewritePayload } from '../../types/public/common.js';
4
+ import type { RouteData } from '../../types/public/internal.js';
5
+ interface TryRewriteResult {
6
+ routeData: RouteData;
7
+ componentInstance: ComponentInstance;
8
+ newUrl: URL;
9
+ pathname: string;
10
+ }
11
+ /**
12
+ * Validates and applies a rewrite target to the given `FetchState`.
13
+ *
14
+ * - Validates that SSR→prerender rewrites are not attempted (except
15
+ * for i18n fallback routes).
16
+ * - Mutates `state` to reflect the new route: request, URL, cookies,
17
+ * params, pathname, component instance, etc.
18
+ * - Invalidates cached API contexts so they're re-derived from the
19
+ * new route.
20
+ *
21
+ * Called by both `Rewrites.execute()` (user-triggered `Astro.rewrite`)
22
+ * and `AstroMiddleware` (middleware `next(payload)`).
23
+ */
24
+ export declare function applyRewriteToState(state: FetchState, payload: RewritePayload, { routeData, componentInstance, newUrl, pathname }: TryRewriteResult, { mergeCookies }?: {
25
+ mergeCookies?: boolean;
26
+ }): void;
27
+ /**
28
+ * Executes a user-triggered rewrite (`Astro.rewrite(...)` /
29
+ * `ctx.rewrite(...)`) against a `FetchState`. Resolves the rewrite
30
+ * target via `pipeline.tryRewrite`, validates it, mutates the
31
+ * `FetchState` to reflect the new route, and re-runs the middleware
32
+ * and page dispatch to produce the new response.
33
+ */
34
+ export declare class Rewrites {
35
+ execute(state: FetchState, payload: RewritePayload): Promise<Response>;
36
+ }
37
+ export {};
@@ -0,0 +1,67 @@
1
+ import { AstroCookies } from "../cookies/index.js";
2
+ import { ForbiddenRewrite } from "../errors/errors-data.js";
3
+ import { AstroError } from "../errors/errors.js";
4
+ import { AstroMiddleware } from "../middleware/astro-middleware.js";
5
+ import { PagesHandler } from "../pages/handler.js";
6
+ import { getParams } from "../render/index.js";
7
+ import { copyRequest, setOriginPathname } from "../routing/rewrite.js";
8
+ import { createNormalizedUrl } from "../util/normalized-url.js";
9
+ function applyRewriteToState(state, payload, { routeData, componentInstance, newUrl, pathname }, { mergeCookies = false } = {}) {
10
+ const pipeline = state.pipeline;
11
+ const oldPathname = state.pathname;
12
+ const isI18nFallback = routeData.fallbackRoutes && routeData.fallbackRoutes.length > 0;
13
+ if (pipeline.manifest.serverLike && !state.routeData.prerender && routeData.prerender && !isI18nFallback) {
14
+ throw new AstroError({
15
+ ...ForbiddenRewrite,
16
+ message: ForbiddenRewrite.message(state.pathname, pathname, routeData.component),
17
+ hint: ForbiddenRewrite.hint(routeData.component)
18
+ });
19
+ }
20
+ state.routeData = routeData;
21
+ state.componentInstance = componentInstance;
22
+ if (payload instanceof Request) {
23
+ state.request = payload;
24
+ } else {
25
+ state.request = copyRequest(
26
+ newUrl,
27
+ state.request,
28
+ routeData.prerender,
29
+ pipeline.logger,
30
+ state.routeData.route
31
+ );
32
+ }
33
+ state.url = createNormalizedUrl(state.request.url);
34
+ if (mergeCookies) {
35
+ const newCookies = new AstroCookies(state.request);
36
+ if (state.cookies) {
37
+ newCookies.merge(state.cookies);
38
+ }
39
+ state.cookies = newCookies;
40
+ }
41
+ state.params = getParams(routeData, pathname);
42
+ state.pathname = pathname;
43
+ state.isRewriting = true;
44
+ state.status = 200;
45
+ setOriginPathname(
46
+ state.request,
47
+ oldPathname,
48
+ pipeline.manifest.trailingSlash,
49
+ pipeline.manifest.buildFormat
50
+ );
51
+ state.invalidateContexts();
52
+ }
53
+ class Rewrites {
54
+ async execute(state, payload) {
55
+ const pipeline = state.pipeline;
56
+ pipeline.logger.debug("router", "Calling rewrite: ", payload);
57
+ const result = await pipeline.tryRewrite(payload, state.request);
58
+ applyRewriteToState(state, payload, result, { mergeCookies: true });
59
+ const middleware = new AstroMiddleware(pipeline);
60
+ const pagesHandler = new PagesHandler(pipeline);
61
+ return middleware.handle(state, pagesHandler.handle.bind(pagesHandler));
62
+ }
63
+ }
64
+ export {
65
+ Rewrites,
66
+ applyRewriteToState
67
+ };
@@ -1,3 +1,4 @@
1
+ import { escape } from "html-escaper";
1
2
  function redirectTemplate({
2
3
  status,
3
4
  absoluteLocation,
@@ -5,13 +6,16 @@ function redirectTemplate({
5
6
  from
6
7
  }) {
7
8
  const delay = status === 302 ? 2 : 0;
9
+ const rel = escape(String(relativeLocation));
10
+ const abs = escape(String(absoluteLocation));
11
+ const fromHtml = from ? `from <code>${escape(from)}</code> ` : "";
8
12
  return `<!doctype html>
9
- <title>Redirecting to: ${relativeLocation}</title>
10
- <meta http-equiv="refresh" content="${delay};url=${relativeLocation}">
13
+ <title>Redirecting to: ${rel}</title>
14
+ <meta http-equiv="refresh" content="${delay};url=${rel}">
11
15
  <meta name="robots" content="noindex">
12
- <link rel="canonical" href="${absoluteLocation}">
16
+ <link rel="canonical" href="${abs}">
13
17
  <body>
14
- <a href="${relativeLocation}">Redirecting ${from ? `from <code>${from}</code> ` : ""}to <code>${relativeLocation}</code></a>
18
+ <a href="${rel}">Redirecting ${fromHtml}to <code>${rel}</code></a>
15
19
  </body>`;
16
20
  }
17
21
  export {
@@ -0,0 +1,17 @@
1
+ import type { FetchState } from '../fetch/fetch-state.js';
2
+ import type { BaseApp } from '../app/base.js';
3
+ import { type Pipeline } from '../base-pipeline.js';
4
+ export declare class AstroHandler {
5
+ #private;
6
+ constructor(app: BaseApp<Pipeline>);
7
+ handle(state: FetchState): Promise<Response>;
8
+ /**
9
+ * Renders a response for the given `FetchState`. Assumes
10
+ * trailing-slash redirects and routeData resolution have already run.
11
+ *
12
+ * User-triggered rewrites (`Astro.rewrite` / `ctx.rewrite`) go through
13
+ * `Rewrites.execute` on the current `FetchState` — they mutate the
14
+ * existing state in place and re-run middleware + page dispatch.
15
+ */
16
+ render(state: FetchState): Promise<Response>;
17
+ }
@@ -0,0 +1,172 @@
1
+ import { ActionHandler } from "../../actions/handler.js";
2
+ import {
3
+ REROUTABLE_STATUS_CODES,
4
+ REROUTE_DIRECTIVE_HEADER,
5
+ REWRITE_DIRECTIVE_HEADER_KEY
6
+ } from "../constants.js";
7
+ import { TrailingSlashHandler } from "./trailing-slash-handler.js";
8
+ import { CacheHandler, provideCache } from "../cache/handler.js";
9
+ import { I18n } from "../i18n/handler.js";
10
+ import { AstroMiddleware } from "../middleware/astro-middleware.js";
11
+ import { PagesHandler } from "../pages/handler.js";
12
+ import { renderRedirect } from "../redirects/render.js";
13
+ import { provideSession } from "../session/handler.js";
14
+ import { prepareResponse } from "../app/prepare-response.js";
15
+ import { PipelineFeatures } from "../base-pipeline.js";
16
+ class AstroHandler {
17
+ #app;
18
+ #trailingSlashHandler;
19
+ #actionHandler;
20
+ #astroMiddleware;
21
+ #pagesHandler;
22
+ #cacheHandler;
23
+ /** Bound callback for the middleware chain — created once, reused per request. */
24
+ #renderRouteCallback;
25
+ /**
26
+ * i18n post-processor. Only set when the app has i18n configured and
27
+ * the strategy is not `manual` — for the manual strategy users wire
28
+ * `astro:i18n.middleware(...)` into their own `onRequest`.
29
+ */
30
+ #i18n;
31
+ /** Whether sessions are configured on the manifest. */
32
+ #hasSession;
33
+ constructor(app) {
34
+ this.#app = app;
35
+ this.#trailingSlashHandler = new TrailingSlashHandler(app);
36
+ this.#actionHandler = new ActionHandler();
37
+ this.#astroMiddleware = new AstroMiddleware(app.pipeline);
38
+ this.#pagesHandler = new PagesHandler(app.pipeline);
39
+ this.#cacheHandler = new CacheHandler(app);
40
+ this.#renderRouteCallback = this.#actionsAndPages.bind(this);
41
+ this.#hasSession = !!app.manifest.sessionConfig;
42
+ const i18n = app.manifest.i18n;
43
+ if (i18n && i18n.strategy !== "manual") {
44
+ this.#i18n = new I18n(
45
+ i18n,
46
+ app.manifest.base,
47
+ app.manifest.trailingSlash,
48
+ app.manifest.buildFormat
49
+ );
50
+ }
51
+ }
52
+ /**
53
+ * Runs actions then pages — the callback at the bottom of the
54
+ * middleware chain. Bound once in the constructor to avoid
55
+ * per-request closure allocation.
56
+ */
57
+ #actionsAndPages(state, ctx) {
58
+ if (!state.skipMiddleware) {
59
+ const actionResult = this.#actionHandler.handle(ctx, state);
60
+ if (actionResult) {
61
+ return actionResult.then((response) => response ?? this.#pagesHandler.handle(state, ctx));
62
+ }
63
+ }
64
+ return this.#pagesHandler.handle(state, ctx);
65
+ }
66
+ async handle(state) {
67
+ const trailingSlashRedirect = this.#trailingSlashHandler.handle(state);
68
+ if (trailingSlashRedirect) {
69
+ return trailingSlashRedirect;
70
+ }
71
+ if (!state.routeData) {
72
+ return this.#app.renderError(state.request, {
73
+ ...state.renderOptions,
74
+ status: 404,
75
+ pathname: state.pathname
76
+ });
77
+ }
78
+ return this.render(state);
79
+ }
80
+ /**
81
+ * Renders a response for the given `FetchState`. Assumes
82
+ * trailing-slash redirects and routeData resolution have already run.
83
+ *
84
+ * User-triggered rewrites (`Astro.rewrite` / `ctx.rewrite`) go through
85
+ * `Rewrites.execute` on the current `FetchState` — they mutate the
86
+ * existing state in place and re-run middleware + page dispatch.
87
+ */
88
+ async render(state) {
89
+ const routeData = state.routeData;
90
+ const pathname = state.pathname;
91
+ const request = state.request;
92
+ const { addCookieHeader } = state.renderOptions;
93
+ const defaultStatus = this.#app.getDefaultStatusCode(routeData, pathname);
94
+ state.status = defaultStatus;
95
+ let response;
96
+ try {
97
+ if (this.#hasSession || this.#app.pipeline.cacheConfig) {
98
+ const sessionP = this.#hasSession ? provideSession(state) : void 0;
99
+ const cacheP = this.#app.pipeline.cacheConfig ? provideCache(state) : void 0;
100
+ if (sessionP || cacheP) await Promise.all([sessionP, cacheP]);
101
+ }
102
+ state.pipeline.usedFeatures |= PipelineFeatures.sessions;
103
+ if (routeData.type === "redirect") {
104
+ const redirectResponse = await renderRedirect(state);
105
+ this.#app.logThisRequest({
106
+ pathname,
107
+ method: request.method,
108
+ statusCode: redirectResponse.status,
109
+ isRewrite: false,
110
+ timeStart: state.timeStart
111
+ });
112
+ prepareResponse(redirectResponse, { addCookieHeader });
113
+ this.#app.pipeline.logger.flush();
114
+ return redirectResponse;
115
+ }
116
+ if (!this.#app.pipeline.cacheProvider) {
117
+ this.#app.pipeline.usedFeatures |= PipelineFeatures.cache;
118
+ response = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
119
+ if (this.#i18n) {
120
+ response = await this.#i18n.finalize(state, response);
121
+ }
122
+ } else {
123
+ const runPipeline = async () => {
124
+ let res = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
125
+ if (this.#i18n) {
126
+ res = await this.#i18n.finalize(state, res);
127
+ }
128
+ return res;
129
+ };
130
+ response = await this.#cacheHandler.handle(state, runPipeline);
131
+ }
132
+ const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
133
+ this.#app.logThisRequest({
134
+ pathname,
135
+ method: request.method,
136
+ statusCode: response.status,
137
+ isRewrite,
138
+ timeStart: state.timeStart
139
+ });
140
+ } catch (err) {
141
+ this.#app.logger.error(null, err.stack || err.message || String(err));
142
+ return this.#app.renderError(request, {
143
+ ...state.renderOptions,
144
+ status: 500,
145
+ error: err,
146
+ pathname: state.pathname
147
+ });
148
+ } finally {
149
+ const finalize = state.finalizeAll();
150
+ if (finalize) await finalize;
151
+ }
152
+ if (REROUTABLE_STATUS_CODES.includes(response.status) && // If the body isn't null, that means the user sets the 404 status
153
+ // but uses the current route to handle the 404
154
+ response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
155
+ return this.#app.renderError(request, {
156
+ ...state.renderOptions,
157
+ response,
158
+ status: response.status,
159
+ // We don't have an error to report here. Passing null means we pass nothing intentionally
160
+ // while undefined means there's no error
161
+ error: response.status === 500 ? null : void 0,
162
+ pathname: state.pathname
163
+ });
164
+ }
165
+ prepareResponse(response, { addCookieHeader });
166
+ this.#app.pipeline.logger.flush();
167
+ return response;
168
+ }
169
+ }
170
+ export {
171
+ AstroHandler
172
+ };
@@ -18,10 +18,3 @@ export declare function isRoute404or500(route: RouteData): boolean;
18
18
  * @return {boolean} Returns true if the route's component is the server island component; otherwise, false.
19
19
  */
20
20
  export declare function isRouteServerIsland(route: RouteData): boolean;
21
- /**
22
- * Determines whether a given route is an external redirect.
23
- *
24
- * @param {RouteData} route - The route object to check.
25
- * @return {boolean} Returns true if the route is an external redirect; otherwise, false.
26
- */
27
- export declare function isRouteExternalRedirect(route: RouteData): boolean;
@@ -1,4 +1,3 @@
1
- import { redirectIsExternal } from "../redirects/render.js";
2
1
  import { SERVER_ISLAND_COMPONENT } from "../server-islands/endpoint.js";
3
2
  import { isRoute404, isRoute500 } from "./internal/route-errors.js";
4
3
  function matchRoute(pathname, manifest) {
@@ -23,12 +22,8 @@ function isRoute404or500(route) {
23
22
  function isRouteServerIsland(route) {
24
23
  return route.component === SERVER_ISLAND_COMPONENT;
25
24
  }
26
- function isRouteExternalRedirect(route) {
27
- return !!(route.type === "redirect" && route.redirect && redirectIsExternal(route.redirect));
28
- }
29
25
  export {
30
26
  isRoute404or500,
31
- isRouteExternalRedirect,
32
27
  isRouteServerIsland,
33
28
  matchAllRoutes,
34
29
  matchRoute
@@ -0,0 +1,18 @@
1
+ import type { BaseApp } from '../app/base.js';
2
+ import type { Pipeline } from '../base-pipeline.js';
3
+ import type { FetchState } from '../fetch/fetch-state.js';
4
+ /**
5
+ * Handles trailing-slash normalization for incoming requests. If the
6
+ * request's pathname does not match the app's configured `trailingSlash`
7
+ * policy, a redirect response is returned. Otherwise, returns `undefined`
8
+ * so the caller can continue processing the request.
9
+ */
10
+ export declare class TrailingSlashHandler {
11
+ #private;
12
+ constructor(app: BaseApp<Pipeline>);
13
+ /**
14
+ * Returns a redirect `Response` if the request pathname needs
15
+ * normalization, or `undefined` if no redirect is required.
16
+ */
17
+ handle(state: FetchState): Response | undefined;
18
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ appendForwardSlash,
3
+ collapseDuplicateTrailingSlashes,
4
+ hasFileExtension,
5
+ isInternalPath,
6
+ removeTrailingForwardSlash
7
+ } from "@astrojs/internal-helpers/path";
8
+ import { prepareResponse } from "../app/prepare-response.js";
9
+ import { redirectTemplate } from "./3xx.js";
10
+ class TrailingSlashHandler {
11
+ #app;
12
+ constructor(app) {
13
+ this.#app = app;
14
+ }
15
+ /**
16
+ * Returns a redirect `Response` if the request pathname needs
17
+ * normalization, or `undefined` if no redirect is required.
18
+ */
19
+ handle(state) {
20
+ const url = new URL(state.request.url);
21
+ const redirect = this.#redirectTrailingSlash(url.pathname);
22
+ if (redirect === url.pathname) {
23
+ return void 0;
24
+ }
25
+ const addCookieHeader = state.renderOptions.addCookieHeader;
26
+ const status = state.request.method === "GET" ? 301 : 308;
27
+ const response = new Response(
28
+ redirectTemplate({
29
+ status,
30
+ relativeLocation: url.pathname,
31
+ absoluteLocation: redirect,
32
+ from: state.request.url
33
+ }),
34
+ {
35
+ status,
36
+ headers: {
37
+ location: redirect + url.search
38
+ }
39
+ }
40
+ );
41
+ prepareResponse(response, { addCookieHeader });
42
+ return response;
43
+ }
44
+ #redirectTrailingSlash(pathname) {
45
+ const { trailingSlash } = this.#app.manifest;
46
+ if (pathname === "/" || isInternalPath(pathname)) {
47
+ return pathname;
48
+ }
49
+ const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
50
+ if (path !== pathname) {
51
+ return path;
52
+ }
53
+ if (trailingSlash === "ignore") {
54
+ return pathname;
55
+ }
56
+ if (trailingSlash === "always" && !hasFileExtension(pathname)) {
57
+ return appendForwardSlash(pathname);
58
+ }
59
+ if (trailingSlash === "never") {
60
+ return removeTrailingForwardSlash(pathname);
61
+ }
62
+ return pathname;
63
+ }
64
+ }
65
+ export {
66
+ TrailingSlashHandler
67
+ };
@@ -1,6 +1,7 @@
1
1
  import type { SessionDriverConfig } from './types.js';
2
2
  export declare const sessionDrivers: {
3
3
  http: (config?: import("unstorage/drivers/http").HTTPOptions | undefined) => SessionDriverConfig;
4
+ fs: (config?: import("unstorage/drivers/fs").FSStorageOptions | undefined) => SessionDriverConfig;
4
5
  azureAppConfiguration: (config?: import("unstorage/drivers/azure-app-configuration").AzureAppConfigurationOptions | undefined) => SessionDriverConfig;
5
6
  azureCosmos: (config?: import("unstorage/drivers/azure-cosmos").AzureCosmosOptions | undefined) => SessionDriverConfig;
6
7
  azureKeyVault: (config?: import("unstorage/drivers/azure-key-vault").AzureKeyVaultOptions | undefined) => SessionDriverConfig;
@@ -14,7 +15,6 @@ export declare const sessionDrivers: {
14
15
  denoKVNode: (config?: import("unstorage/drivers/deno-kv-node").DenoKvNodeOptions | undefined) => SessionDriverConfig;
15
16
  denoKV: (config?: import("unstorage/drivers/deno-kv").DenoKvOptions | undefined) => SessionDriverConfig;
16
17
  fsLite: (config?: import("unstorage/drivers/fs-lite").FSStorageOptions | undefined) => SessionDriverConfig;
17
- fs: (config?: import("unstorage/drivers/fs").FSStorageOptions | undefined) => SessionDriverConfig;
18
18
  github: (config?: import("unstorage/drivers/github").GithubOptions | undefined) => SessionDriverConfig;
19
19
  indexedb: (config?: import("unstorage/drivers/indexedb").IDBKeyvalOptions | undefined) => SessionDriverConfig;
20
20
  localstorage: (config?: import("unstorage/drivers/localstorage").LocalStorageOptions | undefined) => SessionDriverConfig;
@@ -0,0 +1,11 @@
1
+ import type { FetchState } from '../fetch/fetch-state.js';
2
+ /**
3
+ * Registers a session provider on the given `FetchState`. When
4
+ * `state.resolve('session')` is first called, the `AstroSession` is
5
+ * created lazily. When `state.finalizeAll()` runs, any mutations are
6
+ * persisted.
7
+ *
8
+ * No-op (returns synchronously) if sessions are not configured on the
9
+ * pipeline, avoiding promise allocation on the hot path.
10
+ */
11
+ export declare function provideSession(state: FetchState): Promise<void> | void;
@@ -0,0 +1,33 @@
1
+ import { PipelineFeatures } from "../base-pipeline.js";
2
+ import { AstroSession, PERSIST_SYMBOL } from "./runtime.js";
3
+ const SESSION_KEY = "session";
4
+ function provideSession(state) {
5
+ state.pipeline.usedFeatures |= PipelineFeatures.sessions;
6
+ const pipeline = state.pipeline;
7
+ const config = pipeline.manifest.sessionConfig;
8
+ if (!config) return;
9
+ return provideSessionAsync(state, config);
10
+ }
11
+ async function provideSessionAsync(state, config) {
12
+ const pipeline = state.pipeline;
13
+ const driverFactory = await pipeline.getSessionDriver();
14
+ if (!driverFactory) return;
15
+ state.provide(SESSION_KEY, {
16
+ create() {
17
+ const cookies = state.cookies;
18
+ return new AstroSession({
19
+ cookies,
20
+ config,
21
+ runtimeMode: pipeline.runtimeMode,
22
+ driverFactory,
23
+ mockStorage: null
24
+ });
25
+ },
26
+ finalize(session) {
27
+ return session[PERSIST_SYMBOL]();
28
+ }
29
+ });
30
+ }
31
+ export {
32
+ provideSession
33
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Creates a normalized URL from a request URL string.
3
+ * Decodes and validates the pathname, collapses duplicate slashes.
4
+ */
5
+ export declare function createNormalizedUrl(requestUrl: string): URL;
6
+ /**
7
+ * Normalizes an already-parsed URL in place: decodes and validates the
8
+ * pathname, collapses duplicate slashes. Returns the same URL object.
9
+ */
10
+ export declare function normalizeUrl(url: URL): URL;
@@ -0,0 +1,21 @@
1
+ import { collapseDuplicateSlashes } from "@astrojs/internal-helpers/path";
2
+ import { validateAndDecodePathname } from "./pathname.js";
3
+ function createNormalizedUrl(requestUrl) {
4
+ return normalizeUrl(new URL(requestUrl));
5
+ }
6
+ function normalizeUrl(url) {
7
+ try {
8
+ url.pathname = validateAndDecodePathname(url.pathname);
9
+ } catch {
10
+ try {
11
+ url.pathname = decodeURI(url.pathname);
12
+ } catch {
13
+ }
14
+ }
15
+ url.pathname = collapseDuplicateSlashes(url.pathname);
16
+ return url;
17
+ }
18
+ export {
19
+ createNormalizedUrl,
20
+ normalizeUrl
21
+ };