feat: scaffold Astro + Tailwind project

This commit is contained in:
TerryM
2026-05-12 16:16:03 +08:00
parent 906eb5c763
commit 03d3800c6c
12097 changed files with 1266600 additions and 0 deletions

7
node_modules/astro/dist/core/app/app.d.ts generated vendored Normal file
View File

@@ -0,0 +1,7 @@
import { BaseApp, type LogRequestPayload } from './base.js';
import { AppPipeline } from './pipeline.js';
export declare class App extends BaseApp {
createPipeline(streaming: boolean): AppPipeline;
isDev(): boolean;
logRequest(_options: LogRequestPayload): void;
}

19
node_modules/astro/dist/core/app/app.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
import { BaseApp } from "./base.js";
import { AppPipeline } from "./pipeline.js";
class App extends BaseApp {
createPipeline(streaming) {
return AppPipeline.create({
manifest: this.manifest,
streaming
});
}
isDev() {
return false;
}
// Should we log something for our users?
logRequest(_options) {
}
}
export {
App
};

212
node_modules/astro/dist/core/app/base.d.ts generated vendored Normal file
View File

@@ -0,0 +1,212 @@
import type { RoutesList } from '../../types/astro.js';
import type { RemotePattern, RouteData } from '../../types/public/index.js';
import { type Pipeline } from '../base-pipeline.js';
import { getSetCookiesFromResponse } from '../cookies/index.js';
import { AstroIntegrationLogger, type AstroLogger } from '../logger/core.js';
import type { FetchHandler } from '../fetch/types.js';
import type { ErrorHandler } from '../errors/handler.js';
import type { WaitUntilHook } from '../wait-until.js';
import type { AppPipeline } from './pipeline.js';
import type { SSRManifest } from './types.js';
export interface DevMatch {
routeData: RouteData;
resolvedPathname: string;
}
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;
/**
* A custom fetch function for retrieving prerendered pages - 404 or 500.
*
* If not provided, Astro will fall back to its default behavior for fetching error pages.
*
* When a dynamic route is matched but ultimately results in a 404, this function will be used
* to fetch the prerendered 404 page if available. Similarly, it may be used to fetch a
* prerendered 500 error page when necessary.
*
* @param {ErrorPagePath} url - The URL of the prerendered 404 or 500 error page to fetch.
* @returns {Promise<Response>} A promise resolving to the prerendered response.
*/
prerenderedErrorPageFetch?: (url: ErrorPagePath) => Promise<Response>;
/**
* Optional platform hook to keep background work alive after the response is sent.
*
* Adapters can pass this through so runtime cache providers can schedule cache writes
* without blocking the response path.
*/
waitUntil?: WaitUntilHook;
/**
* **Advanced API**: you probably do not need to use this.
*
* Default: `app.match(request)`
*/
routeData?: RouteData;
}
type RequiredRenderOptions = Required<RenderOptions>;
export interface ResolvedRenderOptions {
addCookieHeader: RequiredRenderOptions['addCookieHeader'];
clientAddress: RequiredRenderOptions['clientAddress'] | undefined;
prerenderedErrorPageFetch: RequiredRenderOptions['prerenderedErrorPageFetch'] | undefined;
locals: RequiredRenderOptions['locals'] | undefined;
routeData: RequiredRenderOptions['routeData'] | undefined;
waitUntil: RequiredRenderOptions['waitUntil'] | undefined;
}
export interface RenderErrorOptions extends ResolvedRenderOptions {
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;
/**
* The pathname to use for the error page render context. If omitted, the
* error handler computes it from `request` via a short-lived `FetchState`.
*/
pathname?: string;
}
type ErrorPagePath = `${string}/404` | `${string}/500` | `${string}/404/` | `${string}/500/` | `${string}404.html` | `${string}500.html`;
export declare abstract class BaseApp<P extends Pipeline = AppPipeline> {
#private;
manifest: SSRManifest;
manifestData: {
routes: RouteData[];
};
pipeline: P;
baseWithoutTrailingSlash: string;
get logger(): AstroLogger;
get adapterLogger(): AstroIntegrationLogger;
constructor(manifest: SSRManifest, streaming?: boolean, ...args: any[]);
/**
* Override the fetch handler used to dispatch requests. Entrypoints
* call this with the default export of `virtual:astro:fetchable` to
* plug in a user-authored handler from `src/app.ts`.
*/
setFetchHandler(handler: {
fetch: FetchHandler;
}): void;
/**
* Returns the error handler strategy used by this app. Override to
* provide environment-specific behavior (dev overlay, build-time throws, etc.).
*/
protected createErrorHandler(): ErrorHandler;
abstract isDev(): boolean;
/**
* Resets the cached adapter logger so it picks up a new logger instance.
* Used by BuildApp when the logger is replaced via setOptions().
*/
protected resetAdapterLogger(): void;
getAllowedDomains(): Partial<RemotePattern>[] | undefined;
protected matchesAllowedDomains(forwardedHost: string, protocol?: string): boolean;
static validateForwardedHost(forwardedHost: string, allowedDomains?: Partial<RemotePattern>[], protocol?: string): boolean;
/**
* Creates a pipeline by reading the stored manifest
*
* @param streaming
* @param manifest
* @param args
* @private
*/
abstract createPipeline(streaming: boolean, manifest: SSRManifest, ...args: any[]): P;
set setManifestData(newManifestData: RoutesList);
removeBase(pathname: string): string;
/**
* Extracts the base-stripped, decoded pathname from a request.
* Used by adapters to compute the pathname for dev-mode route matching.
*/
getPathnameFromRequest(request: Request): string;
/**
* Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
* routes aren't returned, even if they are matched.
*
* When `allowPrerenderedRoutes` is `true`, the function returns matched prerendered routes too.
* @param request
* @param allowPrerenderedRoutes
*/
match(request: Request, allowPrerenderedRoutes?: boolean): RouteData | undefined;
/**
* A matching route function to use in the development server.
* Contrary to the `.match` function, this function resolves props and params, returning the correct
* route based on the priority, segments. It also returns the correct, resolved pathname.
* @param pathname
*/
devMatch(pathname?: string): Promise<DevMatch | undefined> | undefined;
private computePathnameFromDomain;
render(request: Request, { addCookieHeader, clientAddress, locals, prerenderedErrorPageFetch, routeData, waitUntil, }?: RenderOptions): 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;
/**
* 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.
*
* Delegates to the app's configured `ErrorHandler`. To customize behavior
* for a specific environment, override `createErrorHandler()` rather than
* this method.
*/
renderError(request: Request, options: RenderErrorOptions): Promise<Response>;
getDefaultStatusCode(routeData: RouteData, pathname: string): number;
getManifest(): SSRManifest;
logThisRequest({ pathname, method, statusCode, isRewrite, timeStart, }: {
pathname: string;
method: string;
statusCode: number;
isRewrite: boolean;
timeStart: number;
}): void;
abstract logRequest(_options: LogRequestPayload): void;
}
export type LogRequestPayload = {
/**
* The current path being rendered
*/
pathname: string;
/**
* The method of the request
*/
method: string;
/**
* The status code of the request
*/
statusCode: number;
/**
* If the current request is a rewrite
*/
isRewrite: boolean;
/**
* How long it took to render the request
*/
reqTime: number;
};
export {};

391
node_modules/astro/dist/core/app/base.js generated vendored Normal file
View File

