astro 6.2.1 → 6.3.0
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.
- package/dist/actions/handler.d.ts +32 -0
- package/dist/actions/handler.js +45 -0
- package/dist/actions/runtime/server.js +1 -1
- package/dist/assets/build/generate.js +1 -1
- package/dist/assets/build/remote.d.ts +3 -2
- package/dist/assets/build/remote.js +16 -9
- package/dist/assets/endpoint/generic.js +4 -7
- package/dist/assets/endpoint/shared.js +7 -2
- package/dist/assets/index.d.ts +1 -0
- package/dist/assets/index.js +2 -0
- package/dist/assets/internal.js +16 -1
- package/dist/assets/services/sharp.js +16 -1
- package/dist/assets/utils/index.d.ts +1 -0
- package/dist/assets/utils/index.js +2 -0
- package/dist/assets/utils/redirectValidation.d.ts +48 -0
- package/dist/assets/utils/redirectValidation.js +48 -0
- package/dist/assets/utils/remoteProbe.js +25 -2
- package/dist/cli/add/index.js +7 -4
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/container/index.js +18 -14
- package/dist/content/content-layer.js +3 -4
- package/dist/content/loaders/types.d.ts +1 -1
- package/dist/content/server-listeners.js +0 -4
- package/dist/content/vite-plugin-content-virtual-mod.js +9 -1
- package/dist/core/app/base.d.ts +33 -15
- package/dist/core/app/base.js +120 -324
- package/dist/core/app/dev/app.d.ts +3 -2
- package/dist/core/app/dev/app.js +4 -60
- package/dist/core/app/entrypoints/virtual/dev.js +2 -0
- package/dist/core/app/entrypoints/virtual/prod.js +4 -1
- package/dist/core/app/prepare-response.d.ts +11 -0
- package/dist/core/app/prepare-response.js +18 -0
- package/dist/core/app/render-options.d.ts +11 -0
- package/dist/core/app/render-options.js +11 -0
- package/dist/core/base-pipeline.d.ts +38 -1
- package/dist/core/base-pipeline.js +50 -7
- package/dist/core/build/app.d.ts +3 -4
- package/dist/core/build/app.js +3 -17
- package/dist/core/build/plugins/plugin-css.js +7 -1
- package/dist/core/build/plugins/plugin-manifest.js +11 -1
- package/dist/core/build/static-build.js +16 -2
- package/dist/core/cache/handler.d.ts +29 -0
- package/dist/core/cache/handler.js +81 -0
- package/dist/core/compile/style.js +18 -1
- package/dist/core/config/index.d.ts +1 -1
- package/dist/core/config/index.js +4 -1
- package/dist/core/config/schemas/base.d.ts +4 -0
- package/dist/core/config/schemas/base.js +4 -0
- package/dist/core/config/schemas/relative.d.ts +6 -0
- package/dist/core/config/settings.js +2 -4
- package/dist/core/config/tsconfig.d.ts +24 -9
- package/dist/core/config/tsconfig.js +54 -45
- package/dist/core/constants.d.ts +27 -1
- package/dist/core/constants.js +14 -1
- package/dist/core/cookies/cookies.d.ts +7 -2
- package/dist/core/cookies/cookies.js +11 -4
- package/dist/core/cookies/response.d.ts +1 -1
- package/dist/core/cookies/response.js +1 -2
- package/dist/core/create-vite.js +15 -0
- package/dist/core/csp/runtime.js +6 -4
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/build-handler.d.ts +17 -0
- package/dist/core/errors/build-handler.js +22 -0
- package/dist/core/errors/default-handler.d.ts +14 -0
- package/dist/core/errors/default-handler.js +144 -0
- package/dist/core/errors/dev-handler.d.ts +21 -0
- package/dist/core/errors/dev-handler.js +82 -0
- package/dist/core/errors/handler.d.ts +9 -0
- package/dist/core/errors/handler.js +0 -0
- package/dist/core/fetch/default-handler.d.ts +17 -0
- package/dist/core/fetch/default-handler.js +45 -0
- package/dist/core/fetch/fetch-state.d.ts +244 -0
- package/dist/core/fetch/fetch-state.js +779 -0
- package/dist/core/fetch/index.d.ts +61 -0
- package/dist/core/fetch/index.js +121 -0
- package/dist/core/fetch/types.d.ts +6 -0
- package/dist/core/fetch/types.js +0 -0
- package/dist/core/fetch/vite-plugin.d.ts +5 -0
- package/dist/core/fetch/vite-plugin.js +69 -0
- package/dist/core/hono/index.d.ts +21 -0
- package/dist/core/hono/index.js +98 -0
- package/dist/core/i18n/handler.d.ts +18 -0
- package/dist/core/i18n/handler.js +119 -0
- package/dist/core/logger/core.d.ts +8 -0
- package/dist/core/logger/core.js +16 -0
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/astro-middleware.d.ts +27 -0
- package/dist/core/middleware/astro-middleware.js +53 -0
- package/dist/core/pages/handler.d.ts +20 -0
- package/dist/core/pages/handler.js +74 -0
- package/dist/core/redirects/render.d.ts +2 -2
- package/dist/core/redirects/render.js +7 -8
- package/dist/core/render/params-and-props.js +1 -1
- package/dist/core/render/slots.js +9 -2
- package/dist/core/rewrites/handler.d.ts +37 -0
- package/dist/core/rewrites/handler.js +67 -0
- package/dist/core/routing/3xx.js +8 -4
- package/dist/core/routing/handler.d.ts +17 -0
- package/dist/core/routing/handler.js +172 -0
- package/dist/core/routing/match.d.ts +0 -7
- package/dist/core/routing/match.js +0 -5
- package/dist/core/routing/pattern.js +1 -1
- package/dist/core/routing/rewrite.js +1 -4
- package/dist/core/routing/trailing-slash-handler.d.ts +18 -0
- package/dist/core/routing/trailing-slash-handler.js +67 -0
- package/dist/core/session/drivers.d.ts +1 -1
- package/dist/core/session/handler.d.ts +11 -0
- package/dist/core/session/handler.js +33 -0
- package/dist/core/session/runtime.js +7 -2
- package/dist/core/util/normalized-url.d.ts +10 -0
- package/dist/core/util/normalized-url.js +21 -0
- package/dist/i18n/middleware.d.ts +10 -0
- package/dist/i18n/middleware.js +4 -88
- package/dist/i18n/utils.js +2 -2
- package/dist/prefetch/index.js +12 -7
- package/dist/runtime/server/astro-island.js +57 -20
- package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
- package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
- package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
- package/dist/runtime/server/astro-island.prebuilt.js +1 -1
- package/dist/runtime/server/render/common.js +10 -4
- package/dist/runtime/server/render/server-islands.js +2 -1
- package/dist/runtime/server/scripts.js +6 -0
- package/dist/types/public/config.d.ts +46 -12
- package/dist/types/public/content.d.ts +4 -4
- package/dist/types/public/internal.d.ts +1 -1
- package/dist/vite-plugin-app/app.d.ts +4 -5
- package/dist/vite-plugin-app/app.js +20 -68
- package/dist/vite-plugin-astro/compile.js +2 -2
- package/dist/vite-plugin-astro/utils.d.ts +1 -0
- package/dist/vite-plugin-astro/utils.js +9 -1
- package/dist/vite-plugin-head/index.js +36 -19
- package/dist/vite-plugin-utils/index.d.ts +1 -0
- package/dist/vite-plugin-utils/index.js +3 -0
- package/package.json +13 -6
- package/dist/core/render-context.d.ts +0 -77
- 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
|
+
};
|
|
@@ -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 {
|
|
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(
|
|
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(
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
44
|
+
state.params,
|
|
46
45
|
redirect,
|
|
47
46
|
redirectRoute,
|
|
48
|
-
|
|
47
|
+
state.pipeline.manifest.trailingSlash
|
|
49
48
|
)
|
|
50
49
|
)
|
|
51
50
|
};
|
|
@@ -44,7 +44,7 @@ async function getProps(opts) {
|
|
|
44
44
|
}
|
|
45
45
|
function getParams(route, pathname) {
|
|
46
46
|
if (!route.params.length) return {};
|
|
47
|
-
const path = pathname.endsWith(".html") && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
|
|
47
|
+
const path = pathname.endsWith(".html") && route.type === "page" && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
|
|
48
48
|
const allPatterns = [route, ...route.fallbackRoutes].map((r) => r.pattern);
|
|
49
49
|
const paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
|
|
50
50
|
if (!paramsMatch) return {};
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { renderSlotToString } from "../../runtime/server/index.js";
|
|
2
2
|
import { renderJSX } from "../../runtime/server/jsx.js";
|
|
3
|
+
import { isRenderTemplateResult } from "../../runtime/server/render/astro/index.js";
|
|
3
4
|
import { chunkToString } from "../../runtime/server/render/index.js";
|
|
4
5
|
import { isRenderInstruction } from "../../runtime/server/render/instruction.js";
|
|
5
6
|
import { AstroError, AstroErrorData } from "../errors/index.js";
|
|
6
7
|
function getFunctionExpression(slot) {
|
|
7
8
|
if (!slot) return;
|
|
8
|
-
const expressions = slot?.expressions?.filter(
|
|
9
|
+
const expressions = slot?.expressions?.filter(
|
|
10
|
+
(e) => isRenderInstruction(e) === false || isRenderTemplateResult(e)
|
|
11
|
+
);
|
|
9
12
|
if (expressions?.length !== 1) return;
|
|
10
|
-
|
|
13
|
+
const expression = expressions[0];
|
|
14
|
+
if (isRenderTemplateResult(expression)) {
|
|
15
|
+
return getFunctionExpression(expression);
|
|
16
|
+
}
|
|
17
|
+
return expression;
|
|
11
18
|
}
|
|
12
19
|
class Slots {
|
|
13
20
|
#result;
|
|
@@ -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
|
+
};
|
package/dist/core/routing/3xx.js
CHANGED
|
@@ -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: ${
|
|
10
|
-
<meta http-equiv="refresh" content="${delay};url=${
|
|
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="${
|
|
16
|
+
<link rel="canonical" href="${abs}">
|
|
13
17
|
<body>
|
|
14
|
-
<a href="${
|
|
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
|
|
@@ -16,7 +16,7 @@ function getPattern(segments, base, addTrailingSlash) {
|
|
|
16
16
|
}).join("");
|
|
17
17
|
const trailing = addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : "$";
|
|
18
18
|
let initial = "\\/";
|
|
19
|
-
if (addTrailingSlash === "never" && base !== "/") {
|
|
19
|
+
if (addTrailingSlash === "never" && base !== "/" && pathname !== "") {
|
|
20
20
|
initial = "";
|
|
21
21
|
}
|
|
22
22
|
return new RegExp(`^${pathname || initial}${trailing}`);
|
|
@@ -134,7 +134,7 @@ function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat)
|
|
|
134
134
|
if (base !== "/") {
|
|
135
135
|
const isBasePathRequest = urlPathname === base || urlPathname === removeTrailingForwardSlash(base);
|
|
136
136
|
if (isBasePathRequest) {
|
|
137
|
-
pathname =
|
|
137
|
+
pathname = "/";
|
|
138
138
|
} else if (urlPathname.startsWith(base)) {
|
|
139
139
|
pathname = shouldAppendSlash ? appendForwardSlash(urlPathname) : removeTrailingForwardSlash(urlPathname);
|
|
140
140
|
pathname = pathname.slice(base.length);
|
|
@@ -143,9 +143,6 @@ function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat)
|
|
|
143
143
|
if (!pathname.startsWith("/") && shouldAppendSlash && urlPathname.endsWith("/")) {
|
|
144
144
|
pathname = prependForwardSlash(pathname);
|
|
145
145
|
}
|
|
146
|
-
if (pathname === "/" && base !== "/" && !shouldAppendSlash) {
|
|
147
|
-
pathname = "";
|
|
148
|
-
}
|
|
149
146
|
if (buildFormat === "file") {
|
|
150
147
|
pathname = pathname.replace(/\.html$/, "");
|
|
151
148
|
}
|
|
@@ -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;
|