Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.
This commit is contained in:
2
node_modules/astro/dist/core/app/common.d.ts
generated
vendored
Normal file
2
node_modules/astro/dist/core/app/common.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import type { SSRManifest, SerializedSSRManifest } from './types.js';
|
||||
export declare function deserializeManifest(serializedManifest: SerializedSSRManifest): SSRManifest;
|
37
node_modules/astro/dist/core/app/common.js
generated
vendored
Normal file
37
node_modules/astro/dist/core/app/common.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { decodeKey } from "../encryption.js";
|
||||
import { NOOP_MIDDLEWARE_FN } from "../middleware/noop-middleware.js";
|
||||
import { deserializeRouteData } from "../routing/manifest/serialization.js";
|
||||
function deserializeManifest(serializedManifest) {
|
||||
const routes = [];
|
||||
for (const serializedRoute of serializedManifest.routes) {
|
||||
routes.push({
|
||||
...serializedRoute,
|
||||
routeData: deserializeRouteData(serializedRoute.routeData)
|
||||
});
|
||||
const route = serializedRoute;
|
||||
route.routeData = deserializeRouteData(serializedRoute.routeData);
|
||||
}
|
||||
const assets = new Set(serializedManifest.assets);
|
||||
const componentMetadata = new Map(serializedManifest.componentMetadata);
|
||||
const inlinedScripts = new Map(serializedManifest.inlinedScripts);
|
||||
const clientDirectives = new Map(serializedManifest.clientDirectives);
|
||||
const serverIslandNameMap = new Map(serializedManifest.serverIslandNameMap);
|
||||
const key = decodeKey(serializedManifest.key);
|
||||
return {
|
||||
// in case user middleware exists, this no-op middleware will be reassigned (see plugin-ssr.ts)
|
||||
middleware() {
|
||||
return { onRequest: NOOP_MIDDLEWARE_FN };
|
||||
},
|
||||
...serializedManifest,
|
||||
assets,
|
||||
componentMetadata,
|
||||
inlinedScripts,
|
||||
clientDirectives,
|
||||
routes,
|
||||
serverIslandNameMap,
|
||||
key
|
||||
};
|
||||
}
|
||||
export {
|
||||
deserializeManifest
|
||||
};
|
9
node_modules/astro/dist/core/app/createOutgoingHttpHeaders.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/core/app/createOutgoingHttpHeaders.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { OutgoingHttpHeaders } from 'node:http';
|
||||
/**
|
||||
* Takes in a nullable WebAPI Headers object and produces a NodeJS OutgoingHttpHeaders object suitable for usage
|
||||
* with ServerResponse.writeHead(..) or ServerResponse.setHeader(..)
|
||||
*
|
||||
* @param headers WebAPI Headers object
|
||||
* @returns {OutgoingHttpHeaders} NodeJS OutgoingHttpHeaders object with multiple set-cookie handled as an array of values
|
||||
*/
|
||||
export declare const createOutgoingHttpHeaders: (headers: Headers | undefined | null) => OutgoingHttpHeaders | undefined;
|
19
node_modules/astro/dist/core/app/createOutgoingHttpHeaders.js
generated
vendored
Normal file
19
node_modules/astro/dist/core/app/createOutgoingHttpHeaders.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
const createOutgoingHttpHeaders = (headers) => {
|
||||
if (!headers) {
|
||||
return void 0;
|
||||
}
|
||||
const nodeHeaders = Object.fromEntries(headers.entries());
|
||||
if (Object.keys(nodeHeaders).length === 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (headers.has("set-cookie")) {
|
||||
const cookieHeaders = headers.getSetCookie();
|
||||
if (cookieHeaders.length > 1) {
|
||||
nodeHeaders["set-cookie"] = cookieHeaders;
|
||||
}
|
||||
}
|
||||
return nodeHeaders;
|
||||
};
|
||||
export {
|
||||
createOutgoingHttpHeaders
|
||||
};
|
73
node_modules/astro/dist/core/app/index.d.ts
generated
vendored
Normal file
73
node_modules/astro/dist/core/app/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ManifestData, RouteData, SSRManifest } from '../../@types/astro.js';
|
||||
import { getSetCookiesFromResponse } from '../cookies/index.js';
|
||||
import { AstroIntegrationLogger } from '../logger/core.js';
|
||||
export { deserializeManifest } from './common.js';
|
||||
export interface RenderOptions {
|
||||
/**
|
||||
* Whether to automatically add all cookies written by `Astro.cookie.set()` to the response headers.
|
||||
*
|
||||
* When set to `true`, they will be added to the `Set-Cookie` header as comma-separated key=value pairs. You can use the standard `response.headers.getSetCookie()` API to read them individually.
|
||||
*
|
||||
* When set to `false`, the cookies will only be available from `App.getSetCookieFromResponse(response)`.
|
||||
*
|
||||
* @default {false}
|
||||
*/
|
||||
addCookieHeader?: boolean;
|
||||
/**
|
||||
* The client IP address that will be made available as `Astro.clientAddress` in pages, and as `ctx.clientAddress` in API routes and middleware.
|
||||
*
|
||||
* Default: `request[Symbol.for("astro.clientAddress")]`
|
||||
*/
|
||||
clientAddress?: string;
|
||||
/**
|
||||
* The mutable object that will be made available as `Astro.locals` in pages, and as `ctx.locals` in API routes and middleware.
|
||||
*/
|
||||
locals?: object;
|
||||
/**
|
||||
* **Advanced API**: you probably do not need to use this.
|
||||
*
|
||||
* Default: `app.match(request)`
|
||||
*/
|
||||
routeData?: RouteData;
|
||||
}
|
||||
export interface RenderErrorOptions {
|
||||
locals?: App.Locals;
|
||||
routeData?: RouteData;
|
||||
response?: Response;
|
||||
status: 404 | 500;
|
||||
/**
|
||||
* Whether to skip middleware while rendering the error page. Defaults to false.
|
||||
*/
|
||||
skipMiddleware?: boolean;
|
||||
/**
|
||||
* Allows passing an error to 500.astro. It will be available through `Astro.props.error`.
|
||||
*/
|
||||
error?: unknown;
|
||||
}
|
||||
export declare class App {
|
||||
#private;
|
||||
constructor(manifest: SSRManifest, streaming?: boolean);
|
||||
getAdapterLogger(): AstroIntegrationLogger;
|
||||
set setManifestData(newManifestData: ManifestData);
|
||||
removeBase(pathname: string): string;
|
||||
match(request: Request): RouteData | undefined;
|
||||
render(request: Request, options?: RenderOptions): Promise<Response>;
|
||||
/**
|
||||
* @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
|
||||
* See https://github.com/withastro/astro/pull/9199 for more information.
|
||||
*/
|
||||
render(request: Request, routeData?: RouteData, locals?: object): Promise<Response>;
|
||||
setCookieHeaders(response: Response): Generator<string, string[], any>;
|
||||
/**
|
||||
* Reads all the cookies written by `Astro.cookie.set()` onto the passed response.
|
||||
* For example,
|
||||
* ```ts
|
||||
* for (const cookie_ of App.getSetCookieFromResponse(response)) {
|
||||
* const cookie: string = cookie_
|
||||
* }
|
||||
* ```
|
||||
* @param response The response to read cookies from.
|
||||
* @returns An iterator that yields key-value pairs as equal-sign-separated strings.
|
||||
*/
|
||||
static getSetCookieFromResponse: typeof getSetCookiesFromResponse;
|
||||
}
|
372
node_modules/astro/dist/core/app/index.js
generated
vendored
Normal file
372
node_modules/astro/dist/core/app/index.js
generated
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
import { normalizeTheLocale } from "../../i18n/index.js";
|
||||
import {
|
||||
REROUTABLE_STATUS_CODES,
|
||||
REROUTE_DIRECTIVE_HEADER,
|
||||
clientAddressSymbol,
|
||||
clientLocalsSymbol,
|
||||
responseSentSymbol
|
||||
} from "../constants.js";
|
||||
import { getSetCookiesFromResponse } from "../cookies/index.js";
|
||||
import { AstroError, AstroErrorData } from "../errors/index.js";
|
||||
import { consoleLogDestination } from "../logger/console.js";
|
||||
import { AstroIntegrationLogger, Logger } from "../logger/core.js";
|
||||
import { NOOP_MIDDLEWARE_FN } from "../middleware/noop-middleware.js";
|
||||
import {
|
||||
appendForwardSlash,
|
||||
joinPaths,
|
||||
prependForwardSlash,
|
||||
removeTrailingForwardSlash
|
||||
} from "../path.js";
|
||||
import { RenderContext } from "../render-context.js";
|
||||
import { createAssetLink } from "../render/ssr-element.js";
|
||||
import { createDefaultRoutes, injectDefaultRoutes } from "../routing/default.js";
|
||||
import { matchRoute } from "../routing/match.js";
|
||||
import { AppPipeline } from "./pipeline.js";
|
||||
import { deserializeManifest } from "./common.js";
|
||||
class App {
|
||||
#manifest;
|
||||
#manifestData;
|
||||
#logger = new Logger({
|
||||
dest: consoleLogDestination,
|
||||
level: "info"
|
||||
});
|
||||
#baseWithoutTrailingSlash;
|
||||
#pipeline;
|
||||
#adapterLogger;
|
||||
#renderOptionsDeprecationWarningShown = false;
|
||||
constructor(manifest, streaming = true) {
|
||||
this.#manifest = manifest;
|
||||
this.#manifestData = injectDefaultRoutes(manifest, {
|
||||
routes: manifest.routes.map((route) => route.routeData)
|
||||
});
|
||||
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
|
||||
this.#pipeline = this.#createPipeline(this.#manifestData, streaming);
|
||||
this.#adapterLogger = new AstroIntegrationLogger(
|
||||
this.#logger.options,
|
||||
this.#manifest.adapterName
|
||||
);
|
||||
}
|
||||
getAdapterLogger() {
|
||||
return this.#adapterLogger;
|
||||
}
|
||||
/**
|
||||
* Creates a pipeline by reading the stored manifest
|
||||
*
|
||||
* @param manifestData
|
||||
* @param streaming
|
||||
* @private
|
||||
*/
|
||||
#createPipeline(manifestData, streaming = false) {
|
||||
return AppPipeline.create(manifestData, {
|
||||
logger: this.#logger,
|
||||
manifest: this.#manifest,
|
||||
mode: "production",
|
||||
renderers: this.#manifest.renderers,
|
||||
defaultRoutes: createDefaultRoutes(this.#manifest),
|
||||
resolve: async (specifier) => {
|
||||
if (!(specifier in this.#manifest.entryModules)) {
|
||||
throw new Error(`Unable to resolve [${specifier}]`);
|
||||
}
|
||||
const bundlePath = this.#manifest.entryModules[specifier];
|
||||
if (bundlePath.startsWith("data:") || bundlePath.length === 0) {
|
||||
return bundlePath;
|
||||
} else {
|
||||
return createAssetLink(bundlePath, this.#manifest.base, this.#manifest.assetsPrefix);
|
||||
}
|
||||
},
|
||||
serverLike: true,
|
||||
streaming
|
||||
});
|
||||
}
|
||||
set setManifestData(newManifestData) {
|
||||
this.#manifestData = newManifestData;
|
||||
}
|
||||
removeBase(pathname) {
|
||||
if (pathname.startsWith(this.#manifest.base)) {
|
||||
return pathname.slice(this.#baseWithoutTrailingSlash.length + 1);
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
#getPathnameFromRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
const pathname = prependForwardSlash(this.removeBase(url.pathname));
|
||||
return pathname;
|
||||
}
|
||||
match(request) {
|
||||
const url = new URL(request.url);
|
||||
if (this.#manifest.assets.has(url.pathname)) return void 0;
|
||||
let pathname = this.#computePathnameFromDomain(request);
|
||||
if (!pathname) {
|
||||
pathname = prependForwardSlash(this.removeBase(url.pathname));
|
||||
}
|
||||
let routeData = matchRoute(pathname, this.#manifestData);
|
||||
if (!routeData || routeData.prerender) return void 0;
|
||||
return routeData;
|
||||
}
|
||||
#computePathnameFromDomain(request) {
|
||||
let pathname = void 0;
|
||||
const url = new URL(request.url);
|
||||
if (this.#manifest.i18n && (this.#manifest.i18n.strategy === "domains-prefix-always" || this.#manifest.i18n.strategy === "domains-prefix-other-locales" || this.#manifest.i18n.strategy === "domains-prefix-always-no-redirect")) {
|
||||
let host = request.headers.get("X-Forwarded-Host");
|
||||
let protocol = request.headers.get("X-Forwarded-Proto");
|
||||
if (protocol) {
|
||||
protocol = protocol + ":";
|
||||
} else {
|
||||
protocol = url.protocol;
|
||||
}
|
||||
if (!host) {
|
||||
host = request.headers.get("Host");
|
||||
}
|
||||
if (host && protocol) {
|
||||
host = host.split(":")[0];
|
||||
try {
|
||||
let locale;
|
||||
const hostAsUrl = new URL(`${protocol}//${host}`);
|
||||
for (const [domainKey, localeValue] of Object.entries(
|
||||
this.#manifest.i18n.domainLookupTable
|
||||
)) {
|
||||
const domainKeyAsUrl = new URL(domainKey);
|
||||
if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
|
||||
locale = localeValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (locale) {
|
||||
pathname = prependForwardSlash(
|
||||
joinPaths(normalizeTheLocale(locale), this.removeBase(url.pathname))
|
||||
);
|
||||
if (url.pathname.endsWith("/")) {
|
||||
pathname = appendForwardSlash(pathname);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.#logger.error(
|
||||
"router",
|
||||
`Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
|
||||
);
|
||||
this.#logger.error("router", `Error: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
async render(request, routeDataOrOptions, maybeLocals) {
|
||||
let routeData;
|
||||
let locals;
|
||||
let clientAddress;
|
||||
let addCookieHeader;
|
||||
if (routeDataOrOptions && ("addCookieHeader" in routeDataOrOptions || "clientAddress" in routeDataOrOptions || "locals" in routeDataOrOptions || "routeData" in routeDataOrOptions)) {
|
||||
if ("addCookieHeader" in routeDataOrOptions) {
|
||||
addCookieHeader = routeDataOrOptions.addCookieHeader;
|
||||
}
|
||||
if ("clientAddress" in routeDataOrOptions) {
|
||||
clientAddress = routeDataOrOptions.clientAddress;
|
||||
}
|
||||
if ("routeData" in routeDataOrOptions) {
|
||||
routeData = routeDataOrOptions.routeData;
|
||||
}
|
||||
if ("locals" in routeDataOrOptions) {
|
||||
locals = routeDataOrOptions.locals;
|
||||
}
|
||||
} else {
|
||||
routeData = routeDataOrOptions;
|
||||
locals = maybeLocals;
|
||||
if (routeDataOrOptions || locals) {
|
||||
this.#logRenderOptionsDeprecationWarning();
|
||||
}
|
||||
}
|
||||
if (routeData) {
|
||||
this.#logger.debug(
|
||||
"router",
|
||||
"The adapter " + this.#manifest.adapterName + " provided a custom RouteData for ",
|
||||
request.url
|
||||
);
|
||||
this.#logger.debug("router", "RouteData:\n" + routeData);
|
||||
}
|
||||
if (locals) {
|
||||
if (typeof locals !== "object") {
|
||||
const error = new AstroError(AstroErrorData.LocalsNotAnObject);
|
||||
this.#logger.error(null, error.stack);
|
||||
return this.#renderError(request, { status: 500, error });
|
||||
}
|
||||
Reflect.set(request, clientLocalsSymbol, locals);
|
||||
}
|
||||
if (clientAddress) {
|
||||
Reflect.set(request, clientAddressSymbol, clientAddress);
|
||||
}
|
||||
if (!routeData) {
|
||||
routeData = this.match(request);
|
||||
this.#logger.debug("router", "Astro matched the following route for " + request.url);
|
||||
this.#logger.debug("router", "RouteData:\n" + routeData);
|
||||
}
|
||||
if (!routeData) {
|
||||
this.#logger.debug("router", "Astro hasn't found routes that match " + request.url);
|
||||
this.#logger.debug("router", "Here's the available routes:\n", this.#manifestData);
|
||||
return this.#renderError(request, { locals, status: 404 });
|
||||
}
|
||||
const pathname = this.#getPathnameFromRequest(request);
|
||||
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
|
||||
let response;
|
||||
try {
|
||||
const mod = await this.#pipeline.getModuleForRoute(routeData);
|
||||
const renderContext = await RenderContext.create({
|
||||
pipeline: this.#pipeline,
|
||||
locals,
|
||||
pathname,
|
||||
request,
|
||||
routeData,
|
||||
status: defaultStatus
|
||||
});
|
||||
response = await renderContext.render(await mod.page());
|
||||
} catch (err) {
|
||||
this.#logger.error(null, err.stack || err.message || String(err));
|
||||
return this.#renderError(request, { locals, status: 500, error: err });
|
||||
}
|
||||
if (REROUTABLE_STATUS_CODES.includes(response.status) && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
|
||||
return this.#renderError(request, {
|
||||
locals,
|
||||
response,
|
||||
status: response.status,
|
||||
// We don't have an error to report here. Passing null means we pass nothing intentionally
|
||||
// while undefined means there's no error
|
||||
error: response.status === 500 ? null : void 0
|
||||
});
|
||||
}
|
||||
if (response.headers.has(REROUTE_DIRECTIVE_HEADER)) {
|
||||
response.headers.delete(REROUTE_DIRECTIVE_HEADER);
|
||||
}
|
||||
if (addCookieHeader) {
|
||||
for (const setCookieHeaderValue of App.getSetCookieFromResponse(response)) {
|
||||
response.headers.append("set-cookie", setCookieHeaderValue);
|
||||
}
|
||||
}
|
||||
Reflect.set(response, responseSentSymbol, true);
|
||||
return response;
|
||||
}
|
||||
#logRenderOptionsDeprecationWarning() {
|
||||
if (this.#renderOptionsDeprecationWarningShown) return;
|
||||
this.#logger.warn(
|
||||
"deprecated",
|
||||
`The adapter ${this.#manifest.adapterName} is using a deprecated signature of the 'app.render()' method. From Astro 4.0, locals and routeData are provided as properties on an optional object to this method. Using the old signature will cause an error in Astro 5.0. See https://github.com/withastro/astro/pull/9199 for more information.`
|
||||
);
|
||||
this.#renderOptionsDeprecationWarningShown = true;
|
||||
}
|
||||
setCookieHeaders(response) {
|
||||
return getSetCookiesFromResponse(response);
|
||||
}
|
||||
/**
|
||||
* Reads all the cookies written by `Astro.cookie.set()` onto the passed response.
|
||||
* For example,
|
||||
* ```ts
|
||||
* for (const cookie_ of App.getSetCookieFromResponse(response)) {
|
||||
* const cookie: string = cookie_
|
||||
* }
|
||||
* ```
|
||||
* @param response The response to read cookies from.
|
||||
* @returns An iterator that yields key-value pairs as equal-sign-separated strings.
|
||||
*/
|
||||
static getSetCookieFromResponse = getSetCookiesFromResponse;
|
||||
/**
|
||||
* If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
|
||||
* This also handles pre-rendered /404 or /500 routes
|
||||
*/
|
||||
async #renderError(request, {
|
||||
locals,
|
||||
status,
|
||||
response: originalResponse,
|
||||
skipMiddleware = false,
|
||||
error
|
||||
}) {
|
||||
const errorRoutePath = `/${status}${this.#manifest.trailingSlash === "always" ? "/" : ""}`;
|
||||
const errorRouteData = matchRoute(errorRoutePath, this.#manifestData);
|
||||
const url = new URL(request.url);
|
||||
if (errorRouteData) {
|
||||
if (errorRouteData.prerender) {
|
||||
const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? ".html" : "";
|
||||
const statusURL = new URL(
|
||||
`${this.#baseWithoutTrailingSlash}/${status}${maybeDotHtml}`,
|
||||
url
|
||||
);
|
||||
if (statusURL.toString() !== request.url) {
|
||||
const response2 = await fetch(statusURL.toString());
|
||||
const override = { status };
|
||||
return this.#mergeResponses(response2, originalResponse, override);
|
||||
}
|
||||
}
|
||||
const mod = await this.#pipeline.getModuleForRoute(errorRouteData);
|
||||
try {
|
||||
const renderContext = await RenderContext.create({
|
||||
locals,
|
||||
pipeline: this.#pipeline,
|
||||
middleware: skipMiddleware ? NOOP_MIDDLEWARE_FN : void 0,
|
||||
pathname: this.#getPathnameFromRequest(request),
|
||||
request,
|
||||
routeData: errorRouteData,
|
||||
status,
|
||||
props: { error }
|
||||
});
|
||||
const response2 = await renderContext.render(await mod.page());
|
||||
return this.#mergeResponses(response2, originalResponse);
|
||||
} catch {
|
||||
if (skipMiddleware === false) {
|
||||
return this.#renderError(request, {
|
||||
locals,
|
||||
status,
|
||||
response: originalResponse,
|
||||
skipMiddleware: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const response = this.#mergeResponses(new Response(null, { status }), originalResponse);
|
||||
Reflect.set(response, responseSentSymbol, true);
|
||||
return response;
|
||||
}
|
||||
#mergeResponses(newResponse, originalResponse, override) {
|
||||
if (!originalResponse) {
|
||||
if (override !== void 0) {
|
||||
return new Response(newResponse.body, {
|
||||
status: override.status,
|
||||
statusText: newResponse.statusText,
|
||||
headers: newResponse.headers
|
||||
});
|
||||
}
|
||||
return newResponse;
|
||||
}
|
||||
const status = override?.status ? override.status : originalResponse.status === 200 ? newResponse.status : originalResponse.status;
|
||||
try {
|
||||
originalResponse.headers.delete("Content-type");
|
||||
} catch {
|
||||
}
|
||||
return new Response(newResponse.body, {
|
||||
status,
|
||||
statusText: status === 200 ? newResponse.statusText : originalResponse.statusText,
|
||||
// If you're looking at here for possible bugs, it means that it's not a bug.
|
||||
// With the middleware, users can meddle with headers, and we should pass to the 404/500.
|
||||
// If users see something weird, it's because they are setting some headers they should not.
|
||||
//
|
||||
// Although, we don't want it to replace the content-type, because the error page must return `text/html`
|
||||
headers: new Headers([
|
||||
...Array.from(newResponse.headers),
|
||||
...Array.from(originalResponse.headers)
|
||||
])
|
||||
});
|
||||
}
|
||||
#getDefaultStatusCode(routeData, pathname) {
|
||||
if (!routeData.pattern.test(pathname)) {
|
||||
for (const fallbackRoute of routeData.fallbackRoutes) {
|
||||
if (fallbackRoute.pattern.test(pathname)) {
|
||||
return 302;
|
||||
}
|
||||
}
|
||||
}
|
||||
const route = removeTrailingForwardSlash(routeData.route);
|
||||
if (route.endsWith("/404")) return 404;
|
||||
if (route.endsWith("/500")) return 500;
|
||||
return 200;
|
||||
}
|
||||
}
|
||||
export {
|
||||
App,
|
||||
deserializeManifest
|
||||
};
|
7
node_modules/astro/dist/core/app/middlewares.d.ts
generated
vendored
Normal file
7
node_modules/astro/dist/core/app/middlewares.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { MiddlewareHandler } from '../../@types/astro.js';
|
||||
/**
|
||||
* Returns a middleware function in charge to check the `origin` header.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export declare function createOriginCheckMiddleware(): MiddlewareHandler;
|
44
node_modules/astro/dist/core/app/middlewares.js
generated
vendored
Normal file
44
node_modules/astro/dist/core/app/middlewares.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { defineMiddleware } from "../middleware/index.js";
|
||||
const FORM_CONTENT_TYPES = [
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data",
|
||||
"text/plain"
|
||||
];
|
||||
function createOriginCheckMiddleware() {
|
||||
return defineMiddleware((context, next) => {
|
||||
const { request, url } = context;
|
||||
if (request.method === "GET") {
|
||||
return next();
|
||||
}
|
||||
const sameOrigin = (request.method === "POST" || request.method === "PUT" || request.method === "PATCH" || request.method === "DELETE") && request.headers.get("origin") === url.origin;
|
||||
const hasContentType = request.headers.has("content-type");
|
||||
if (hasContentType) {
|
||||
const formLikeHeader = hasFormLikeHeader(request.headers.get("content-type"));
|
||||
if (formLikeHeader && !sameOrigin) {
|
||||
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
|
||||
status: 403
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!sameOrigin) {
|
||||
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
|
||||
status: 403
|
||||
});
|
||||
}
|
||||
}
|
||||
return next();
|
||||
});
|
||||
}
|
||||
function hasFormLikeHeader(contentType) {
|
||||
if (contentType) {
|
||||
for (const FORM_CONTENT_TYPE of FORM_CONTENT_TYPES) {
|
||||
if (contentType.toLowerCase().includes(FORM_CONTENT_TYPE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export {
|
||||
createOriginCheckMiddleware
|
||||
};
|
56
node_modules/astro/dist/core/app/node.d.ts
generated
vendored
Normal file
56
node_modules/astro/dist/core/app/node.d.ts
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { RouteData } from '../../@types/astro.js';
|
||||
import { App } from './index.js';
|
||||
import type { RenderOptions } from './index.js';
|
||||
import type { SSRManifest } from './types.js';
|
||||
export { apply as applyPolyfills } from '../polyfill.js';
|
||||
/**
|
||||
* Allow the request body to be explicitly overridden. For example, this
|
||||
* is used by the Express JSON middleware.
|
||||
*/
|
||||
interface NodeRequest extends IncomingMessage {
|
||||
body?: unknown;
|
||||
}
|
||||
export declare class NodeApp extends App {
|
||||
match(req: NodeRequest | Request): RouteData | undefined;
|
||||
render(request: NodeRequest | Request, options?: RenderOptions): Promise<Response>;
|
||||
/**
|
||||
* @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
|
||||
* See https://github.com/withastro/astro/pull/9199 for more information.
|
||||
*/
|
||||
render(request: NodeRequest | Request, routeData?: RouteData, locals?: object): Promise<Response>;
|
||||
/**
|
||||
* Converts a NodeJS IncomingMessage into a web standard Request.
|
||||
* ```js
|
||||
* import { NodeApp } from 'astro/app/node';
|
||||
* import { createServer } from 'node:http';
|
||||
*
|
||||
* const server = createServer(async (req, res) => {
|
||||
* const request = NodeApp.createRequest(req);
|
||||
* const response = await app.render(request);
|
||||
* await NodeApp.writeResponse(response, res);
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
static createRequest(req: NodeRequest, { skipBody }?: {
|
||||
skipBody?: boolean | undefined;
|
||||
}): Request;
|
||||
/**
|
||||
* Streams a web-standard Response into a NodeJS Server Response.
|
||||
* ```js
|
||||
* import { NodeApp } from 'astro/app/node';
|
||||
* import { createServer } from 'node:http';
|
||||
*
|
||||
* const server = createServer(async (req, res) => {
|
||||
* const request = NodeApp.createRequest(req);
|
||||
* const response = await app.render(request);
|
||||
* await NodeApp.writeResponse(response, res);
|
||||
* })
|
||||
* ```
|
||||
* @param source WhatWG Response
|
||||
* @param destination NodeJS ServerResponse
|
||||
*/
|
||||
static writeResponse(source: Response, destination: ServerResponse): Promise<ServerResponse<IncomingMessage> | undefined>;
|
||||
}
|
||||
export declare function loadManifest(rootFolder: URL): Promise<SSRManifest>;
|
||||
export declare function loadApp(rootFolder: URL): Promise<NodeApp>;
|
167
node_modules/astro/dist/core/app/node.js
generated
vendored
Normal file
167
node_modules/astro/dist/core/app/node.js
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
import fs from "node:fs";
|
||||
import { Http2ServerResponse } from "node:http2";
|
||||
import { clientAddressSymbol } from "../constants.js";
|
||||
import { deserializeManifest } from "./common.js";
|
||||
import { createOutgoingHttpHeaders } from "./createOutgoingHttpHeaders.js";
|
||||
import { App } from "./index.js";
|
||||
import { apply } from "../polyfill.js";
|
||||
class NodeApp extends App {
|
||||
match(req) {
|
||||
if (!(req instanceof Request)) {
|
||||
req = NodeApp.createRequest(req, {
|
||||
skipBody: true
|
||||
});
|
||||
}
|
||||
return super.match(req);
|
||||
}
|
||||
render(req, routeDataOrOptions, maybeLocals) {
|
||||
if (!(req instanceof Request)) {
|
||||
req = NodeApp.createRequest(req);
|
||||
}
|
||||
return super.render(req, routeDataOrOptions, maybeLocals);
|
||||
}
|
||||
/**
|
||||
* Converts a NodeJS IncomingMessage into a web standard Request.
|
||||
* ```js
|
||||
* import { NodeApp } from 'astro/app/node';
|
||||
* import { createServer } from 'node:http';
|
||||
*
|
||||
* const server = createServer(async (req, res) => {
|
||||
* const request = NodeApp.createRequest(req);
|
||||
* const response = await app.render(request);
|
||||
* await NodeApp.writeResponse(response, res);
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
static createRequest(req, { skipBody = false } = {}) {
|
||||
const isEncrypted = "encrypted" in req.socket && req.socket.encrypted;
|
||||
const getFirstForwardedValue = (multiValueHeader) => {
|
||||
return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0];
|
||||
};
|
||||
const forwardedProtocol = getFirstForwardedValue(req.headers["x-forwarded-proto"]);
|
||||
const protocol = forwardedProtocol ?? (isEncrypted ? "https" : "http");
|
||||
const forwardedHostname = getFirstForwardedValue(req.headers["x-forwarded-host"]);
|
||||
const hostname = forwardedHostname ?? req.headers.host ?? req.headers[":authority"];
|
||||
const port = getFirstForwardedValue(req.headers["x-forwarded-port"]);
|
||||
const portInHostname = typeof hostname === "string" && /:\d+$/.test(hostname);
|
||||
const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ""}`;
|
||||
const url = `${protocol}://${hostnamePort}${req.url}`;
|
||||
const options = {
|
||||
method: req.method || "GET",
|
||||
headers: makeRequestHeaders(req)
|
||||
};
|
||||
const bodyAllowed = options.method !== "HEAD" && options.method !== "GET" && skipBody === false;
|
||||
if (bodyAllowed) {
|
||||
Object.assign(options, makeRequestBody(req));
|
||||
}
|
||||
const request = new Request(url, options);
|
||||
const forwardedClientIp = getFirstForwardedValue(req.headers["x-forwarded-for"]);
|
||||
const clientIp = forwardedClientIp || req.socket?.remoteAddress;
|
||||
if (clientIp) {
|
||||
Reflect.set(request, clientAddressSymbol, clientIp);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
/**
|
||||
* Streams a web-standard Response into a NodeJS Server Response.
|
||||
* ```js
|
||||
* import { NodeApp } from 'astro/app/node';
|
||||
* import { createServer } from 'node:http';
|
||||
*
|
||||
* const server = createServer(async (req, res) => {
|
||||
* const request = NodeApp.createRequest(req);
|
||||
* const response = await app.render(request);
|
||||
* await NodeApp.writeResponse(response, res);
|
||||
* })
|
||||
* ```
|
||||
* @param source WhatWG Response
|
||||
* @param destination NodeJS ServerResponse
|
||||
*/
|
||||
static async writeResponse(source, destination) {
|
||||
const { status, headers, body, statusText } = source;
|
||||
if (!(destination instanceof Http2ServerResponse)) {
|
||||
destination.statusMessage = statusText;
|
||||
}
|
||||
destination.writeHead(status, createOutgoingHttpHeaders(headers));
|
||||
if (!body) return destination.end();
|
||||
try {
|
||||
const reader = body.getReader();
|
||||
destination.on("close", () => {
|
||||
reader.cancel().catch((err) => {
|
||||
console.error(
|
||||
`There was an uncaught error in the middle of the stream while rendering ${destination.req.url}.`,
|
||||
err
|
||||
);
|
||||
});
|
||||
});
|
||||
let result = await reader.read();
|
||||
while (!result.done) {
|
||||
destination.write(result.value);
|
||||
result = await reader.read();
|
||||
}
|
||||
destination.end();
|
||||
} catch (err) {
|
||||
destination.write("Internal server error", () => {
|
||||
err instanceof Error ? destination.destroy(err) : destination.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function makeRequestHeaders(req) {
|
||||
const headers = new Headers();
|
||||
for (const [name, value] of Object.entries(req.headers)) {
|
||||
if (value === void 0) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
headers.append(name, item);
|
||||
}
|
||||
} else {
|
||||
headers.append(name, value);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
function makeRequestBody(req) {
|
||||
if (req.body !== void 0) {
|
||||
if (typeof req.body === "string" && req.body.length > 0) {
|
||||
return { body: Buffer.from(req.body) };
|
||||
}
|
||||
if (typeof req.body === "object" && req.body !== null && Object.keys(req.body).length > 0) {
|
||||
return { body: Buffer.from(JSON.stringify(req.body)) };
|
||||
}
|
||||
if (typeof req.body === "object" && req.body !== null && typeof req.body[Symbol.asyncIterator] !== "undefined") {
|
||||
return asyncIterableToBodyProps(req.body);
|
||||
}
|
||||
}
|
||||
return asyncIterableToBodyProps(req);
|
||||
}
|
||||
function asyncIterableToBodyProps(iterable) {
|
||||
return {
|
||||
// Node uses undici for the Request implementation. Undici accepts
|
||||
// a non-standard async iterable for the body.
|
||||
// @ts-expect-error
|
||||
body: iterable,
|
||||
// The duplex property is required when using a ReadableStream or async
|
||||
// iterable for the body. The type definitions do not include the duplex
|
||||
// property because they are not up-to-date.
|
||||
duplex: "half"
|
||||
};
|
||||
}
|
||||
async function loadManifest(rootFolder) {
|
||||
const manifestFile = new URL("./manifest.json", rootFolder);
|
||||
const rawManifest = await fs.promises.readFile(manifestFile, "utf-8");
|
||||
const serializedManifest = JSON.parse(rawManifest);
|
||||
return deserializeManifest(serializedManifest);
|
||||
}
|
||||
async function loadApp(rootFolder) {
|
||||
const manifest = await loadManifest(rootFolder);
|
||||
return new NodeApp(manifest);
|
||||
}
|
||||
export {
|
||||
NodeApp,
|
||||
apply as applyPolyfills,
|
||||
loadApp,
|
||||
loadManifest
|
||||
};
|
12
node_modules/astro/dist/core/app/pipeline.d.ts
generated
vendored
Normal file
12
node_modules/astro/dist/core/app/pipeline.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ComponentInstance, ManifestData, RewritePayload, RouteData, SSRResult } from '../../@types/astro.js';
|
||||
import { Pipeline, type TryRewriteResult } from '../base-pipeline.js';
|
||||
import type { SinglePageBuiltModule } from '../build/types.js';
|
||||
export declare class AppPipeline extends Pipeline {
|
||||
#private;
|
||||
static create(manifestData: ManifestData, { logger, manifest, mode, renderers, resolve, serverLike, streaming, defaultRoutes, }: Pick<AppPipeline, 'logger' | 'manifest' | 'mode' | 'renderers' | 'resolve' | 'serverLike' | 'streaming' | 'defaultRoutes'>): AppPipeline;
|
||||
headElements(routeData: RouteData): Pick<SSRResult, 'scripts' | 'styles' | 'links'>;
|
||||
componentMetadata(): void;
|
||||
getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
|
||||
tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
|
||||
getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule>;
|
||||
}
|
106
node_modules/astro/dist/core/app/pipeline.js
generated
vendored
Normal file
106
node_modules/astro/dist/core/app/pipeline.js
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Pipeline } from "../base-pipeline.js";
|
||||
import { RedirectSinglePageBuiltModule } from "../redirects/component.js";
|
||||
import { createModuleScriptElement, createStylesheetElementSet } from "../render/ssr-element.js";
|
||||
import { findRouteToRewrite } from "../routing/rewrite.js";
|
||||
class AppPipeline extends Pipeline {
|
||||
#manifestData;
|
||||
static create(manifestData, {
|
||||
logger,
|
||||
manifest,
|
||||
mode,
|
||||
renderers,
|
||||
resolve,
|
||||
serverLike,
|
||||
streaming,
|
||||
defaultRoutes
|
||||
}) {
|
||||
const pipeline = new AppPipeline(
|
||||
logger,
|
||||
manifest,
|
||||
mode,
|
||||
renderers,
|
||||
resolve,
|
||||
serverLike,
|
||||
streaming,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
void 0,
|
||||
defaultRoutes
|
||||
);
|
||||
pipeline.#manifestData = manifestData;
|
||||
return pipeline;
|
||||
}
|
||||
headElements(routeData) {
|
||||
const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
|
||||
const links = /* @__PURE__ */ new Set();
|
||||
const scripts = /* @__PURE__ */ new Set();
|
||||
const styles = createStylesheetElementSet(routeInfo?.styles ?? []);
|
||||
for (const script of routeInfo?.scripts ?? []) {
|
||||
if ("stage" in script) {
|
||||
if (script.stage === "head-inline") {
|
||||
scripts.add({
|
||||
props: {},
|
||||
children: script.children
|
||||
});
|
||||
}
|
||||
} else {
|
||||
scripts.add(createModuleScriptElement(script));
|
||||
}
|
||||
}
|
||||
return { links, styles, scripts };
|
||||
}
|
||||
componentMetadata() {
|
||||
}
|
||||
async getComponentByRoute(routeData) {
|
||||
const module = await this.getModuleForRoute(routeData);
|
||||
return module.page();
|
||||
}
|
||||
async tryRewrite(payload, request) {
|
||||
const { newUrl, pathname, routeData } = findRouteToRewrite({
|
||||
payload,
|
||||
request,
|
||||
routes: this.manifest?.routes.map((r) => r.routeData),
|
||||
trailingSlash: this.manifest.trailingSlash,
|
||||
buildFormat: this.manifest.buildFormat,
|
||||
base: this.manifest.base
|
||||
});
|
||||
const componentInstance = await this.getComponentByRoute(routeData);
|
||||
return { newUrl, pathname, componentInstance, routeData };
|
||||
}
|
||||
async getModuleForRoute(route) {
|
||||
for (const defaultRoute of this.defaultRoutes) {
|
||||
if (route.component === defaultRoute.component) {
|
||||
return {
|
||||
page: () => Promise.resolve(defaultRoute.instance),
|
||||
renderers: []
|
||||
};
|
||||
}
|
||||
}
|
||||
if (route.type === "redirect") {
|
||||
return RedirectSinglePageBuiltModule;
|
||||
} else {
|
||||
if (this.manifest.pageMap) {
|
||||
const importComponentInstance = this.manifest.pageMap.get(route.component);
|
||||
if (!importComponentInstance) {
|
||||
throw new Error(
|
||||
`Unexpectedly unable to find a component instance for route ${route.route}`
|
||||
);
|
||||
}
|
||||
return await importComponentInstance();
|
||||
} else if (this.manifest.pageModule) {
|
||||
return this.manifest.pageModule;
|
||||
}
|
||||
throw new Error(
|
||||
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
AppPipeline
|
||||
};
|
77
node_modules/astro/dist/core/app/types.d.ts
generated
vendored
Normal file
77
node_modules/astro/dist/core/app/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { AstroMiddlewareInstance, ComponentInstance, Locales, RouteData, SSRComponentMetadata, SSRLoadedRenderer, SSRResult, SerializedRouteData } from '../../@types/astro.js';
|
||||
import type { RoutingStrategies } from '../../i18n/utils.js';
|
||||
import type { SinglePageBuiltModule } from '../build/types.js';
|
||||
export type ComponentPath = string;
|
||||
export type StylesheetAsset = {
|
||||
type: 'inline';
|
||||
content: string;
|
||||
} | {
|
||||
type: 'external';
|
||||
src: string;
|
||||
};
|
||||
export interface RouteInfo {
|
||||
routeData: RouteData;
|
||||
file: string;
|
||||
links: string[];
|
||||
scripts: ({
|
||||
children: string;
|
||||
stage: string;
|
||||
} | {
|
||||
type: 'inline' | 'external';
|
||||
value: string;
|
||||
})[];
|
||||
styles: StylesheetAsset[];
|
||||
}
|
||||
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
|
||||
routeData: SerializedRouteData;
|
||||
};
|
||||
export type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
|
||||
export type AssetsPrefix = string | ({
|
||||
fallback: string;
|
||||
} & Record<string, string>) | undefined;
|
||||
export type SSRManifest = {
|
||||
hrefRoot: string;
|
||||
adapterName: string;
|
||||
routes: RouteInfo[];
|
||||
site?: string;
|
||||
base: string;
|
||||
trailingSlash: 'always' | 'never' | 'ignore';
|
||||
buildFormat: 'file' | 'directory' | 'preserve';
|
||||
compressHTML: boolean;
|
||||
assetsPrefix?: AssetsPrefix;
|
||||
renderers: SSRLoadedRenderer[];
|
||||
/**
|
||||
* Map of directive name (e.g. `load`) to the directive script code
|
||||
*/
|
||||
clientDirectives: Map<string, string>;
|
||||
entryModules: Record<string, string>;
|
||||
inlinedScripts: Map<string, string>;
|
||||
assets: Set<string>;
|
||||
componentMetadata: SSRResult['componentMetadata'];
|
||||
pageModule?: SinglePageBuiltModule;
|
||||
pageMap?: Map<ComponentPath, ImportComponentInstance>;
|
||||
serverIslandMap?: Map<string, () => Promise<ComponentInstance>>;
|
||||
serverIslandNameMap?: Map<string, string>;
|
||||
key: Promise<CryptoKey>;
|
||||
i18n: SSRManifestI18n | undefined;
|
||||
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
|
||||
checkOrigin: boolean;
|
||||
experimentalEnvGetSecretEnabled: boolean;
|
||||
};
|
||||
export type SSRManifestI18n = {
|
||||
fallback: Record<string, string> | undefined;
|
||||
fallbackType: 'redirect' | 'rewrite';
|
||||
strategy: RoutingStrategies;
|
||||
locales: Locales;
|
||||
defaultLocale: string;
|
||||
domainLookupTable: Record<string, string>;
|
||||
};
|
||||
export type SerializedSSRManifest = Omit<SSRManifest, 'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'inlinedScripts' | 'clientDirectives' | 'serverIslandNameMap' | 'key'> & {
|
||||
routes: SerializedRouteInfo[];
|
||||
assets: string[];
|
||||
componentMetadata: [string, SSRComponentMetadata][];
|
||||
inlinedScripts: [string, string][];
|
||||
clientDirectives: [string, string][];
|
||||
serverIslandNameMap: [string, string][];
|
||||
key: string;
|
||||
};
|
0
node_modules/astro/dist/core/app/types.js
generated
vendored
Normal file
0
node_modules/astro/dist/core/app/types.js
generated
vendored
Normal file
Reference in New Issue
Block a user