@@ -0,0 +1,391 @@
import {
appendForwardSlash,
collapseDuplicateLeadingSlashes,
joinPaths,
prependForwardSlash,
removeTrailingForwardSlash
} from "@astrojs/internal-helpers/path";
import { matchPattern } from "@astrojs/internal-helpers/remote";
import { normalizeTheLocale } from "../../i18n/index.js";
import { PipelineFeatures } from "../base-pipeline.js";
import { ASTRO_ERROR_HEADER, clientAddressSymbol } from "../constants.js";
import { getSetCookiesFromResponse } from "../cookies/index.js";
import { AstroError, AstroErrorData } from "../errors/index.js";
import { AstroIntegrationLogger } from "../logger/core.js";
import { DefaultFetchHandler } from "../fetch/default-handler.js";
import { appSymbol } from "../constants.js";
import { DefaultErrorHandler } from "../errors/default-handler.js";
import { setRenderOptions } from "./render-options.js";
class BaseApp {
manifest;
manifestData;
pipeline;
#adapterLogger;
baseWithoutTrailingSlash;
/**
* The handler that turns incoming `Request` objects into `Response`s.
* Defaults to a `DefaultFetchHandler` pinned to this app and can be
* overridden via `setFetchHandler` — typically by the bundled
* entrypoint after importing `virtual:astro:fetchable`.
*/
#fetchHandler;
#errorHandler;
/**
* Whether a custom fetch handler (from `src/app.ts`) has been set
* via `setFetchHandler`. When false, the `DefaultFetchHandler` is
* in use and all features are implicitly active.
*/
#hasCustomFetchHandler = false;
/**
* Whether the missing-feature check has already run. We only want
* to warn once — after the first request in dev, or at build end.
*/
#featureCheckDone = false;
get logger() {
return this.pipeline.logger;
}
get adapterLogger() {
if (!this.#adapterLogger) {
this.#adapterLogger = new AstroIntegrationLogger(
this.logger.options,
this.manifest.adapterName
);
}
return this.#adapterLogger;
}
constructor(manifest, streaming = true, ...args) {
this.manifest = manifest;
this.baseWithoutTrailingSlash = removeTrailingForwardSlash(manifest.base);
this.pipeline = this.createPipeline(streaming, manifest, ...args);
this.manifestData = this.pipeline.manifestData;
this.#fetchHandler = new DefaultFetchHandler(this);
this.#errorHandler = this.createErrorHandler();
}
/**
* Override the fetch handler used to dispatch requests. Entrypoints
* call this with the default export of `virtual:astro:fetchable` to
* plug in a user-authored handler from `src/app.ts`.
*/
setFetchHandler(handler) {
this.#fetchHandler = handler;
this.#hasCustomFetchHandler = !(handler instanceof DefaultFetchHandler);
}
/**
* Returns the error handler strategy used by this app. Override to
* provide environment-specific behavior (dev overlay, build-time throws, etc.).
*/
createErrorHandler() {
return new DefaultErrorHandler(this);
}
/**
* Resets the cached adapter logger so it picks up a new logger instance.
* Used by BuildApp when the logger is replaced via setOptions().
*/
resetAdapterLogger() {
this.#adapterLogger = void 0;
}
getAllowedDomains() {
return this.manifest.allowedDomains;
}
matchesAllowedDomains(forwardedHost, protocol) {
return BaseApp.validateForwardedHost(forwardedHost, this.manifest.allowedDomains, protocol);
}
static validateForwardedHost(forwardedHost, allowedDomains, protocol) {
if (!allowedDomains || allowedDomains.length === 0) {
return false;
}
try {
const testUrl = new URL(`${protocol || "https"}://${forwardedHost}`);
return allowedDomains.some((pattern) => {
return matchPattern(testUrl, pattern);
});
} catch {
return false;
}
}
set setManifestData(newManifestData) {
this.manifestData = newManifestData;
this.pipeline.manifestData = newManifestData;
this.pipeline.rebuildRouter();
}
removeBase(pathname) {
pathname = collapseDuplicateLeadingSlashes(pathname);
if (pathname.startsWith(this.manifest.base)) {
return pathname.slice(this.baseWithoutTrailingSlash.length + 1);
}
return pathname;
}
/**
* Extracts the base-stripped, decoded pathname from a request.
* Used by adapters to compute the pathname for dev-mode route matching.
*/
getPathnameFromRequest(request) {
const url = new URL(request.url);
const pathname = prependForwardSlash(this.removeBase(url.pathname));
try {
return decodeURI(pathname);
} catch (e) {
this.adapterLogger.error(e.toString());
return pathname;
}
}
/**
* Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
* routes aren't returned, even if they are matched.
*
* When `allowPrerenderedRoutes` is `true`, the function returns matched prerendered routes too.
* @param request
* @param allowPrerenderedRoutes
*/
match(request, allowPrerenderedRoutes = false) {
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));
}
const routeData = this.pipeline.matchRoute(decodeURI(pathname));
if (!routeData) return void 0;
if (allowPrerenderedRoutes) {
return routeData;
}
if (routeData.prerender) {
return void 0;
}
return routeData;
}
/**
* A matching route function to use in the development server.
* Contrary to the `.match` function, this function resolves props and params, returning the correct
* route based on the priority, segments. It also returns the correct, resolved pathname.
* @param pathname
*/
devMatch(pathname) {
pathname;
return void 0;
}
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 (this.manifest.trailingSlash === "always") {
pathname = appendForwardSlash(pathname);
} else if (this.manifest.trailingSlash === "never") {
pathname = removeTrailingForwardSlash(pathname);
} else 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, {
addCookieHeader = false,
clientAddress = Reflect.get(request, clientAddressSymbol),
locals,
prerenderedErrorPageFetch = fetch,
routeData,
waitUntil
} = {}) {
await this.pipeline.getLogger();
if (routeData) {
this.logger.debug(
"router",
"The adapter " + this.manifest.adapterName + " provided a custom RouteData for ",
request.url
);
this.logger.debug("router", "RouteData");
this.logger.debug("router", routeData);
}
if (locals) {
if (typeof locals !== "object") {
const error = new AstroError(AstroErrorData.LocalsNotAnObject);
this.logger.error(null, error.stack);
return this.renderError(request, {
addCookieHeader,
clientAddress,
prerenderedErrorPageFetch,
// If locals are invalid, we don't want to include them when
// rendering the error page
locals: void 0,
routeData,
waitUntil,
status: 500,
error
});
}
}
if (!routeData) {
const domainPathname = this.computePathnameFromDomain(request);
if (domainPathname) {
routeData = this.pipeline.matchRoute(decodeURI(domainPathname));
}
}
const resolvedOptions = {
addCookieHeader,
clientAddress,
prerenderedErrorPageFetch,
locals,
routeData,
waitUntil
};
let response;
if (this.#fetchHandler instanceof DefaultFetchHandler) {
Reflect.set(request, appSymbol, this);
response = await this.#fetchHandler.renderWithOptions(request, resolvedOptions);
} else {
setRenderOptions(request, resolvedOptions);
Reflect.set(request, appSymbol, this);
response = await this.#fetchHandler.fetch(request);
}
this.#warnMissingFeatures();
if (response.headers.get(ASTRO_ERROR_HEADER)) {
response.headers.delete(ASTRO_ERROR_HEADER);
return this.renderError(request, {
addCookieHeader,
clientAddress,
prerenderedErrorPageFetch,
locals,
routeData,
waitUntil,
response,
status: response.status,
error: response.status === 500 ? null : void 0
});
}
return response;
}
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.
*
* Delegates to the app's configured `ErrorHandler`. To customize behavior
* for a specific environment, override `createErrorHandler()` rather than
* this method.
*/
async renderError(request, options) {
return this.#errorHandler.renderError(request, options);
}
/**
* One-shot check: after the first request with a custom `src/app.ts`,
* compare `usedFeatures` against the manifest and warn about any
* configured features the user's pipeline doesn't call.
*/
#warnMissingFeatures() {
if (this.#featureCheckDone || !this.#hasCustomFetchHandler) return;
this.#featureCheckDone = true;
const manifest = this.manifest;
const missing = [];
const used = this.pipeline.usedFeatures;
if (manifest.routes.some((r) => r.routeData.type === "redirect") && !(used & PipelineFeatures.redirects)) {
missing.push("redirects");
}
if (manifest.sessionConfig && !(used & PipelineFeatures.sessions)) {
missing.push("sessions");
}
if (manifest.actions && !(used & PipelineFeatures.actions)) {
missing.push("actions");
}
if (manifest.middleware && !(used & PipelineFeatures.middleware)) {
missing.push("middleware");
}
if (manifest.i18n && manifest.i18n.strategy !== "manual" && !(used & PipelineFeatures.i18n)) {
missing.push("i18n");
}
if (manifest.cacheConfig && !(used & PipelineFeatures.cache)) {
missing.push("cache");
}
for (const feature of missing) {
this.logger.warn(
"router",
`Your project uses ${feature}, but your custom src/app.ts does not call the ${feature}() handler. This feature will not work unless you add it to your app.ts pipeline.`
);
}
}
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;
}
getManifest() {
return this.pipeline.manifest;
}
logThisRequest({
pathname,
method,
statusCode,
isRewrite,
timeStart
}) {
const timeEnd = performance.now();
this.logRequest({
pathname,
method,
statusCode,
isRewrite,
reqTime: timeEnd - timeStart
});
}
}
export {
BaseApp
};

