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

View File

@@ -0,0 +1,17 @@
import type { BaseApp, ResolvedRenderOptions } from '../app/base.js';
import type { Pipeline } from '../base-pipeline.js';
import type { FetchHandler } from './types.js';
/**
* The default request handler for `BaseApp`. Builds the per-request
* `FetchState` and delegates to an `AstroHandler`.
*/
export declare class DefaultFetchHandler {
#private;
constructor(app?: BaseApp<Pipeline>);
/**
* Fast path: called directly by `BaseApp.render()` with pre-resolved
* options, avoiding the `Reflect.set/get` round-trip through the request.
*/
renderWithOptions(request: Request, options: ResolvedRenderOptions): Promise<Response>;
fetch: FetchHandler;
}

45
node_modules/astro/dist/core/fetch/default-handler.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import { FetchState } from "./fetch-state.js";
import { appSymbol } from "../constants.js";
import { AstroHandler } from "../routing/handler.js";
class DefaultFetchHandler {
#app;
#handler;
constructor(app) {
this.#app = app ?? null;
this.#handler = app ? new AstroHandler(app) : null;
}
/**
* Fast path: called directly by `BaseApp.render()` with pre-resolved
* options, avoiding the `Reflect.set/get` round-trip through the request.
*/
renderWithOptions(request, options) {
if (!this.#app) {
const app = Reflect.get(request, appSymbol);
if (!app) {
throw new Error("No fetch handler provided.");
}
this.#app = app;
this.#handler = new AstroHandler(app);
}
const state = new FetchState(this.#app.pipeline, request, options);
return this.#handler.handle(state);
}
fetch = (request) => {
if (!this.#app) {
const app = Reflect.get(request, appSymbol);
if (!app) {
throw new Error("No fetch handler provided.");
}
this.#app = app;
this.#handler = new AstroHandler(app);
}
const state = new FetchState(this.#app.pipeline, request);
if (!this.#handler) {
throw new Error("No fetch handler provided.");
}
return this.#handler.handle(state);
};
}
export {
DefaultFetchHandler
};

244
node_modules/astro/dist/core/fetch/fetch-state.d.ts generated vendored Normal file
View File

@@ -0,0 +1,244 @@
import type { ActionAPIContext } from '../../actions/runtime/types.js';
import type { ComponentInstance } from '../../types/astro.js';
import type { Params, Props, RewritePayload } from '../../types/public/common.js';
import type { APIContext, AstroGlobal } from '../../types/public/context.js';
import type { RouteData, SSRResult } from '../../types/public/internal.js';
import { AstroCookies } from '../cookies/index.js';
import { type Pipeline } from '../render/index.js';
import type { ResolvedRenderOptions } from '../app/base.js';
/**
* Describes a lazily-created value that handlers can contribute to the
* request context. `create` is called at most once (on first `resolve`);
* `finalize` runs during `finalizeAll` to clean up / persist.
*/
export interface ContextProvider<T> {
/** Factory called lazily on the first `resolve(key)`. */
create: () => T;
/** Optional cleanup / persist callback. Receives the created value. */
finalize?: (value: T) => Promise<void> | void;
}
/**
* The public contract of {@link FetchState} exposed to user-land code
* (custom fetch handlers, Hono middleware, etc.).
*
* Only the members listed here are part of the stable public API.
* Everything else on the concrete `FetchState` class is internal and
* may change without notice.
*
* If you add a new member to `FetchState` that should be user-visible,
* add it here first — the `implements` clause on the class ensures a
* compile-time error if the class falls out of sync.
*/
export interface AstroFetchState {
/** The incoming request. */
readonly request: Request;
/** Normalized URL derived from the request. */
readonly url: URL;
/** Base-stripped, decoded pathname of the request. */
readonly pathname: string;
/** The matched route for this request, if any. */
readonly routeData: RouteData | undefined;
/** Cookies for this request. */
readonly cookies: AstroCookies;
/** Request-scoped locals object, shared with user middleware. */
readonly locals: App.Locals;
/** Route params derived from routeData + pathname. */
readonly params: Params | undefined;
/** Default HTTP status for the rendered response. */
status: number;
/**
* Triggers a rewrite to a different route.
*
* [Astro reference](https://docs.astro.build/en/guides/routing/#rewrites)
*/
rewrite(payload: RewritePayload): Promise<Response>;
/**
* Registers a context provider under the given key. The `create`
* factory is called lazily on the first `resolve(key)`.
*/
provide<T>(key: string, provider: ContextProvider<T>): void;
/**
* Lazily resolves a provider registered under `key`. Returns
* `undefined` if no provider was registered for the key.
*/
resolve<T>(key: string): T | undefined;
/**
* Runs all registered provider `finalize` callbacks. Call this after
* the response is produced, typically in a `finally` block.
*/
finalizeAll(): Promise<void> | void;
}
/**
* Retrieves the `FetchState` stashed on an `APIContext` by
* `FetchState.getAPIContext()`. Throws if not found — this indicates
* the context was not created through Astro's request pipeline.
*/
export declare function getFetchStateFromAPIContext(context: APIContext): FetchState;
/**
* Holds per-request state as it flows through the handler pipeline.
*
* **This class is user-facing** via `astro/fetch` and `astro/hono`.
* The {@link AstroFetchState} interface defines the stable public
* surface. Members not on that interface are internal and
* may change without notice.
*
* Performance note: fields on this class are plain properties — ES
* private fields (`#foo`) have a non-zero per-access cost in V8
* which is measurable on the hot render path, so `#` is used only
* for rarely-accessed memoized caches and Maps.
*/
export declare class FetchState implements AstroFetchState {
#private;
pipeline: Pipeline;
/**
* The request to render. Mutated during rewrites so subsequent renders
* see the rewritten URL.
*/
request: Request;
routeData: RouteData | undefined;
/**
* The pathname to use for routing and rendering. Starts out as the raw,
* base-stripped, decoded pathname from the request. May be further
* normalized by `AstroHandler` after routeData is known (in dev, when
* the matched route has no `.html` extension, `.html` / `/index.html`
* suffixes are stripped).
*/
pathname: string;
/** Resolved render options (addCookieHeader, clientAddress, locals, etc.). */
readonly renderOptions: ResolvedRenderOptions;
/** When the request started, used to log duration. */
readonly timeStart: number;
/**
* The route's loaded component module. Set before middleware runs; may
* be swapped during in-flight rewrites from inside the middleware chain.
*/
componentInstance: ComponentInstance | undefined;
/**
* Slot overrides supplied by the container API. `undefined` for HTTP
* requests — `PagesHandler` coalesces to `{}` on read so we don't
* allocate an empty object per request.
*/
slots: Record<string, any> | undefined;
/**
* Default HTTP status for the rendered response. Callers override
* before rendering runs (e.g. `AstroHandler` sets this from
* `BaseApp.getDefaultStatusCode`; error handlers set `404` / `500`).
*/
status: number;
/** Whether user middleware should be skipped for this request. */
skipMiddleware: boolean;
/** A flag that tells the render content if the rewriting was triggered. */
isRewriting: boolean;
/** A safety net in case of loops (rewrite counter). */
counter: number;
/** Cookies for this request. Created lazily on first access. */
cookies: AstroCookies;
get params(): Params | undefined;
set params(value: Params | undefined);
/** Normalized URL for this request. */
url: URL;
/** Client address for this request. */
clientAddress: string | undefined;
/** Whether this is a partial render (container API). */
partial: boolean | undefined;
/** Whether to inject CSP meta tags. */
shouldInjectCspMetaTags: boolean | undefined;
/** Request-scoped locals object, shared with user middleware. */
locals: App.Locals;
/**
* Memoized `props` (see `getProps`). `null` means "not yet computed"
* — using `null` (rather than `undefined`) keeps the hidden class
* stable and distinct from a valid-but-empty result.
*/
props: APIContext['props'] | null;
/** Memoized `ActionAPIContext` (see `getActionAPIContext`). */
actionApiContext: ActionAPIContext | null;
/** Memoized `APIContext` (see `getAPIContext`). */
apiContext: APIContext | null;
/** SSR result for the current page render. */
result: SSRResult | undefined;
/** Initial props (from container/error handler). */
initialProps: Props;
constructor(pipeline: Pipeline, request: Request, options?: ResolvedRenderOptions);
/**
* Triggers a rewrite. Delegates to the Rewrites handler.
*/
rewrite(payload: RewritePayload): Promise<Response>;
/**
* Creates the SSR result for the current page render.
*/
createResult(mod: ComponentInstance, ctx: ActionAPIContext): Promise<SSRResult>;
/**
* Creates the Astro global object for a component render.
*/
createAstro(result: SSRResult, props: Record<string, any>, slotValues: Record<string, any> | null, apiContext: ActionAPIContext): AstroGlobal;
/**
* Creates the Astro page-level partial (prototype for Astro global).
*/
createAstroPagePartial(result: SSRResult, apiContext: ActionAPIContext): Omit<AstroGlobal, 'props' | 'self' | 'slots'>;
getClientAddress(): string;
getCookies(): AstroCookies;
getCsp(): APIContext['csp'];
computeCurrentLocale(): string | undefined;
computePreferredLocale(): string | undefined;
computePreferredLocaleList(): string[] | undefined;
/**
* Lazily loads the route's component module. Returns the cached
* instance if already loaded. The promise is cached so concurrent
* callers share the same load.
*/
loadComponentInstance(): Promise<ComponentInstance>;
/**
* Registers a context provider under the given key. Handlers call
* this to contribute values to the request context (e.g. sessions).
* The `create` factory is called lazily on the first `resolve(key)`.
*/
provide<T>(key: string, provider: ContextProvider<T>): void;
/**
* Lazily resolves a provider registered under `key`. Calls
* `provider.create()` on first access and caches the result.
* Returns `undefined` if no provider was registered for the key.
*/
resolve<T>(key: string): T | undefined;
/**
* Runs all registered `finalize` callbacks. Should be called after
* the response is produced, typically in a `finally` block.
*
* Returns synchronously (no promise allocation) when nothing needs
* finalizing — important for the hot path where sessions are not used.
*/
finalizeAll(): Promise<void> | void;
/**
* Adds lazy getters to `target` for each registered provider key.
* Used by context creation (APIContext, Astro global) so that
* provider values like `session` and `cache` appear as properties
* without hard-coding the keys.
*/
defineProviderGetters(target: Record<string, any>): void;
/**
* Returns the resolved `props` for this render, computing them lazily
* from the route + component module on first access. If the
* `initialProps` already carries user-supplied props (e.g. the
* container API) those are used verbatim.
*/
getProps(): Promise<APIContext['props']>;
/**
* Returns the `ActionAPIContext` for this render, creating it lazily.
* Used by middleware, actions, and page dispatch.
*/
getActionAPIContext(): ActionAPIContext;
/**
* Returns the `APIContext` for this render, creating it lazily from
* the memoized props + action context.
*
* Callers must ensure `getProps()` has resolved at least once before
* calling this.
*/
getAPIContext(): APIContext;
/**
* Invalidates the cached `APIContext` so the next `getAPIContext()`
* call re-derives it from the (possibly mutated) state. Used
* after an in-flight rewrite swaps the route / request / params.
*/
invalidateContexts(): void;
}

779
node_modules/astro/dist/core/fetch/fetch-state.js generated vendored Normal file
View File

@@ -0,0 +1,779 @@
import colors from "piccolore";
import {
collapseDuplicateLeadingSlashes,
prependForwardSlash,
removeTrailingForwardSlash
} from "@astrojs/internal-helpers/path";
import { deserializeActionResult } from "../../actions/runtime/client.js";
import { createCallAction, createGetActionResult, hasActionPayload } from "../../actions/utils.js";
import { AstroCookies } from "../cookies/index.js";
import { Slots } from "../render/index.js";
import {
ASTRO_GENERATOR,
DEFAULT_404_COMPONENT,
fetchStateSymbol,
originPathnameSymbol,
pipelineSymbol,
responseSentSymbol
} from "../constants.js";
import { pushDirective } from "../csp/runtime.js";
import { generateCspDigest } from "../encryption.js";
import { AstroError, AstroErrorData } from "../errors/index.js";
import {
computeCurrentLocale as computeCurrentLocaleUtil,
computeCurrentLocaleFromParams,
computePreferredLocale as computePreferredLocaleUtil,
computePreferredLocaleList as computePreferredLocaleListUtil
} from "../../i18n/utils.js";
import { getParams, getProps } from "../render/index.js";
import { Rewrites } from "../rewrites/handler.js";
import { isRoute404or500, isRouteServerIsland } from "../routing/match.js";
import { normalizeUrl } from "../util/normalized-url.js";
import { getOriginPathname, setOriginPathname } from "../routing/rewrite.js";
import { routeHasHtmlExtension } from "../routing/helpers.js";
import { getRenderOptions } from "../app/render-options.js";
function getFetchStateFromAPIContext(context) {
const state = context[fetchStateSymbol];
if (!state) {
throw new Error(
"FetchState not found on APIContext. This is an internal error \u2014 the context was not created through Astro's request pipeline."
);
}
return state;
}
class FetchState {
pipeline;
/**
* The request to render. Mutated during rewrites so subsequent renders
* see the rewritten URL.
*/
request;
routeData;
/**
* The pathname to use for routing and rendering. Starts out as the raw,
* base-stripped, decoded pathname from the request. May be further
* normalized by `AstroHandler` after routeData is known (in dev, when
* the matched route has no `.html` extension, `.html` / `/index.html`
* suffixes are stripped).
*/
pathname;
/** Resolved render options (addCookieHeader, clientAddress, locals, etc.). */
renderOptions;
/** When the request started, used to log duration. */
timeStart;
/**
* The route's loaded component module. Set before middleware runs; may
* be swapped during in-flight rewrites from inside the middleware chain.
*/
componentInstance;
/**
* Slot overrides supplied by the container API. `undefined` for HTTP
* requests — `PagesHandler` coalesces to `{}` on read so we don't
* allocate an empty object per request.
*/
slots;
/**
* Default HTTP status for the rendered response. Callers override
* before rendering runs (e.g. `AstroHandler` sets this from
* `BaseApp.getDefaultStatusCode`; error handlers set `404` / `500`).
*/
status = 200;
/** Whether user middleware should be skipped for this request. */
skipMiddleware = false;
/** A flag that tells the render content if the rewriting was triggered. */
isRewriting = false;
/** A safety net in case of loops (rewrite counter). */
counter = 0;
/** Cookies for this request. Created lazily on first access. */
cookies;
/** Route params derived from routeData + pathname. Computed lazily. */
#params;
get params() {
if (!this.#params && this.routeData) {
this.#params = getParams(this.routeData, this.pathname);
}
return this.#params;
}
set params(value) {
this.#params = value;
}
/** Normalized URL for this request. */
url;
/** Client address for this request. */
clientAddress;
/** Whether this is a partial render (container API). */
partial;
/** Whether to inject CSP meta tags. */
shouldInjectCspMetaTags;
/** Request-scoped locals object, shared with user middleware. */
locals = {};
/**
* Memoized `props` (see `getProps`). `null` means "not yet computed"
* — using `null` (rather than `undefined`) keeps the hidden class
* stable and distinct from a valid-but-empty result.
*/
props = null;
/** Memoized `ActionAPIContext` (see `getActionAPIContext`). */
actionApiContext = null;
/** Memoized `APIContext` (see `getAPIContext`). */
apiContext = null;
/** Registered context providers keyed by name. Lazy-initialized on first provide(). */
#providers;
/** Cached values from resolved providers. Lazy-initialized on first resolve(). */
#providersResolvedValues;
/** Cached promise for lazy component instance loading. */
#componentInstancePromise;
/** SSR result for the current page render. */
result;
/** Initial props (from container/error handler). */
initialProps = {};
/** Rewrites handler instance. Lazy-initialized on first rewrite(). */
#rewrites;
/** Memoized Astro page partial. */
#astroPagePartial;
/** Memoized current locale. */
#currentLocale;
/** Memoized preferred locale. */
#preferredLocale;
/** Memoized preferred locale list. */
#preferredLocaleList;
constructor(pipeline, request, options) {
this.pipeline = pipeline;
this.request = request;
options ??= getRenderOptions(request);
this.routeData = options?.routeData;
this.renderOptions = options ?? {
addCookieHeader: false,
clientAddress: void 0,
locals: void 0,
prerenderedErrorPageFetch: fetch,
routeData: void 0,
waitUntil: void 0
};
this.componentInstance = void 0;
this.slots = void 0;
const url = new URL(request.url);
this.pathname = this.#computePathname(url);
this.timeStart = performance.now();
this.clientAddress = options?.clientAddress;
this.locals = options?.locals ?? {};
this.url = normalizeUrl(url);
this.cookies = new AstroCookies(request);
if (!Reflect.get(request, originPathnameSymbol)) {
setOriginPathname(
request,
this.pathname,
pipeline.manifest.trailingSlash,
pipeline.manifest.buildFormat
);
}
this.#resolveRouteData();
}
/**
* Triggers a rewrite. Delegates to the Rewrites handler.
*/
rewrite(payload) {
return (this.#rewrites ??= new Rewrites()).execute(this, payload);
}
/**
* Creates the SSR result for the current page render.
*/
async createResult(mod, ctx) {
const pipeline = this.pipeline;
const { clientDirectives, inlinedScripts, compressHTML, manifest, renderers, resolve } = pipeline;
const routeData = this.routeData;
const { links, scripts, styles } = await pipeline.headElements(routeData);
const extraStyleHashes = [];
const extraScriptHashes = [];
const shouldInjectCspMetaTags = this.shouldInjectCspMetaTags ?? manifest.shouldInjectCspMetaTags;
const cspAlgorithm = manifest.csp?.algorithm ?? "SHA-256";
if (shouldInjectCspMetaTags) {
for (const style of styles) {
extraStyleHashes.push(await generateCspDigest(style.children, cspAlgorithm));
}
for (const script of scripts) {
extraScriptHashes.push(await generateCspDigest(script.children, cspAlgorithm));
}
}
const componentMetadata = await pipeline.componentMetadata(routeData) ?? manifest.componentMetadata;
const headers = new Headers({ "Content-Type": "text/html" });
const partial = typeof this.partial === "boolean" ? this.partial : Boolean(mod.partial);
const actionResult = hasActionPayload(this.locals) ? deserializeActionResult(this.locals._actionPayload.actionResult) : void 0;
const status = this.status;
const response = {
status: actionResult?.error ? actionResult?.error.status : status,
statusText: actionResult?.error ? actionResult?.error.type : "OK",
get headers() {
return headers;
},
set headers(_) {
throw new AstroError(AstroErrorData.AstroResponseHeadersReassigned);
}
};
const state = this;
const result = {
base: manifest.base,
userAssetsBase: manifest.userAssetsBase,
cancelled: false,
clientDirectives,
inlinedScripts,
componentMetadata,
compressHTML,
cookies: this.cookies,
createAstro: (props, slots) => state.createAstro(result, props, slots, ctx),
links,
// SAFETY: createResult is only called after route resolution, so routeData
// is always set and the params getter always returns a value.
params: this.params,
partial,
pathname: this.pathname,
renderers,
resolve,
response,
request: this.request,
scripts,
styles,
actionResult,
async getServerIslandNameMap() {
const serverIslands = await pipeline.getServerIslands();
return serverIslands.serverIslandNameMap ?? /* @__PURE__ */ new Map();
},
key: manifest.key,
trailingSlash: manifest.trailingSlash,
_experimentalQueuedRendering: {
pool: pipeline.nodePool,
htmlStringCache: pipeline.htmlStringCache,
enabled: manifest.experimentalQueuedRendering?.enabled,
poolSize: manifest.experimentalQueuedRendering?.poolSize,
contentCache: manifest.experimentalQueuedRendering?.contentCache
},
_metadata: {
hasHydrationScript: false,
rendererSpecificHydrationScripts: /* @__PURE__ */ new Set(),
hasRenderedHead: false,
renderedScripts: /* @__PURE__ */ new Set(),
hasDirectives: /* @__PURE__ */ new Set(),
hasRenderedServerIslandRuntime: false,
headInTree: false,
extraHead: [],
extraStyleHashes,
extraScriptHashes,
propagators: /* @__PURE__ */ new Set(),
templateDepth: 0
},
cspDestination: manifest.csp?.cspDestination ?? (routeData.prerender ? "meta" : "header"),
shouldInjectCspMetaTags,
cspAlgorithm,
scriptHashes: manifest.csp?.scriptHashes ? [...manifest.csp.scriptHashes] : [],
scriptResources: manifest.csp?.scriptResources ? [...manifest.csp.scriptResources] : [],
styleHashes: manifest.csp?.styleHashes ? [...manifest.csp.styleHashes] : [],
styleResources: manifest.csp?.styleResources ? [...manifest.csp.styleResources] : [],
directives: manifest.csp?.directives ? [...manifest.csp.directives] : [],
isStrictDynamic: manifest.csp?.isStrictDynamic ?? false,
internalFetchHeaders: manifest.internalFetchHeaders
};
this.result = result;
return result;
}
/**
* Creates the Astro global object for a component render.
*/
createAstro(result, props, slotValues, apiContext) {
let astroPagePartial;
if (this.isRewriting) {
this.#astroPagePartial = this.createAstroPagePartial(result, apiContext);
}
this.#astroPagePartial ??= this.createAstroPagePartial(result, apiContext);
astroPagePartial = this.#astroPagePartial;
const astroComponentPartial = { props, self: null };
const Astro = Object.assign(
Object.create(astroPagePartial),
astroComponentPartial
);
let _slots;
Object.defineProperty(Astro, "slots", {
get: () => {
if (!_slots) {
_slots = new Slots(
result,
slotValues,
this.pipeline.logger
);
}
return _slots;
}
});
return Astro;
}
/**
* Creates the Astro page-level partial (prototype for Astro global).
*/
createAstroPagePartial(result, apiContext) {
const state = this;
const { cookies, locals, params, pipeline, url } = this;
const { response } = result;
const redirect = (path, status = 302) => {
if (state.request[responseSentSymbol]) {
throw new AstroError({
...AstroErrorData.ResponseSentError
});
}
return new Response(null, { status, headers: { Location: path } });
};
const rewrite = async (reroutePayload) => {
return await state.rewrite(reroutePayload);
};
const callAction = createCallAction(apiContext);
const partial = {
generator: ASTRO_GENERATOR,
routePattern: this.routeData.route,
isPrerendered: this.routeData.prerender,
cookies,
get clientAddress() {
return state.getClientAddress();
},
get currentLocale() {
return state.computeCurrentLocale();
},
params,
get preferredLocale() {
return state.computePreferredLocale();
},
get preferredLocaleList() {
return state.computePreferredLocaleList();
},
locals,
redirect,
rewrite,
request: this.request,
response,
site: pipeline.site,
getActionResult: createGetActionResult(locals),
get callAction() {
return callAction;
},
url,
get originPathname() {
return getOriginPathname(state.request);
},
get csp() {
return state.getCsp();
},
get logger() {
return {
info(msg) {
pipeline.logger.info(null, msg);
},
warn(msg) {
pipeline.logger.warn(null, msg);
},
error(msg) {
pipeline.logger.error(null, msg);
}
};
}
};
this.defineProviderGetters(partial);
return partial;
}
getClientAddress() {
const { pipeline, clientAddress } = this;
const routeData = this.routeData;
if (routeData.prerender) {
throw new AstroError({
...AstroErrorData.PrerenderClientAddressNotAvailable,
message: AstroErrorData.PrerenderClientAddressNotAvailable.message(routeData.component)
});
}
if (clientAddress) {
return clientAddress;
}
if (pipeline.adapterName) {
throw new AstroError({
...AstroErrorData.ClientAddressNotAvailable,
message: AstroErrorData.ClientAddressNotAvailable.message(pipeline.adapterName)
});
}
throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable);
}
getCookies() {
return this.cookies;
}
getCsp() {
const state = this;
const { pipeline } = this;
if (!pipeline.manifest.csp) {
if (pipeline.runtimeMode === "production") {
pipeline.logger.warn(
"csp",
`context.csp was used when rendering the route ${colors.green(state.routeData.route)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/configuration-reference/#securitycsp`
);
}
return void 0;
}
return {
insertDirective(payload) {
if (state?.result?.directives) {
state.result.directives = pushDirective(state.result.directives, payload);
} else {
state?.result?.directives.push(payload);
}
},
insertScriptResource(resource) {
state.result?.scriptResources.push(resource);
},
insertStyleResource(resource) {
state.result?.styleResources.push(resource);
},
insertStyleHash(hash) {
state.result?.styleHashes.push(hash);
},
insertScriptHash(hash) {
state.result?.scriptHashes.push(hash);
}
};
}
computeCurrentLocale() {
const {
url,
pipeline: { i18n },
routeData
} = this;
if (!i18n || !routeData) return;
const { defaultLocale, locales, strategy } = i18n;
const fallbackTo = strategy === "pathname-prefix-other-locales" || strategy === "domains-prefix-other-locales" ? defaultLocale : void 0;
if (this.#currentLocale) {
return this.#currentLocale;
}
let computedLocale;
if (isRouteServerIsland(routeData)) {
let referer = this.request.headers.get("referer");
if (referer) {
if (URL.canParse(referer)) {
referer = new URL(referer).pathname;
}
computedLocale = computeCurrentLocaleUtil(referer, locales, defaultLocale);
}
} else {
let pathname = routeData.pathname;
if (url && !routeData.pattern.test(url.pathname)) {
for (const fallbackRoute of routeData.fallbackRoutes) {
if (fallbackRoute.pattern.test(url.pathname)) {
pathname = fallbackRoute.pathname;
break;
}
}
}
pathname = pathname && !isRoute404or500(routeData) ? pathname : url.pathname ?? this.pathname;
computedLocale = computeCurrentLocaleUtil(pathname, locales, defaultLocale);
if (routeData.params.length > 0) {
const localeFromParams = computeCurrentLocaleFromParams(this.params, locales);
if (localeFromParams) {
computedLocale = localeFromParams;
}
}
}
this.#currentLocale = computedLocale ?? fallbackTo;
return this.#currentLocale;
}
computePreferredLocale() {
const {
pipeline: { i18n },
request
} = this;
if (!i18n) return;
return this.#preferredLocale ??= computePreferredLocaleUtil(request, i18n.locales);
}
computePreferredLocaleList() {
const {
pipeline: { i18n },
request
} = this;
if (!i18n) return;
return this.#preferredLocaleList ??= computePreferredLocaleListUtil(request, i18n.locales);
}
/**
* Lazily loads the route's component module. Returns the cached
* instance if already loaded. The promise is cached so concurrent
* callers share the same load.
*/
async loadComponentInstance() {
if (this.componentInstance) return this.componentInstance;
if (this.#componentInstancePromise) return this.#componentInstancePromise;
this.#componentInstancePromise = this.pipeline.getComponentByRoute(this.routeData).then((mod) => {
this.componentInstance = mod;
return mod;
});
return this.#componentInstancePromise;
}
/**
* Registers a context provider under the given key. Handlers call
* this to contribute values to the request context (e.g. sessions).
* The `create` factory is called lazily on the first `resolve(key)`.
*/
provide(key, provider) {
(this.#providers ??= /* @__PURE__ */ new Map()).set(key, provider);
}
/**
* Lazily resolves a provider registered under `key`. Calls
* `provider.create()` on first access and caches the result.
* Returns `undefined` if no provider was registered for the key.
*/
resolve(key) {
if (this.#providersResolvedValues?.has(key)) {
return this.#providersResolvedValues.get(key);
}
const provider = this.#providers?.get(key);
if (!provider) return void 0;
const value = provider.create();
(this.#providersResolvedValues ??= /* @__PURE__ */ new Map()).set(key, value);
return value;
}
/**
* Runs all registered `finalize` callbacks. Should be called after
* the response is produced, typically in a `finally` block.
*
* Returns synchronously (no promise allocation) when nothing needs
* finalizing — important for the hot path where sessions are not used.
*/
finalizeAll() {
if (!this.#providersResolvedValues || this.#providersResolvedValues.size === 0) return;
let chain;
for (const [key, provider] of this.#providers) {
if (provider.finalize && this.#providersResolvedValues.has(key)) {
const result = provider.finalize(this.#providersResolvedValues.get(key));
if (result) {
chain = chain ? chain.then(() => result) : result;
}
}
}
return chain;
}
/**
* Adds lazy getters to `target` for each registered provider key.
* Used by context creation (APIContext, Astro global) so that
* provider values like `session` and `cache` appear as properties
* without hard-coding the keys.
*/
defineProviderGetters(target) {
if (!this.#providers) return;
const state = this;
for (const key of this.#providers.keys()) {
Object.defineProperty(target, key, {
get: () => state.resolve(key),
enumerable: true,
configurable: true
});
}
}
/**
* Resolves the route to use for this request and stores it on
* `this.routeData`. If the adapter (or the dev server) provided a
* `routeData` via render options it's already set and this is a
* no-op. Otherwise we use the app's synchronous route matcher and
* fall back to a `404.astro` route so middleware can still run.
*
* Called eagerly from the constructor so individual handlers
* (actions, pages, middleware, etc.) always see a resolved route
* without the caller needing an extra setup step.
*
* Once routeData is known, finalizes `this.pathname`: in dev, if the
* matched route has no `.html` extension, strip `.html` / `/index.html`
* suffixes so the rendering pipeline sees the canonical pathname.
*/
/**
* Strip `.html` / `/index.html` suffixes from the pathname so the
* rendering pipeline sees the canonical route path. Skipped when the
* matched route itself has an `.html` extension in its definition.
*/
#stripHtmlExtension() {
if (this.routeData && !routeHasHtmlExtension(this.routeData)) {
this.pathname = this.pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
}
}
#resolveRouteData() {
const pipeline = this.pipeline;
if (this.routeData) {
this.#stripHtmlExtension();
return;
}
const matched = pipeline.matchRoute(this.pathname);
if (matched && matched.prerender && pipeline.manifest.serverLike) {
this.routeData = void 0;
} else {
this.routeData = matched;
}
pipeline.logger.debug("router", "Astro matched the following route for " + this.request.url);
pipeline.logger.debug("router", "RouteData:\n" + this.routeData);
if (!this.routeData) {
this.routeData = pipeline.manifestData.routes.find(
(route) => route.component === "404.astro" || route.component === DEFAULT_404_COMPONENT
);
}
if (!this.routeData) {
pipeline.logger.debug("router", "Astro hasn't found routes that match " + this.request.url);
pipeline.logger.debug("router", "Here's the available routes:\n", pipeline.manifestData);
return;
}
this.#stripHtmlExtension();
}
/**
* Strips the pipeline's base from the request URL, prepends a forward
* slash, and decodes the pathname. Falls back to the raw (not decoded)
* pathname if `decodeURI` throws.
*
* Mirrors `BaseApp.removeBase`, including the
* `collapseDuplicateLeadingSlashes` fix that prevents middleware
* authorization bypass when the URL starts with `//`.
*/
#computePathname(url) {
let pathname = collapseDuplicateLeadingSlashes(url.pathname);
const base = this.pipeline.manifest.base;
if (pathname.startsWith(base)) {
const baseWithoutTrailingSlash = removeTrailingForwardSlash(base);
pathname = pathname.slice(baseWithoutTrailingSlash.length + 1);
}
pathname = prependForwardSlash(pathname);
try {
return decodeURI(pathname);
} catch (e) {
this.pipeline.logger.error(null, e.toString());
return pathname;
}
}
/**
* Returns the resolved `props` for this render, computing them lazily
* from the route + component module on first access. If the
* `initialProps` already carries user-supplied props (e.g. the
* container API) those are used verbatim.
*/
async getProps() {
if (this.props !== null) return this.props;
if (Object.keys(this.initialProps).length > 0) {
this.props = this.initialProps;
return this.props;
}
const pipeline = this.pipeline;
const mod = await this.loadComponentInstance();
this.props = await getProps({
mod,
routeData: this.routeData,
routeCache: pipeline.routeCache,
pathname: this.pathname,
logger: pipeline.logger,
serverLike: pipeline.manifest.serverLike,
base: pipeline.manifest.base,
trailingSlash: pipeline.manifest.trailingSlash
});
return this.props;
}
/**
* Returns the `ActionAPIContext` for this render, creating it lazily.
* Used by middleware, actions, and page dispatch.
*/
getActionAPIContext() {
if (this.actionApiContext !== null) return this.actionApiContext;
const state = this;
const ctx = {
get cookies() {
return state.cookies;
},
routePattern: this.routeData.route,
isPrerendered: this.routeData.prerender,
get clientAddress() {
return state.getClientAddress();
},
get currentLocale() {
return state.computeCurrentLocale();
},
generator: ASTRO_GENERATOR,
get locals() {
return state.locals;
},
set locals(_) {
throw new AstroError(AstroErrorData.LocalsReassigned);
},
// SAFETY: getActionAPIContext is only called after route resolution,
// so routeData is always set and the params getter always returns a value.
params: this.params,
get preferredLocale() {
return state.computePreferredLocale();
},
get preferredLocaleList() {
return state.computePreferredLocaleList();
},
request: this.request,
site: this.pipeline.site,
url: this.url,
get originPathname() {
return getOriginPathname(state.request);
},
get csp() {
return state.getCsp();
},
get logger() {
if (!state.pipeline.manifest.experimentalLogger) {
state.pipeline.logger.warn(
null,
"The Astro.logger is available only when experimental.logger is defined."
);
return void 0;
}
return {
info(msg) {
state.pipeline.logger.info(null, msg);
},
warn(msg) {
state.pipeline.logger.warn(null, msg);
},
error(msg) {
state.pipeline.logger.error(null, msg);
}
};
}
};
this.defineProviderGetters(ctx);
this.actionApiContext = ctx;
return this.actionApiContext;
}
/**
* Returns the `APIContext` for this render, creating it lazily from
* the memoized props + action context.
*
* Callers must ensure `getProps()` has resolved at least once before
* calling this.
*/
getAPIContext() {
if (this.apiContext !== null) return this.apiContext;
const actionApiContext = this.getActionAPIContext();
const state = this;
const redirect = (path, status = 302) => new Response(null, { status, headers: { Location: path } });
const rewrite = async (reroutePayload) => {
return await state.rewrite(reroutePayload);
};
Reflect.set(actionApiContext, pipelineSymbol, this.pipeline);
actionApiContext[fetchStateSymbol] = this;
this.apiContext = Object.assign(actionApiContext, {
props: this.props,
redirect,
rewrite,
getActionResult: createGetActionResult(actionApiContext.locals),
callAction: createCallAction(actionApiContext)
});
return this.apiContext;
}
/**
* Invalidates the cached `APIContext` so the next `getAPIContext()`
* call re-derives it from the (possibly mutated) state. Used
* after an in-flight rewrite swaps the route / request / params.
*/
invalidateContexts() {
this.props = null;
this.actionApiContext = null;
this.apiContext = null;
}
}
export {
FetchState,
getFetchStateFromAPIContext
};

61
node_modules/astro/dist/core/fetch/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,61 @@
import { FetchState as BaseFetchState } from './fetch-state.js';
import type { AstroFetchState } from './fetch-state.js';
export type { AstroFetchState };
export declare class FetchState extends BaseFetchState {
constructor(request: Request);
}
export declare function astro(state: FetchState): Promise<Response>;
/**
* Checks if the request pathname needs trailing-slash normalization and
* returns a redirect `Response` if so. Returns `undefined` when no
* redirect is needed and the caller should continue processing.
*/
export declare function trailingSlash(state: FetchState): Response | undefined;
/**
* Runs Astro's middleware chain for the given state, calling `next` at
* the bottom of the chain to produce the response. Lazily creates
* the render context if needed.
*/
export declare function middleware(state: FetchState, next: (state: FetchState) => Promise<Response>): Promise<Response>;
/**
* Dispatches the request to the matched route (endpoint, page, redirect,
* or fallback). Lazily creates the render context if needed.
*/
export declare function pages(state: FetchState): Promise<Response>;
/**
* Registers the session provider on the state. The session is created
* lazily when user code accesses `ctx.session`, and persisted when
* `state.finalizeAll()` is called. No-op if sessions are not configured.
*
* Call this early (before middleware runs). Call `state.finalizeAll()`
* in a `finally` block after the response is produced to persist
* any session mutations.
*/
export declare function sessions(state: FetchState): Promise<void> | void;
/**
* Checks if the matched route is a redirect and returns the redirect
* `Response` if so. Returns `undefined` when the route is not a
* redirect and the caller should continue processing.
* `state.routeData` must be set before calling this.
*/
export declare function redirects(state: FetchState): Promise<Response> | undefined;
/**
* Handles Astro Action requests (RPC + form). Returns a `Response` for
* RPC actions, or `undefined` for form actions / non-action requests
* (the caller should continue to page rendering). Lazily creates
* the render context if needed.
*/
export declare function actions(state: FetchState): Promise<Response | undefined> | undefined;
/**
* Post-processes a response against the app's i18n configuration.
* Handles locale redirects, 404s for invalid locales, and fallback
* routing. Returns the response unmodified if i18n is not configured.
*/
export declare function i18n(state: FetchState, response: Response): Promise<Response>;
/**
* Wraps a render callback with cache provider logic. Handles runtime
* caching (onRequest), CDN-based providers (headers only), and the
* no-cache case transparently. Cache headers are applied and stripped
* internally.
*/
export declare function cache(state: FetchState, next: () => Promise<Response>): Promise<Response>;

121
node_modules/astro/dist/core/fetch/index.js generated vendored Normal file
View File

@@ -0,0 +1,121 @@
import { ActionHandler } from "../../actions/handler.js";
import { FetchState as BaseFetchState } from "./fetch-state.js";
import { CacheHandler } from "../cache/handler.js";
import { appSymbol } from "../constants.js";
import { I18n } from "../i18n/handler.js";
import { AstroMiddleware } from "../middleware/astro-middleware.js";
import { PagesHandler } from "../pages/handler.js";
import { renderRedirect } from "../redirects/render.js";
import { AstroHandler } from "../routing/handler.js";
import { provideSession } from "../session/handler.js";
import { TrailingSlashHandler } from "../routing/trailing-slash-handler.js";
function getApp(request) {
const app = Reflect.get(request, appSymbol);
if (!app) {
throw new Error(
"FetchState(request) called on a request without an attached app. Ensure it runs inside Astro's request pipeline."
);
}
return app;
}
class FetchState extends BaseFetchState {
constructor(request) {
super(getApp(request).pipeline, request);
}
}
const astroHandlers = /* @__PURE__ */ new WeakMap();
function astro(state) {
const app = getApp(state.request);
let handler = astroHandlers.get(app);
if (!handler) {
handler = new AstroHandler(app);
astroHandlers.set(app, handler);
}
return handler.handle(state);
}
const trailingSlashHandlers = /* @__PURE__ */ new WeakMap();
function trailingSlash(state) {
const app = getApp(state.request);
let handler = trailingSlashHandlers.get(app);
if (!handler) {
handler = new TrailingSlashHandler(app);
trailingSlashHandlers.set(app, handler);
}
return handler.handle(state);
}
const middlewareInstances = /* @__PURE__ */ new WeakMap();
function middleware(state, next) {
const app = getApp(state.request);
let mw = middlewareInstances.get(app);
if (!mw) {
mw = new AstroMiddleware(app.pipeline);
middlewareInstances.set(app, mw);
}
return mw.handle(state, (s, _ctx) => next(s));
}
const pagesHandlers = /* @__PURE__ */ new WeakMap();
function pages(state) {
const app = getApp(state.request);
let handler = pagesHandlers.get(app);
if (!handler) {
handler = new PagesHandler(app.pipeline);
pagesHandlers.set(app, handler);
}
return handler.handle(state, state.getAPIContext());
}
function sessions(state) {
return provideSession(state);
}
function redirects(state) {
if (state.routeData?.type === "redirect") {
return renderRedirect(state);
}
return void 0;
}
const actionHandlers = /* @__PURE__ */ new WeakMap();
function actions(state) {
const app = getApp(state.request);
let handler = actionHandlers.get(app);
if (!handler) {
handler = new ActionHandler();
actionHandlers.set(app, handler);
}
return handler.handle(state.getAPIContext(), state);
}
const i18nHandlers = /* @__PURE__ */ new WeakMap();
function getI18n(app) {
let handler = i18nHandlers.get(app);
if (handler === void 0) {
const config = app.manifest.i18n;
handler = config && config.strategy !== "manual" ? new I18n(config, app.manifest.base, app.manifest.trailingSlash, app.manifest.buildFormat) : null;
i18nHandlers.set(app, handler);
}
return handler;
}
function i18n(state, response) {
const handler = getI18n(getApp(state.request));
if (!handler) return Promise.resolve(response);
return handler.finalize(state, response);
}
const cacheHandlers = /* @__PURE__ */ new WeakMap();
function cache(state, next) {
const app = getApp(state.request);
let handler = cacheHandlers.get(app);
if (!handler) {
handler = new CacheHandler(app);
cacheHandlers.set(app, handler);
}
return handler.handle(state, next);
}
export {
FetchState,
actions,
astro,
cache,
i18n,
middleware,
pages,
redirects,
sessions,
trailingSlash
};

6
node_modules/astro/dist/core/fetch/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,6 @@
/**
* A framework-agnostic request handler. Takes a standard `Request` and
* returns a `Response`. This mirrors the Web Fetch API handler shape, which
* lets handlers compose easily with other middleware systems later.
*/
export type FetchHandler = (request: Request) => Promise<Response>;

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

5
node_modules/astro/dist/core/fetch/vite-plugin.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import { type Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../../types/astro.js';
export declare function vitePluginFetchable({ settings }: {
settings: AstroSettings;
}): VitePlugin;

69
node_modules/astro/dist/core/fetch/vite-plugin.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
import { fileURLToPath } from "node:url";
import {
normalizePath as viteNormalizePath
} from "vite";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
const FETCHABLE_MODULE_ID = "virtual:astro:fetchable";
const FETCHABLE_RESOLVED_MODULE_ID = "\0" + FETCHABLE_MODULE_ID;
const APP_PATH_SEGMENT_NAME = "app";
function vitePluginFetchable({ settings }) {
let resolvedUserAppId;
let userAppPresent = false;
const advancedRoutingEnabled = settings.config.experimental.advancedRouting;
const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir));
return {
name: "@astro/plugin-fetchable",
applyToEnvironment(environment) {
return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
},
configureServer(server) {
server.watcher.on("change", (path) => {
const normalizedPath = viteNormalizePath(path);
if (!normalizedPath.startsWith(normalizedSrcDir)) return;
const relativePath = normalizedPath.slice(normalizedSrcDir.length);
if (!relativePath.startsWith(`${APP_PATH_SEGMENT_NAME}.`)) return;
for (const name of [
ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
ASTRO_VITE_ENVIRONMENT_NAMES.astro
]) {
const environment = server.environments[name];
if (!environment) continue;
const virtualMod = environment.moduleGraph.getModuleById(FETCHABLE_RESOLVED_MODULE_ID);
if (virtualMod) {
environment.moduleGraph.invalidateModule(virtualMod);
}
}
});
},
resolveId: {
filter: {
id: new RegExp(`^${FETCHABLE_MODULE_ID}$`)
},
async handler() {
const resolved = await this.resolve(`${normalizedSrcDir}${APP_PATH_SEGMENT_NAME}`);
userAppPresent = advancedRoutingEnabled && !!resolved;
resolvedUserAppId = resolved?.id;
return FETCHABLE_RESOLVED_MODULE_ID;
}
},
load: {
filter: {
id: new RegExp(`^${FETCHABLE_RESOLVED_MODULE_ID}$`)
},
handler() {
if (userAppPresent && resolvedUserAppId) {
return {
code: `export { default } from '${resolvedUserAppId}';`
};
}
return {
code: `import { DefaultFetchHandler } from 'astro/app/fetch/default-handler';
export default new DefaultFetchHandler();`
};
}
}
};
}
export {
vitePluginFetchable
};