6
node_modules/astro/dist/core/app/common.d.ts generated vendored Normal file
View File

@@ -0,0 +1,6 @@
import type { AstroConfig } from '../../types/public/index.js';
import type { SSRManifest } from './types.js';
export type RoutingStrategies = 'manual' | 'pathname-prefix-always' | 'pathname-prefix-other-locales' | 'pathname-prefix-always-no-redirect' | 'domains-prefix-always' | 'domains-prefix-other-locales' | 'domains-prefix-always-no-redirect';
export declare function toRoutingStrategy(routing: NonNullable<AstroConfig['i18n']>['routing'], domains: NonNullable<AstroConfig['i18n']>['domains']): RoutingStrategies;
export declare function toFallbackType(routing: NonNullable<AstroConfig['i18n']>['routing']): 'redirect' | 'rewrite';
export declare function fromRoutingStrategy(strategy: RoutingStrategies, fallbackType: NonNullable<SSRManifest['i18n']>['fallbackType']): NonNullable<AstroConfig['i18n']>['routing'];

64
node_modules/astro/dist/core/app/common.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
function toRoutingStrategy(routing, domains) {
let strategy;
const hasDomains = domains ? Object.keys(domains).length > 0 : false;
if (routing === "manual") {
strategy = "manual";
} else {
if (!hasDomains) {
if (routing?.prefixDefaultLocale === true) {
if (routing.redirectToDefaultLocale) {
strategy = "pathname-prefix-always";
} else {
strategy = "pathname-prefix-always-no-redirect";
}
} else {
strategy = "pathname-prefix-other-locales";
}
} else {
if (routing?.prefixDefaultLocale === true) {
if (routing.redirectToDefaultLocale) {
strategy = "domains-prefix-always";
} else {
strategy = "domains-prefix-always-no-redirect";
}
} else {
strategy = "domains-prefix-other-locales";
}
}
}
return strategy;
}
function toFallbackType(routing) {
if (routing === "manual") {
return "rewrite";
}
return routing.fallbackType;
}
const PREFIX_DEFAULT_LOCALE = /* @__PURE__ */ new Set([
"pathname-prefix-always",
"domains-prefix-always",
"pathname-prefix-always-no-redirect",
"domains-prefix-always-no-redirect"
]);
const REDIRECT_TO_DEFAULT_LOCALE = /* @__PURE__ */ new Set([
"pathname-prefix-always-no-redirect",
"domains-prefix-always-no-redirect"
]);
function fromRoutingStrategy(strategy, fallbackType) {
let routing;
if (strategy === "manual") {
routing = "manual";
} else {
routing = {
prefixDefaultLocale: PREFIX_DEFAULT_LOCALE.has(strategy),
redirectToDefaultLocale: !REDIRECT_TO_DEFAULT_LOCALE.has(strategy),
fallbackType
};
}
return routing;
}
export {
fromRoutingStrategy,
toFallbackType,
toRoutingStrategy
};

View 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;

View 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
};

26
node_modules/astro/dist/core/app/dev/app.d.ts generated vendored Normal file
View File

@@ -0,0 +1,26 @@
import type { RouteData } from '../../../types/public/index.js';
import type { ErrorHandler } from '../../errors/handler.js';
import type { AstroLogger } from '../../logger/core.js';
import { BaseApp, type DevMatch, type LogRequestPayload } from '../base.js';
import type { SSRManifest } from '../types.js';
import { NonRunnablePipeline } from './pipeline.js';
import type { RoutesList } from '../../../types/astro.js';
export declare class DevApp extends BaseApp<NonRunnablePipeline> {
constructor(manifest: SSRManifest, streaming: boolean | undefined, logger: AstroLogger);
createPipeline(streaming: boolean, manifest: SSRManifest, logger: AstroLogger): NonRunnablePipeline;
isDev(): boolean;
/**
* Clears the cached middleware so it is re-resolved on the next request.
* Called via HMR when middleware files change.
*/
clearMiddleware(): void;
/**
* Updates the routes list when files change during development.
* Called via HMR when new pages are added/removed.
*/
updateRoutes(newRoutesList: RoutesList): void;
match(request: Request): RouteData | undefined;
devMatch(pathname: string): Promise<DevMatch | undefined>;
protected createErrorHandler(): ErrorHandler;
logRequest({ pathname, method, statusCode, isRewrite, reqTime }: LogRequestPayload): void;
}

73
node_modules/astro/dist/core/app/dev/app.js generated vendored Normal file
View File

@@ -0,0 +1,73 @@
import { DevErrorHandler } from "../../errors/dev-handler.js";
import { BaseApp } from "../base.js";
import { NonRunnablePipeline } from "./pipeline.js";
import { ensure404Route } from "../../routing/astro-designed-error-pages.js";
import { matchRoute } from "../../routing/dev.js";
import { req } from "../../messages/runtime.js";
class DevApp extends BaseApp {
constructor(manifest, streaming = true, logger) {
super(manifest, streaming, logger);
}
createPipeline(streaming, manifest, logger) {
return NonRunnablePipeline.create({
logger,
manifest,
streaming
});
}
isDev() {
return true;
}
/**
* Clears the cached middleware so it is re-resolved on the next request.
* Called via HMR when middleware files change.
*/
clearMiddleware() {
this.pipeline.clearMiddleware();
}
/**
* Updates the routes list when files change during development.
* Called via HMR when new pages are added/removed.
*/
updateRoutes(newRoutesList) {
this.manifestData = newRoutesList;
ensure404Route(this.manifestData);
}
match(request) {
return super.match(request, true);
}
async devMatch(pathname) {
const matchedRoute = await matchRoute(
pathname,
this.manifestData,
this.pipeline,
this.manifest
);
if (!matchedRoute) return void 0;
return {
routeData: matchedRoute.route,
resolvedPathname: matchedRoute.resolvedPathname
};
}
createErrorHandler() {
return new DevErrorHandler(this, { shouldInjectCspMetaTags: false });
}
logRequest({ pathname, method, statusCode, isRewrite, reqTime }) {
if (pathname === "/favicon.ico") {
return;
}
this.logger.info(
null,
req({
url: pathname,
method,
statusCode,
isRewrite,
reqTime
})
);
}
}
export {
DevApp
};

16
node_modules/astro/dist/core/app/dev/pipeline.d.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
import type { ComponentInstance } from '../../../types/astro.js';
import type { RewritePayload, RouteData } from '../../../types/public/index.js';
import { type HeadElements, Pipeline, type TryRewriteResult } from '../../base-pipeline.js';
type DevPipelineCreate = Pick<NonRunnablePipeline, 'logger' | 'manifest' | 'streaming'>;
/**
* A pipeline that can't load modules at runtime using the vite environment APIs
*/
export declare class NonRunnablePipeline extends Pipeline {
getName(): string;
static create({ logger, manifest, streaming }: DevPipelineCreate): NonRunnablePipeline;
headElements(routeData: RouteData): Promise<HeadElements>;
componentMetadata(): void;
getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
}
export {};

137
node_modules/astro/dist/core/app/dev/pipeline.js generated vendored Normal file
View File

@@ -0,0 +1,137 @@
import { Pipeline } from "../../base-pipeline.js";
import { ASTRO_VERSION } from "../../constants.js";
import { createModuleScriptElement, createStylesheetElementSet } from "../../render/ssr-element.js";
import { findRouteToRewrite } from "../../routing/rewrite.js";
import { newNodePool } from "../../../runtime/server/render/queue/pool.js";
import { HTMLStringCache } from "../../../runtime/server/html-string-cache.js";
import { queueRenderingEnabled } from "../manifest.js";
class NonRunnablePipeline extends Pipeline {
getName() {
return "NonRunnablePipeline";
}
static create({ logger, manifest, streaming }) {
async function resolve(specifier) {
if (specifier.startsWith("/")) {
return specifier;
} else {
return "/@id/" + specifier;
}
}
const pipeline = new NonRunnablePipeline(
logger,
manifest,
"development",
manifest.renderers,
resolve,
streaming,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0
);
if (queueRenderingEnabled(manifest.experimentalQueuedRendering)) {
pipeline.nodePool = newNodePool(manifest.experimentalQueuedRendering);
if (manifest.experimentalQueuedRendering.contentCache) {
pipeline.htmlStringCache = new HTMLStringCache(1e3);
}
}
return pipeline;
}
async headElements(routeData) {
const { componentMetadataEntries } = await import("virtual:astro:component-metadata");
for (const [id, entry] of componentMetadataEntries) {
this.manifest.componentMetadata.set(id, entry);
}
const { assetsPrefix, base } = this.manifest;
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 ?? [], base, assetsPrefix);
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));
}
}
scripts.add({
props: { type: "module", src: "/@vite/client" },
children: ""
});
if (this.manifest.devToolbar.enabled) {
scripts.add({
props: {
type: "module",
src: "/@id/astro/runtime/client/dev-toolbar/entrypoint.js"
},
children: ""
});
const additionalMetadata = {
root: this.manifest.rootDir.toString(),
version: ASTRO_VERSION,
latestAstroVersion: this.manifest.devToolbar.latestAstroVersion,
debugInfo: this.manifest.devToolbar.debugInfoOutput ?? "",
placement: this.manifest.devToolbar.placement
};
const children = `window.__astro_dev_toolbar__ = ${JSON.stringify(additionalMetadata)}`;
scripts.add({ props: {}, children });
}
const { devCSSMap } = await import("virtual:astro:dev-css-all");
const importer = devCSSMap.get(routeData.component);
let css = /* @__PURE__ */ new Set();
if (importer) {
const cssModule = await importer();
css = cssModule.css;
} else {
this.logger.warn(
"assets",
`Unable to find CSS for ${routeData.component}. This is likely a bug in Astro.`
);
}
for (const { id, url: src, content } of css) {
scripts.add({ props: { type: "module", src }, children: "" });
styles.add({ props: { "data-vite-dev-id": id }, children: content });
}
return { scripts, styles, links };
}
componentMetadata() {
}
async getComponentByRoute(routeData) {
try {
const module2 = await this.getModuleForRoute(routeData);
return module2.page();
} catch {
}
const url = new URL(routeData.component, this.manifest.rootDir);
const module = await import(
/* @vite-ignore */
url.toString()
);
return module;
}
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,
outDir: this.manifest?.serverLike ? this.manifest.buildClientDir : this.manifest.outDir
});
const componentInstance = await this.getComponentByRoute(routeData);
return { newUrl, pathname, componentInstance, routeData };
}
}
export {
NonRunnablePipeline
};

View File

@@ -0,0 +1,7 @@
export type { RoutesList } from '../../../types/astro.js';
export { App } from '../app.js';
export { BaseApp, type RenderErrorOptions, type RenderOptions, type LogRequestPayload, } from '../base.js';
export { fromRoutingStrategy, toRoutingStrategy } from '../common.js';
export { createConsoleLogger } from '../../logger/impls/console.js';
export { deserializeManifest, deserializeRouteData, deserializeRouteInfo, serializeRouteData, serializeRouteInfo, } from '../manifest.js';
export { AppPipeline } from '../pipeline.js';

27
node_modules/astro/dist/core/app/entrypoints/index.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
import { App } from "../app.js";
import {
BaseApp
} from "../base.js";
import { fromRoutingStrategy, toRoutingStrategy } from "../common.js";
import { createConsoleLogger } from "../../logger/impls/console.js";
import {
deserializeManifest,
deserializeRouteData,
deserializeRouteInfo,
serializeRouteData,
serializeRouteInfo
} from "../manifest.js";
import { AppPipeline } from "../pipeline.js";
export {
App,
AppPipeline,
BaseApp,
createConsoleLogger,
deserializeManifest,
deserializeRouteData,
deserializeRouteInfo,
fromRoutingStrategy,
serializeRouteData,
serializeRouteInfo,
toRoutingStrategy
};

View File

@@ -0,0 +1 @@
export { type SerializedRouteData, deserializeManifest, deserializeRouteData, deserializeRouteInfo, serializeRouteData, serializeRouteInfo, } from '../manifest.js';

View File

@@ -0,0 +1,14 @@
import {
deserializeManifest,
deserializeRouteData,
deserializeRouteInfo,
serializeRouteData,
serializeRouteInfo
} from "../manifest.js";
export {
deserializeManifest,
deserializeRouteData,
deserializeRouteInfo,
serializeRouteData,
serializeRouteInfo
};

View File

@@ -0,0 +1 @@
export { NodeApp, loadApp, loadManifest, createRequest, writeResponse, getAbortControllerCleanup, } from '../node.js';

16
node_modules/astro/dist/core/app/entrypoints/node.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
import {
NodeApp,
loadApp,
loadManifest,
createRequest,
writeResponse,
getAbortControllerCleanup
} from "../node.js";
export {
NodeApp,
createRequest,
getAbortControllerCleanup,
loadApp,
loadManifest,
writeResponse
};

View File

@@ -0,0 +1,2 @@
import type { CreateApp } from '../../types.js';
export declare const createApp: CreateApp;

View File

@@ -0,0 +1,37 @@
import fetchable from "virtual:astro:fetchable";
import { manifest } from "virtual:astro:manifest";
import { DevApp } from "../../dev/app.js";
import { createConsoleLogger } from "../../../logger/impls/console.js";
let currentDevApp = null;
const createApp = ({ streaming } = {}) => {
const logger = createConsoleLogger(manifest.logLevel);
currentDevApp = new DevApp(manifest, streaming, logger);
currentDevApp.setFetchHandler(fetchable);
if (import.meta.hot) {
import.meta.hot.on("astro:routes-updated", async () => {
if (!currentDevApp) return;
try {
const { routes: newRoutes } = await import("virtual:astro:routes");
const newRoutesList = {
routes: newRoutes.map((r) => r.routeData)
};
currentDevApp.updateRoutes(newRoutesList);
} catch (e) {
logger.error("router", `Failed to update routes via HMR:
${e}`);
}
});
import.meta.hot.on("astro:content-changed", () => {
if (!currentDevApp) return;
currentDevApp.pipeline.routeCache.clearAll();
});
import.meta.hot.on("astro:middleware-updated", () => {
if (!currentDevApp) return;
currentDevApp.clearMiddleware();
});
}
return currentDevApp;
};
export {
createApp
};

View File

@@ -0,0 +1,2 @@
import type { CreateApp } from '../../types.js';
export declare const createApp: CreateApp;

View File

@@ -0,0 +1,5 @@
import { createApp as _createApp } from "virtual:astro:app";
const createApp = _createApp;
export {
createApp
};

View File

@@ -0,0 +1,2 @@
import type { CreateApp } from '../../types.js';
export declare const createApp: CreateApp;

View File

@@ -0,0 +1,11 @@
import fetchable from "virtual:astro:fetchable";
import { manifest } from "virtual:astro:manifest";
import { App } from "../../app.js";
const createApp = ({ streaming } = {}) => {
const app = new App(manifest, streaming);
app.setFetchHandler(fetchable);
return app;
};
export {
createApp
};

12
node_modules/astro/dist/core/app/manifest.d.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
import type { SerializedRouteData } from '../../types/astro.js';
import type { AstroConfig, RouteData } from '../../types/public/index.js';
import type { RoutesList } from '../../types/astro.js';
import type { RouteInfo, SerializedSSRManifest, SSRManifest, SerializedRouteInfo } from './types.js';
export type { SerializedRouteData } from '../../types/astro.js';
export declare function deserializeManifest(serializedManifest: SerializedSSRManifest, routesList?: RoutesList): SSRManifest;
export declare function serializeRouteData(routeData: RouteData, trailingSlash: AstroConfig['trailingSlash']): SerializedRouteData;
export declare function deserializeRouteData(rawRouteData: SerializedRouteData): RouteData;
export declare function serializeRouteInfo(routeInfo: RouteInfo, trailingSlash: AstroConfig['trailingSlash']): SerializedRouteInfo;
export declare function deserializeRouteInfo(rawRouteInfo: SerializedRouteInfo): RouteInfo;
export declare function queuePoolSize(config: NonNullable<SSRManifest['experimentalQueuedRendering']>): number;
export declare function queueRenderingEnabled(config: SSRManifest['experimentalQueuedRendering']): boolean;

117
node_modules/astro/dist/core/app/manifest.js generated vendored Normal file
View File

@@ -0,0 +1,117 @@
import { decodeKey } from "../encryption.js";
import { NOOP_MIDDLEWARE_FN } from "../middleware/noop-middleware.js";
function deserializeManifest(serializedManifest, routesList) {
const routes = [];
if (serializedManifest.routes) {
for (const serializedRoute of serializedManifest.routes) {
routes.push({
...serializedRoute,
routeData: deserializeRouteData(serializedRoute.routeData)
});
const route = serializedRoute;
route.routeData = deserializeRouteData(serializedRoute.routeData);
}
}
if (routesList) {
for (const route of routesList?.routes) {
routes.push({
file: "",
links: [],
scripts: [],
styles: [],
routeData: route
});
}
}
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 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,
rootDir: new URL(serializedManifest.rootDir),
srcDir: new URL(serializedManifest.srcDir),
publicDir: new URL(serializedManifest.publicDir),
outDir: new URL(serializedManifest.outDir),
cacheDir: new URL(serializedManifest.cacheDir),
buildClientDir: new URL(serializedManifest.buildClientDir),
buildServerDir: new URL(serializedManifest.buildServerDir),
assets,
componentMetadata,
inlinedScripts,
clientDirectives,
routes,
key
};
}
function serializeRouteData(routeData, trailingSlash) {
return {
...routeData,
pattern: routeData.pattern.source,
redirectRoute: routeData.redirectRoute ? serializeRouteData(routeData.redirectRoute, trailingSlash) : void 0,
fallbackRoutes: routeData.fallbackRoutes.map((fallbackRoute) => {
return serializeRouteData(fallbackRoute, trailingSlash);
}),
_meta: { trailingSlash }
};
}
function deserializeRouteData(rawRouteData) {
return {
route: rawRouteData.route,
type: rawRouteData.type,
// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
// This pattern is serialized from Astro's own route manifest.
pattern: new RegExp(rawRouteData.pattern),
params: rawRouteData.params,
component: rawRouteData.component,
pathname: rawRouteData.pathname || void 0,
segments: rawRouteData.segments,
prerender: rawRouteData.prerender,
redirect: rawRouteData.redirect,
redirectRoute: rawRouteData.redirectRoute ? deserializeRouteData(rawRouteData.redirectRoute) : void 0,
fallbackRoutes: rawRouteData.fallbackRoutes.map((fallback) => {
return deserializeRouteData(fallback);
}),
isIndex: rawRouteData.isIndex,
origin: rawRouteData.origin,
distURL: rawRouteData.distURL
};
}
function serializeRouteInfo(routeInfo, trailingSlash) {
return {
styles: routeInfo.styles,
file: routeInfo.file,
links: routeInfo.links,
scripts: routeInfo.scripts,
routeData: serializeRouteData(routeInfo.routeData, trailingSlash)
};
}
function deserializeRouteInfo(rawRouteInfo) {
return {
styles: rawRouteInfo.styles,
file: rawRouteInfo.file,
links: rawRouteInfo.links,
scripts: rawRouteInfo.scripts,
routeData: deserializeRouteData(rawRouteInfo.routeData)
};
}
function queuePoolSize(config) {
return config?.poolSize ?? 1e3;
}
function queueRenderingEnabled(config) {
return config?.enabled ?? false;
}
export {
deserializeManifest,
deserializeRouteData,
deserializeRouteInfo,
queuePoolSize,
queueRenderingEnabled,
serializeRouteData,
serializeRouteInfo
};

8
node_modules/astro/dist/core/app/middlewares.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import type { MiddlewareHandler } from '../../types/public/common.js';
/**
* Returns a middleware function in charge to check the `origin` header.
*
* @private
*/
export declare function createOriginCheckMiddleware(): MiddlewareHandler;
export declare function hasFormLikeHeader(contentType: string | null): boolean;

49
node_modules/astro/dist/core/app/middlewares.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
import { defineMiddleware } from "../middleware/defineMiddleware.js";
const FORM_CONTENT_TYPES = [
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain"
];
const SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
function createOriginCheckMiddleware() {
return defineMiddleware((context, next) => {
const { request, url, isPrerendered } = context;
if (isPrerendered) {
return next();
}
if (SAFE_METHODS.includes(request.method)) {
return next();
}
const isSameOrigin = 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 && !isSameOrigin) {
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
status: 403
});
}
} else {
if (!isSameOrigin) {
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,
hasFormLikeHeader
};

114
node_modules/astro/dist/core/app/node.d.ts generated vendored Normal file
View File

@@ -0,0 +1,114 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { RemotePattern } from '../../types/public/config.js';
import type { RenderOptions } from './base.js';
import { App } from './app.js';
import type { NodeAppHeadersJson, SSRManifest } from './types.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;
}
/**
* Converts a NodeJS IncomingMessage into a web standard Request.
* ```js
* import { createApp } from 'astro/app/entrypoint';
* import { createRequest } from 'astro/app/node';
* import { createServer } from 'node:http';
*
* const app = createApp();
*
* const server = createServer(async (req, res) => {
* const request = createRequest(req);
* const response = await app.render(request);
* })
* ```
*/
export declare function createRequest(req: NodeRequest, { skipBody, allowedDomains, bodySizeLimit, port: serverPort, }?: {
skipBody?: boolean;
allowedDomains?: Partial<RemotePattern>[];
bodySizeLimit?: number;
port?: number;
}): Request;
/**
* Streams a web-standard Response into a NodeJS Server Response.
* ```js
* import { createApp } from 'astro/app/entrypoint';
* import { createRequest, writeResponse } from 'astro/app/node';
* import { createServer } from 'node:http';
*
* const app = createApp();
*
* const server = createServer(async (req, res) => {
* const request = createRequest(req);
* const response = await app.render(request);
* await writeResponse(response, res);
* })
* ```
* @param source WhatWG Response
* @param destination NodeJS ServerResponse
*/
export declare function writeResponse(source: Response, destination: ServerResponse): Promise<ServerResponse<IncomingMessage> | undefined>;
/**
* @deprecated Use `App` or `createApp()` instead, and use in conjunction with `convertRequest()`
* and `writeResponse()` helpers. This will be removed in a future major version.
*/
export declare class NodeApp extends App {
headersMap: NodeAppHeadersJson | undefined;
setHeadersMap(headers: NodeAppHeadersJson): void;
match(req: NodeRequest | Request, allowPrerenderedRoutes?: boolean): import("../../index.js").RouteData | undefined;
render(request: NodeRequest | Request, options?: RenderOptions): 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: typeof createRequest;
/**
* 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: typeof writeResponse;
}
/**
* Returns the cleanup function for the AbortController and socket listeners created by `createRequest()`
* for the NodeJS IncomingMessage. This should only be called directly if the request is not
* being handled by Astro, i.e. if not calling `writeResponse()` after `createRequest()`.
* ```js
* import { createRequest, getAbortControllerCleanup } from 'astro/app/node';
* import { createServer } from 'node:http';
*
* const server = createServer(async (req, res) => {
* const request = createRequest(req);
* const cleanup = getAbortControllerCleanup(req);
* if (cleanup) cleanup();
* // can now safely call another handler
* })
* ```
*/
export declare function getAbortControllerCleanup(req?: NodeRequest): (() => void) | undefined;
/** @deprecated This will be removed in a future major version. */
export declare function loadManifest(rootFolder: URL): Promise<SSRManifest>;
/** @deprecated This will be removed in a future major version. */
export declare function loadApp(rootFolder: URL): Promise<NodeApp>;
export {};

287
node_modules/astro/dist/core/app/node.js generated vendored Normal file
View File

@@ -0,0 +1,287 @@
import fs from "node:fs";
import { Http2ServerResponse } from "node:http2";
import { clientAddressSymbol, nodeRequestAbortControllerCleanupSymbol } from "../constants.js";
import { deserializeManifest } from "./manifest.js";
import { createOutgoingHttpHeaders } from "./createOutgoingHttpHeaders.js";
import { App } from "./app.js";
import {
getFirstForwardedValue,
validateForwardedHeaders,
validateHost
} from "./validate-headers.js";
function createRequest(req, {
skipBody = false,
allowedDomains = [],
bodySizeLimit,
port: serverPort
} = {}) {
const controller = new AbortController();
const isEncrypted = "encrypted" in req.socket && req.socket.encrypted;
const providedProtocol = isEncrypted ? "https" : "http";
const untrustedHostname = req.headers.host ?? req.headers[":authority"];
const validated = validateForwardedHeaders(
getFirstForwardedValue(req.headers["x-forwarded-proto"]),
getFirstForwardedValue(req.headers["x-forwarded-host"]),
getFirstForwardedValue(req.headers["x-forwarded-port"]),
allowedDomains
);
const protocol = validated.protocol ?? providedProtocol;
const validatedHostname = validateHost(
typeof untrustedHostname === "string" ? untrustedHostname : void 0,
protocol,
allowedDomains
);
const hostname = validated.host ?? validatedHostname ?? "localhost";
const port = validated.port ?? (!validated.host && !validatedHostname && serverPort ? String(serverPort) : void 0);
let url;
try {
const hostnamePort = getHostnamePort(hostname, port);
url = new URL(`${protocol}://${hostnamePort}${req.url}`);
} catch {
const hostnamePort = getHostnamePort(hostname, port);
url = new URL(`${protocol}://${hostnamePort}`);
}
const options = {
method: req.method || "GET",
headers: makeRequestHeaders(req),
signal: controller.signal
};
const bodyAllowed = options.method !== "HEAD" && options.method !== "GET" && skipBody === false;
if (bodyAllowed) {
Object.assign(options, makeRequestBody(req, bodySizeLimit));
}
const request = new Request(url, options);
const socket = getRequestSocket(req);
if (socket && typeof socket.on === "function") {
const existingCleanup = getAbortControllerCleanup(req);
if (existingCleanup) {
existingCleanup();
}
let cleanedUp = false;
const removeSocketListener = () => {
if (typeof socket.off === "function") {
socket.off("close", onSocketClose);
} else if (typeof socket.removeListener === "function") {
socket.removeListener("close", onSocketClose);
}
};
const cleanup = () => {
if (cleanedUp) return;
cleanedUp = true;
removeSocketListener();
controller.signal.removeEventListener("abort", cleanup);
Reflect.deleteProperty(req, nodeRequestAbortControllerCleanupSymbol);
};
const onSocketClose = () => {
cleanup();
if (!controller.signal.aborted) {
controller.abort();
}
};
socket.on("close", onSocketClose);
controller.signal.addEventListener("abort", cleanup, { once: true });
Reflect.set(req, nodeRequestAbortControllerCleanupSymbol, cleanup);
if (socket.destroyed) {
onSocketClose();
}
}
const hostValidated = validated.host !== void 0 || validatedHostname !== void 0;
const forwardedClientIp = hostValidated ? getFirstForwardedValue(req.headers["x-forwarded-for"]) : void 0;
const clientIp = forwardedClientIp || req.socket?.remoteAddress;
if (clientIp) {
Reflect.set(request, clientAddressSymbol, clientIp);
}
return request;
}
async function writeResponse(source, destination) {
const { status, headers, body, statusText } = source;
if (!(destination instanceof Http2ServerResponse)) {
destination.statusMessage = statusText;
}
destination.writeHead(status, createOutgoingHttpHeaders(headers));
const cleanupAbortFromDestination = getAbortControllerCleanup(
destination.req ?? void 0
);
if (cleanupAbortFromDestination) {
const runCleanup = () => {
cleanupAbortFromDestination();
if (typeof destination.off === "function") {
destination.off("finish", runCleanup);
destination.off("close", runCleanup);
} else {
destination.removeListener?.("finish", runCleanup);
destination.removeListener?.("close", runCleanup);
}
};
destination.on("finish", runCleanup);
destination.on("close", runCleanup);
}
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 %s.",
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();
});
}
}
class NodeApp extends App {
headersMap = void 0;
setHeadersMap(headers) {
this.headersMap = headers;
}
match(req, allowPrerenderedRoutes = false) {
if (!(req instanceof Request)) {
req = createRequest(req, {
skipBody: true,
allowedDomains: this.manifest.allowedDomains
});
}
return super.match(req, allowPrerenderedRoutes);
}
render(request, options) {
if (!(request instanceof Request)) {
request = createRequest(request, {
allowedDomains: this.manifest.allowedDomains
});
}
return super.render(request, options);
}
/**
* 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 = createRequest;
/**
* 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 = writeResponse;
}
function getHostnamePort(hostname, port) {
const portInHostname = typeof hostname === "string" && /:\d+$/.test(hostname);
const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ""}`;
return hostnamePort;
}
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, bodySizeLimit) {
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, bodySizeLimit);
}
}
return asyncIterableToBodyProps(req, bodySizeLimit);
}
function asyncIterableToBodyProps(iterable, bodySizeLimit) {
const source = bodySizeLimit != null ? limitAsyncIterable(iterable, bodySizeLimit) : iterable;
return {
// Node uses undici for the Request implementation. Undici accepts
// a non-standard async iterable for the body.
// @ts-expect-error
body: source,
// 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* limitAsyncIterable(iterable, limit) {
let received = 0;
for await (const chunk of iterable) {
const byteLength = chunk instanceof Uint8Array ? chunk.byteLength : typeof chunk === "string" ? Buffer.byteLength(chunk) : 0;
received += byteLength;
if (received > limit) {
throw new Error(`Body size limit exceeded: received more than ${limit} bytes`);
}
yield chunk;
}
}
function getAbortControllerCleanup(req) {
if (!req) return void 0;
const cleanup = Reflect.get(req, nodeRequestAbortControllerCleanupSymbol);
return typeof cleanup === "function" ? cleanup : void 0;
}
function getRequestSocket(req) {
if (req.socket && typeof req.socket.on === "function") {
return req.socket;
}
const http2Socket = req.stream?.session?.socket;
if (http2Socket && typeof http2Socket.on === "function") {
return http2Socket;
}
return void 0;
}
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,
createRequest,
getAbortControllerCleanup,
loadApp,
loadManifest,
writeResponse
};

14
node_modules/astro/dist/core/app/pipeline.d.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { ComponentInstance } from '../../types/astro.js';
import type { RewritePayload } from '../../types/public/common.js';
import type { RouteData } from '../../types/public/internal.js';
import { type HeadElements, Pipeline, type TryRewriteResult } from '../base-pipeline.js';
import type { SinglePageBuiltModule } from '../build/types.js';
export declare class AppPipeline extends Pipeline {
getName(): string;
static create({ manifest, streaming }: Pick<AppPipeline, 'manifest' | 'streaming'>): AppPipeline;
headElements(routeData: RouteData): Promise<HeadElements>;
componentMetadata(): void;
getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule>;
tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult>;
}

123
node_modules/astro/dist/core/app/pipeline.js generated vendored Normal file
View File

@@ -0,0 +1,123 @@
import { Pipeline } from "../base-pipeline.js";
import { RedirectSinglePageBuiltModule } from "../redirects/index.js";
import {
createAssetLink,
createModuleScriptElement,
createStylesheetElementSet
} from "../render/ssr-element.js";
import { getFallbackRoute, routeIsFallback, routeIsRedirect } from "../routing/helpers.js";
import { findRouteToRewrite } from "../routing/rewrite.js";
import { createConsoleLogger } from "../logger/impls/console.js";
class AppPipeline extends Pipeline {
getName() {
return "AppPipeline";
}
static create({ manifest, streaming }) {
const resolve = async function resolve2(specifier) {
if (!(specifier in manifest.entryModules)) {
throw new Error(`Unable to resolve [${specifier}]`);
}
const bundlePath = manifest.entryModules[specifier];
if (bundlePath.startsWith("data:") || bundlePath.length === 0) {
return bundlePath;
} else {
return createAssetLink(bundlePath, manifest.base, manifest.assetsPrefix);
}
};
const logger = createConsoleLogger({ level: manifest.logLevel });
const pipeline = new AppPipeline(
logger,
manifest,
"production",
manifest.renderers,
resolve,
streaming,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0,
void 0
);
return pipeline;
}
async headElements(routeData) {
const { assetsPrefix, base } = this.manifest;
const routeInfo = this.manifest.routes.find(
(route) => route.routeData.route === routeData.route
);
const links = /* @__PURE__ */ new Set();
const scripts = /* @__PURE__ */ new Set();
const styles = createStylesheetElementSet(routeInfo?.styles ?? [], base, assetsPrefix);
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, base, assetsPrefix));
}
}
return { links, styles, scripts };
}
componentMetadata() {
}
async getComponentByRoute(routeData) {
const module = await this.getModuleForRoute(routeData);
return module.page();
}
async getModuleForRoute(route) {
for (const defaultRoute of this.defaultRoutes) {
if (route.component === defaultRoute.component) {
return {
page: () => Promise.resolve(defaultRoute.instance)
};
}
}
let routeToProcess = route;
if (routeIsRedirect(route)) {
if (route.redirectRoute) {
routeToProcess = route.redirectRoute;
} else {
return RedirectSinglePageBuiltModule;
}
} else if (routeIsFallback(route)) {
routeToProcess = getFallbackRoute(route, this.manifest.routes);
}
if (this.manifest.pageMap) {
const importComponentInstance = this.manifest.pageMap.get(routeToProcess.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."
);
}
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,
outDir: this.manifest?.serverLike ? this.manifest.buildClientDir : this.manifest.outDir
});
const componentInstance = await this.getComponentByRoute(routeData);
return { newUrl, pathname, componentInstance, routeData };
}
}
export {
AppPipeline
};

11
node_modules/astro/dist/core/app/prepare-response.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/**
* Strips internal-only headers from the response before sending it to the
* user agent, and optionally appends cookies written via `Astro.cookie.set()`
* to the `Set-Cookie` header.
*
* This is a pure function with no dependencies on the app; it is shared by
* `AstroHandler` and the various error handlers.
*/
export declare function prepareResponse(response: Response, { addCookieHeader }: {
addCookieHeader: boolean;
}): void;

18
node_modules/astro/dist/core/app/prepare-response.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
import { INTERNAL_RESPONSE_HEADERS, responseSentSymbol } from "../constants.js";
import { getSetCookiesFromResponse } from "../cookies/index.js";
function prepareResponse(response, { addCookieHeader }) {
for (const headerName of INTERNAL_RESPONSE_HEADERS) {
if (response.headers.has(headerName)) {
response.headers.delete(headerName);
}
}
if (addCookieHeader) {
for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) {
response.headers.append("set-cookie", setCookieHeaderValue);
}
}
Reflect.set(response, responseSentSymbol, true);
}
export {
prepareResponse
};

11
node_modules/astro/dist/core/app/render-options.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import type { ResolvedRenderOptions } from './base.js';
/**
* Reads `ResolvedRenderOptions` that were attached to a request via
* `renderOptionsSymbol`. Returns `undefined` if no options are attached.
*/
export declare function getRenderOptions(request: Request): ResolvedRenderOptions | undefined;
/**
* Attaches `ResolvedRenderOptions` to a request via `renderOptionsSymbol` so
* that downstream handlers can read them.
*/
export declare function setRenderOptions(request: Request, options: ResolvedRenderOptions): void;

11
node_modules/astro/dist/core/app/render-options.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
const renderOptionsSymbol = /* @__PURE__ */ Symbol.for("astro.renderOptions");
function getRenderOptions(request) {
return Reflect.get(request, renderOptionsSymbol);
}
function setRenderOptions(request, options) {
Reflect.set(request, renderOptionsSymbol, options);
}
export {
getRenderOptions,
setRenderOptions
};

203
node_modules/astro/dist/core/app/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,203 @@
import type { ActionClient } from '../../actions/runtime/types.js';
import type { ComponentInstance, SerializedRouteData } from '../../types/astro.js';
import type { AstroMiddlewareInstance } from '../../types/public/common.js';
import type { AstroConfig, CspAlgorithm, Locales, RemotePattern } from '../../types/public/config.js';
import type { RouteData, SSRComponentMetadata, SSRLoadedRenderer, SSRResult } from '../../types/public/internal.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import type { CspDirective } from '../csp/config.js';
import type { AstroLoggerDestination, AstroLoggerLevel, AstroLoggerMessage } from '../logger/core.js';
import type { RoutingStrategies } from './common.js';
import type { CacheProviderFactory, SSRManifestCache } from '../cache/types.js';
import type { BaseSessionConfig, SessionDriverFactory } from '../session/types.js';
import type { DevToolbarPlacement } from '../../types/public/toolbar.js';
import type { MiddlewareMode } from '../../types/public/integrations.js';
import type { BaseApp } from './base.js';
import type { LoggerHandlerConfig } from '../logger/config.js';
type ComponentPath = string;
export type StylesheetAsset = {
type: 'inline';
content: string;
} | {
type: 'external';
src: string;
};
type ScriptAsset = {
children: string;
stage: string;
} | {
type: 'inline' | 'external';
value: string;
};
export interface RouteInfo {
routeData: RouteData;
file: string;
links: string[];
scripts: ScriptAsset[];
styles: StylesheetAsset[];
}
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
routeData: SerializedRouteData;
};
type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;
export type ServerIslandMappings = {
serverIslandMap?: Map<string, () => Promise<ComponentInstance>>;
serverIslandNameMap?: Map<string, string>;
};
export type AssetsPrefix = string | ({
fallback: string;
} & Record<string, string>) | undefined;
export type SSRManifest = {
adapterName: string;
routes: RouteInfo[];
site?: string;
base: string;
/**
* The base of the assets generated **by the user**. For example, scripts created by the user falls under this category.
*
* The value of this field comes from `vite.base`. We aren't usually this tight to vite in our code base, so probably
* this should be refactored somehow.
*/
userAssetsBase: string | undefined;
trailingSlash: AstroConfig['trailingSlash'];
buildFormat: NonNullable<AstroConfig['build']>['format'];
compressHTML: boolean | 'jsx';
experimentalQueuedRendering: {
enabled: boolean;
/** Node pool size for memory reuse (default: 1000, set to 0 to disable pooling) */
poolSize?: number;
/** Whether to enable HTMLString caching for deduplicating repeated HTML fragments (default: true) */
contentCache?: boolean;
};
assetsPrefix?: AssetsPrefix;
renderers: SSRLoadedRenderer[];
/**
* Based on Astro config's `output` option, `true` if "server" or "hybrid".
*
* Whether this application is SSR-like. If so, this has some implications, such as
* the creation of `dist/client` and `dist/server` folders.
*/
serverLike: boolean;
/**
* The middleware mode determines when and how middleware executes.
* - 'classic' (default): Build-time for prerendered pages, request-time for SSR pages
* - 'edge': Middleware deployed as separate edge function
*/
middlewareMode: MiddlewareMode;
/**
* 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>;
serverIslandMappings?: () => Promise<ServerIslandMappings> | ServerIslandMappings;
key: Promise<CryptoKey>;
i18n: SSRManifestI18n | undefined;
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
logger?: () => Promise<{
default: AstroLoggerDestination<AstroLoggerMessage>;
}> | {
default: AstroLoggerDestination<AstroLoggerMessage>;
};
actions?: () => Promise<SSRActions> | SSRActions;
sessionDriver?: () => Promise<{
default: SessionDriverFactory | null;
}>;
cacheProvider?: () => Promise<{
default: CacheProviderFactory | null;
}>;
checkOrigin: boolean;
allowedDomains?: Partial<RemotePattern>[];
actionBodySizeLimit: number;
serverIslandBodySizeLimit: number;
sessionConfig?: SSRManifestSession;
cacheConfig?: SSRManifestCache;
cacheDir: URL;
srcDir: URL;
outDir: URL;
rootDir: URL;
publicDir: URL;
assetsDir: string;
buildClientDir: URL;
buildServerDir: URL;
csp: SSRManifestCSP | undefined;
image: {
objectFit?: string;
objectPosition?: string;
layout?: string;
};
shouldInjectCspMetaTags: boolean;
devToolbar: {
enabled: boolean;
/**
* Latest version of Astro, will be undefined if:
* - unable to check
* - the user has disabled the check
* - the check has not completed yet
* - the user is on the latest version already
*/
latestAstroVersion: string | undefined;
debugInfoOutput: string | undefined;
placement: DevToolbarPlacement | undefined;
};
internalFetchHeaders?: Record<string, string>;
logLevel: AstroLoggerLevel;
experimentalLogger: LoggerHandlerConfig | undefined;
};
export type SSRActions = {
server: Record<string, ActionClient<any, any, any>>;
};
export type SSRManifestI18n = {
fallback: Record<string, string> | undefined;
fallbackType: 'redirect' | 'rewrite';
strategy: RoutingStrategies;
locales: Locales;
defaultLocale: string;
domainLookupTable: Record<string, string>;
domains: Record<string, string> | undefined;
};
export type SSRManifestCSP = {
cspDestination: 'adapter' | 'meta' | 'header' | undefined;
algorithm: CspAlgorithm;
scriptHashes: string[];
scriptResources: string[];
isStrictDynamic: boolean;
styleHashes: string[];
styleResources: string[];
directives: CspDirective[];
};
export interface SSRManifestSession extends BaseSessionConfig {
driver: string;
options?: Record<string, any> | undefined;
}
/** Public type exposed through the `astro:build:ssr` integration hook */
export type SerializedSSRManifest = Omit<SSRManifest, 'middleware' | 'logger' | 'routes' | 'assets' | 'componentMetadata' | 'inlinedScripts' | 'clientDirectives' | 'serverIslandNameMap' | 'key' | 'rootDir' | 'srcDir' | 'cacheDir' | 'outDir' | 'publicDir' | 'buildClientDir' | 'buildServerDir'> & {
rootDir: string;
srcDir: string;
cacheDir: string;
outDir: string;
publicDir: string;
buildClientDir: string;
buildServerDir: string;
routes: SerializedRouteInfo[];
assets: string[];
componentMetadata: [string, SSRComponentMetadata][];
inlinedScripts: [string, string][];
clientDirectives: [string, string][];
key: string;
};
/** @deprecated This will be removed in a future major version. */
export type NodeAppHeadersJson = {
pathname: string;
headers: {
key: string;
value: string;
}[];
}[];
export type CreateApp = (options?: {
streaming?: boolean;
}) => BaseApp;
export {};

0
node_modules/astro/dist/core/app/types.js generated vendored Normal file
View File

23
node_modules/astro/dist/core/app/validate-headers.d.ts generated vendored Normal file
View File

@@ -0,0 +1,23 @@
import { type RemotePattern } from '@astrojs/internal-helpers/remote';
/**
* Parses a potentially comma-separated multi-value header (as produced by
* proxy chains) and returns the first value, trimmed of whitespace.
* Returns `undefined` when the header is absent or empty.
*/
export declare function getFirstForwardedValue(multiValueHeader: string | string[] | undefined): string | undefined;
/**
* Validate a host against allowedDomains.
* Returns the host only if it matches an allowed pattern; otherwise, undefined.
* This prevents SSRF attacks by ensuring the Host header is trusted.
*/
export declare function validateHost(host: string | undefined, protocol: string, allowedDomains?: Partial<RemotePattern>[]): string | undefined;
/**
* Validate forwarded headers (proto, host, port) against allowedDomains.
* Returns validated values or undefined for rejected headers.
* Uses strict defaults: http/https only for proto, rejects port if not in allowedDomains.
*/
export declare function validateForwardedHeaders(forwardedProtocol?: string, forwardedHost?: string, forwardedPort?: string, allowedDomains?: Partial<RemotePattern>[]): {
protocol?: string;
host?: string;
port?: string;
};

84
node_modules/astro/dist/core/app/validate-headers.js generated vendored Normal file
View File

@@ -0,0 +1,84 @@
import { matchPattern } from "@astrojs/internal-helpers/remote";
function getFirstForwardedValue(multiValueHeader) {
return multiValueHeader?.toString().split(",").map((e) => e.trim())[0];
}
function sanitizeHost(hostname) {
if (!hostname) return void 0;
if (/[/\\]/.test(hostname)) return void 0;
return hostname;
}
function parseHost(host) {
const parts = host.split(":");
return {
hostname: parts[0],
port: parts[1]
};
}
function matchesAllowedDomains(hostname, protocol, port, allowedDomains) {
const hostWithPort = port ? `${hostname}:${port}` : hostname;
const urlString = `${protocol}://${hostWithPort}`;
if (!URL.canParse(urlString)) {
return false;
}
const testUrl = new URL(urlString);
return allowedDomains.some((pattern) => matchPattern(testUrl, pattern));
}
function validateHost(host, protocol, allowedDomains) {
if (!host || host.length === 0) return void 0;
if (!allowedDomains || allowedDomains.length === 0) return void 0;
const sanitized = sanitizeHost(host);
if (!sanitized) return void 0;
const { hostname, port } = parseHost(sanitized);
if (matchesAllowedDomains(hostname, protocol, port, allowedDomains)) {
return sanitized;
}
return void 0;
}
function validateForwardedHeaders(forwardedProtocol, forwardedHost, forwardedPort, allowedDomains) {
const result = {};
if (forwardedProtocol) {
if (allowedDomains && allowedDomains.length > 0) {
const hasProtocolPatterns = allowedDomains.some((pattern) => pattern.protocol !== void 0);
if (hasProtocolPatterns) {
try {
const testUrl = new URL(`${forwardedProtocol}://example.com`);
const isAllowed = allowedDomains.some(
(pattern) => matchPattern(testUrl, { protocol: pattern.protocol })
);
if (isAllowed) {
result.protocol = forwardedProtocol;
}
} catch {
}
} else if (/^https?$/.test(forwardedProtocol)) {
result.protocol = forwardedProtocol;
}
}
}
if (forwardedPort && allowedDomains && allowedDomains.length > 0) {
const hasPortPatterns = allowedDomains.some((pattern) => pattern.port !== void 0);
if (hasPortPatterns) {
const isAllowed = allowedDomains.some((pattern) => pattern.port === forwardedPort);
if (isAllowed) {
result.port = forwardedPort;
}
}
}
if (forwardedHost && forwardedHost.length > 0 && allowedDomains && allowedDomains.length > 0) {
const protoForValidation = result.protocol || "https";
const sanitized = sanitizeHost(forwardedHost);
if (sanitized) {
const { hostname, port: portFromHost } = parseHost(sanitized);
const portForValidation = result.port || portFromHost;
if (matchesAllowedDomains(hostname, protoForValidation, portForValidation, allowedDomains)) {
result.host = sanitized;
}
}
}
return result;
}
export {
getFirstForwardedValue,
validateForwardedHeaders,
validateHost
};