feat: scaffold Astro + Tailwind project
This commit is contained in:
11
node_modules/astro/dist/core/routing/3xx.d.ts
generated
vendored
Normal file
11
node_modules/astro/dist/core/routing/3xx.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
type RedirectTemplate = {
|
||||
from?: string;
|
||||
absoluteLocation: string | URL;
|
||||
status: number;
|
||||
relativeLocation: string;
|
||||
};
|
||||
/**
|
||||
* Generates a minimal HTML redirect page used for SSR redirects.
|
||||
*/
|
||||
export declare function redirectTemplate({ status, absoluteLocation, relativeLocation, from, }: RedirectTemplate): string;
|
||||
export {};
|
||||
23
node_modules/astro/dist/core/routing/3xx.js
generated
vendored
Normal file
23
node_modules/astro/dist/core/routing/3xx.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { escape } from "html-escaper";
|
||||
function redirectTemplate({
|
||||
status,
|
||||
absoluteLocation,
|
||||
relativeLocation,
|
||||
from
|
||||
}) {
|
||||
const delay = status === 302 ? 2 : 0;
|
||||
const rel = escape(String(relativeLocation));
|
||||
const abs = escape(String(absoluteLocation));
|
||||
const fromHtml = from ? `from <code>${escape(from)}</code> ` : "";
|
||||
return `<!doctype html>
|
||||
<title>Redirecting to: ${rel}</title>
|
||||
<meta http-equiv="refresh" content="${delay};url=${rel}">
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="canonical" href="${abs}">
|
||||
<body>
|
||||
<a href="${rel}">Redirecting ${fromHtml}to <code>${rel}</code></a>
|
||||
</body>`;
|
||||
}
|
||||
export {
|
||||
redirectTemplate
|
||||
};
|
||||
2
node_modules/astro/dist/core/routing/astro-designed-error-pages.d.ts
generated
vendored
Normal file
2
node_modules/astro/dist/core/routing/astro-designed-error-pages.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import type { RoutesList } from '../../types/astro.js';
|
||||
export declare function ensure404Route(manifest: RoutesList): RoutesList;
|
||||
10
node_modules/astro/dist/core/routing/astro-designed-error-pages.js
generated
vendored
Normal file
10
node_modules/astro/dist/core/routing/astro-designed-error-pages.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DEFAULT_404_ROUTE } from "./internal/astro-designed-error-pages.js";
|
||||
function ensure404Route(manifest) {
|
||||
if (!manifest.routes.some((route) => route.route === "/404")) {
|
||||
manifest.routes.push(DEFAULT_404_ROUTE);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
export {
|
||||
ensure404Route
|
||||
};
|
||||
47
node_modules/astro/dist/core/routing/create-manifest.d.ts
generated
vendored
Normal file
47
node_modules/astro/dist/core/routing/create-manifest.d.ts
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import nodeFs from 'node:fs';
|
||||
import type { AstroSettings, RoutesList } from '../../types/astro.js';
|
||||
import type { AstroConfig } from '../../types/public/config.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
import type { AstroLogger } from '../logger/core.js';
|
||||
export interface RouteEntry {
|
||||
path: string;
|
||||
isDir: boolean;
|
||||
}
|
||||
type RoutingSettings = Pick<AstroSettings, 'config' | 'injectedRoutes' | 'pageExtensions' | 'buildOutput'>;
|
||||
interface CreateRouteManifestParams {
|
||||
/** Astro Settings object */
|
||||
settings: AstroSettings;
|
||||
/** Current working directory */
|
||||
cwd?: string;
|
||||
/** fs module, for testing */
|
||||
fsMod?: typeof nodeFs;
|
||||
}
|
||||
export declare function createRoutesFromEntries(entries: RouteEntry[], settings: RoutingSettings, logger: AstroLogger, pagesDirRelative?: string): RouteData[];
|
||||
/**
|
||||
* Create a full route manifest from filesystem and injected routes.
|
||||
*/
|
||||
export declare function createRoutesList(params: CreateRouteManifestParams, logger: AstroLogger, { dev, }?: {
|
||||
dev?: boolean;
|
||||
}): Promise<RoutesList>;
|
||||
/**
|
||||
* Generates i18n fallback routes and attaches them to their source routes.
|
||||
*
|
||||
* For each locale that has a fallback configured (e.g. `{ es: 'en' }`), this
|
||||
* function inspects the existing route list and creates `type: 'fallback'`
|
||||
* entries for any paths that the source locale does not already have. The
|
||||
* fallback routes are pushed onto `route.fallbackRoutes` of their source route
|
||||
* so that the build pipeline can serve the fallback content.
|
||||
*
|
||||
* @param routes The full route list — mutated in-place.
|
||||
* @param i18n The resolved `config.i18n` object.
|
||||
* @param config The resolved Astro config (needs `base` and `trailingSlash`).
|
||||
*/
|
||||
export declare function createI18nFallbackRoutes(routes: RouteData[], i18n: NonNullable<AstroConfig['i18n']>, config: Pick<AstroConfig, 'base' | 'trailingSlash'>): void;
|
||||
/**
|
||||
* Resolve a route entrypoint to an absolute component path.
|
||||
*/
|
||||
export declare function resolveInjectedRoute(entrypoint: string, root: URL, cwd?: string): {
|
||||
resolved: string;
|
||||
component: string;
|
||||
};
|
||||
export {};
|
||||
681
node_modules/astro/dist/core/routing/create-manifest.js
generated
vendored
Normal file
681
node_modules/astro/dist/core/routing/create-manifest.js
generated
vendored
Normal file
@@ -0,0 +1,681 @@
|
||||
import nodeFs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import pLimit from "p-limit";
|
||||
import colors from "piccolore";
|
||||
import { injectImageEndpoint } from "../../assets/endpoint/config.js";
|
||||
import { runHookRoutesResolved } from "../../integrations/hooks.js";
|
||||
import { getPrerenderDefault } from "../../prerender/utils.js";
|
||||
import { toRoutingStrategy } from "../app/entrypoints/index.js";
|
||||
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "../constants.js";
|
||||
import {
|
||||
InvalidRedirectDestination,
|
||||
MissingIndexForInternationalization,
|
||||
UnsupportedExternalRedirect
|
||||
} from "../errors/errors-data.js";
|
||||
import { AstroError } from "../errors/index.js";
|
||||
import { hasFileExtension, removeLeadingForwardSlash, slash } from "../path.js";
|
||||
import { injectServerIslandRoute } from "../server-islands/endpoint.js";
|
||||
import { resolvePages } from "../util.js";
|
||||
import { ensure404Route } from "./astro-designed-error-pages.js";
|
||||
import { routeComparator } from "./priority.js";
|
||||
import { getPattern } from "./pattern.js";
|
||||
import { getRoutePrerenderOption } from "./prerender.js";
|
||||
import { validateSegment } from "./segment.js";
|
||||
const require2 = createRequire(import.meta.url);
|
||||
const ROUTE_DYNAMIC_SPLIT = /\[([^[\]()]+(?:\([^)]+\))?)\]/;
|
||||
const ROUTE_SPREAD = /^\.{3}.+$/;
|
||||
function getParts(part, file) {
|
||||
const result = [];
|
||||
part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
|
||||
if (!str) return;
|
||||
const dynamic = i % 2 === 1;
|
||||
const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
|
||||
if (!content || dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content)) {
|
||||
throw new Error(`Invalid route ${file} \u2014 parameter name must match /^[a-zA-Z0-9_$]+$/`);
|
||||
}
|
||||
result.push({
|
||||
content,
|
||||
dynamic,
|
||||
spread: dynamic && ROUTE_SPREAD.test(content)
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function isSemanticallyEqualSegment(segmentA, segmentB) {
|
||||
if (segmentA.length !== segmentB.length) {
|
||||
return false;
|
||||
}
|
||||
for (const [index, partA] of segmentA.entries()) {
|
||||
const partB = segmentB[index];
|
||||
if (partA.dynamic !== partB.dynamic || partA.spread !== partB.spread) {
|
||||
return false;
|
||||
}
|
||||
if (!partA.dynamic && partA.content !== partB.content) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function createFileBasedRoutes({ settings, cwd, fsMod }, logger) {
|
||||
const { config } = settings;
|
||||
const pages = resolvePages(config);
|
||||
const localFs = fsMod ?? nodeFs;
|
||||
const rootPath = fileURLToPath(config.root);
|
||||
const pagesPath = fileURLToPath(pages);
|
||||
if (!localFs.existsSync(pages)) {
|
||||
if (settings.injectedRoutes.length === 0) {
|
||||
const pagesDirRootRelative = pages.href.slice(settings.config.root.href.length);
|
||||
logger.warn(null, `Missing pages directory: ${pagesDirRootRelative}`);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
const routes = [];
|
||||
const validPageExtensions = /* @__PURE__ */ new Set([
|
||||
".astro",
|
||||
...SUPPORTED_MARKDOWN_FILE_EXTENSIONS,
|
||||
...settings.pageExtensions
|
||||
]);
|
||||
const invalidPotentialPages = /* @__PURE__ */ new Set([".tsx", ".jsx", ".vue", ".svelte"]);
|
||||
const validEndpointExtensions = /* @__PURE__ */ new Set([".js", ".ts"]);
|
||||
const prerender = getPrerenderDefault(settings.config);
|
||||
function walk(fs, dir, parentSegments, parentParams) {
|
||||
const items = [];
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const basename of files) {
|
||||
const resolved = path.join(dir, basename);
|
||||
const file = slash(path.relative(cwd || rootPath, resolved));
|
||||
const isDir = fs.statSync(resolved).isDirectory();
|
||||
const ext = path.extname(basename);
|
||||
const name = ext ? basename.slice(0, -ext.length) : basename;
|
||||
if (name[0] === "_") {
|
||||
continue;
|
||||
}
|
||||
if (basename[0] === "." && basename !== ".well-known") {
|
||||
continue;
|
||||
}
|
||||
if (!isDir && !validPageExtensions.has(ext) && !validEndpointExtensions.has(ext)) {
|
||||
if (invalidPotentialPages.has(ext)) {
|
||||
logger.warn(
|
||||
null,
|
||||
`Unsupported file type ${colors.bold(
|
||||
resolved
|
||||
)} found in pages directory. Only Astro files can be used as pages. Prefix filename with an underscore (\`_\`) to ignore this warning, or move the file outside of the pages directory.`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const segment = isDir ? basename : name;
|
||||
validateSegment(segment, file);
|
||||
const parts = getParts(segment, file);
|
||||
const isIndex = isDir ? false : basename.substring(0, basename.lastIndexOf(".")) === "index";
|
||||
const routeSuffix = basename.slice(basename.indexOf("."), -ext.length);
|
||||
const isPage = validPageExtensions.has(ext);
|
||||
items.push({
|
||||
basename,
|
||||
ext,
|
||||
parts,
|
||||
file: file.replace(/\\/g, "/"),
|
||||
isDir,
|
||||
isIndex,
|
||||
isPage,
|
||||
routeSuffix
|
||||
});
|
||||
}
|
||||
for (const item of items) {
|
||||
const segments = parentSegments.slice();
|
||||
if (item.isIndex) {
|
||||
if (item.routeSuffix) {
|
||||
if (segments.length > 0) {
|
||||
const lastSegment = segments[segments.length - 1].slice();
|
||||
const lastPart = lastSegment[lastSegment.length - 1];
|
||||
if (lastPart.dynamic) {
|
||||
lastSegment.push({
|
||||
dynamic: false,
|
||||
spread: false,
|
||||
content: item.routeSuffix
|
||||
});
|
||||
} else {
|
||||
lastSegment[lastSegment.length - 1] = {
|
||||
dynamic: false,
|
||||
spread: false,
|
||||
content: `${lastPart.content}${item.routeSuffix}`
|
||||
};
|
||||
}
|
||||
segments[segments.length - 1] = lastSegment;
|
||||
} else {
|
||||
segments.push(item.parts);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
segments.push(item.parts);
|
||||
}
|
||||
const params = parentParams.slice();
|
||||
params.push(...item.parts.filter((p) => p.dynamic).map((p) => p.content));
|
||||
if (item.isDir) {
|
||||
walk(fsMod ?? fs, path.join(dir, item.basename), segments, params);
|
||||
} else {
|
||||
const component = item.file;
|
||||
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
|
||||
const trailingSlash = trailingSlashForPath(
|
||||
pathname,
|
||||
settings.config,
|
||||
item.isPage ? "page" : "endpoint"
|
||||
);
|
||||
const pattern = getPattern(segments, settings.config.base, trailingSlash);
|
||||
const route = joinSegments(segments);
|
||||
routes.push({
|
||||
route,
|
||||
isIndex: item.isIndex,
|
||||
type: item.isPage ? "page" : "endpoint",
|
||||
pattern,
|
||||
segments,
|
||||
params,
|
||||
component,
|
||||
pathname: pathname || void 0,
|
||||
prerender,
|
||||
fallbackRoutes: [],
|
||||
distURL: [],
|
||||
origin: "project"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(localFs, pagesPath, [], []);
|
||||
return routes;
|
||||
}
|
||||
function createRoutesFromEntries(entries, settings, logger, pagesDirRelative = "src/pages") {
|
||||
const entriesByDir = groupEntriesByDir(entries);
|
||||
return createRoutesFromEntriesByDir(entriesByDir, settings, logger, pagesDirRelative);
|
||||
}
|
||||
function createRoutesFromEntriesByDir(entriesByDir, settings, logger, pagesDirRelative) {
|
||||
const routes = [];
|
||||
const validPageExtensions = /* @__PURE__ */ new Set([
|
||||
".astro",
|
||||
...SUPPORTED_MARKDOWN_FILE_EXTENSIONS,
|
||||
...settings.pageExtensions
|
||||
]);
|
||||
const invalidPotentialPages = /* @__PURE__ */ new Set([".tsx", ".jsx", ".vue", ".svelte"]);
|
||||
const validEndpointExtensions = /* @__PURE__ */ new Set([".js", ".ts"]);
|
||||
const prerender = getPrerenderDefault(settings.config);
|
||||
function walk(dir, parentSegments, parentParams) {
|
||||
const items = [];
|
||||
const dirEntries = entriesByDir.get(dir) ?? [];
|
||||
for (const entry of dirEntries) {
|
||||
const basename = path.posix.basename(entry.path);
|
||||
const ext = path.extname(basename);
|
||||
const name = ext ? basename.slice(0, -ext.length) : basename;
|
||||
const isDir = entry.isDir;
|
||||
const file = slash(path.posix.join(pagesDirRelative, entry.path));
|
||||
if (name[0] === "_") {
|
||||
continue;
|
||||
}
|
||||
if (basename[0] === "." && basename !== ".well-known") {
|
||||
continue;
|
||||
}
|
||||
if (!isDir && !validPageExtensions.has(ext) && !validEndpointExtensions.has(ext)) {
|
||||
if (invalidPotentialPages.has(ext)) {
|
||||
logger.warn(
|
||||
null,
|
||||
`Unsupported file type ${colors.bold(
|
||||
file
|
||||
)} found in pages directory. Only Astro files can be used as pages. Prefix filename with an underscore (\`_\`) to ignore this warning, or move the file outside of the pages directory.`
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const segment = isDir ? basename : name;
|
||||
validateSegment(segment, file);
|
||||
const parts = getParts(segment, file);
|
||||
const isIndex = isDir ? false : basename.substring(0, basename.lastIndexOf(".")) === "index";
|
||||
const routeSuffix = basename.slice(basename.indexOf("."), -ext.length);
|
||||
const isPage = validPageExtensions.has(ext);
|
||||
items.push({
|
||||
basename,
|
||||
ext,
|
||||
parts,
|
||||
file,
|
||||
isDir,
|
||||
isIndex,
|
||||
isPage,
|
||||
routeSuffix
|
||||
});
|
||||
}
|
||||
for (const item of items) {
|
||||
const segments = parentSegments.slice();
|
||||
if (item.isIndex) {
|
||||
if (item.routeSuffix) {
|
||||
if (segments.length > 0) {
|
||||
const lastSegment = segments[segments.length - 1].slice();
|
||||
const lastPart = lastSegment[lastSegment.length - 1];
|
||||
if (lastPart.dynamic) {
|
||||
lastSegment.push({
|
||||
dynamic: false,
|
||||
spread: false,
|
||||
content: item.routeSuffix
|
||||
});
|
||||
} else {
|
||||
lastSegment[lastSegment.length - 1] = {
|
||||
dynamic: false,
|
||||
spread: false,
|
||||
content: `${lastPart.content}${item.routeSuffix}`
|
||||
};
|
||||
}
|
||||
segments[segments.length - 1] = lastSegment;
|
||||
} else {
|
||||
segments.push(item.parts);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
segments.push(item.parts);
|
||||
}
|
||||
const params = parentParams.slice();
|
||||
params.push(...item.parts.filter((p) => p.dynamic).map((p) => p.content));
|
||||
if (item.isDir) {
|
||||
walk(path.posix.join(dir, item.basename), segments, params);
|
||||
} else {
|
||||
const component = item.file;
|
||||
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
|
||||
const trailingSlash = trailingSlashForPath(
|
||||
pathname,
|
||||
settings.config,
|
||||
item.isPage ? "page" : "endpoint"
|
||||
);
|
||||
const pattern = getPattern(segments, settings.config.base, trailingSlash);
|
||||
const route = joinSegments(segments);
|
||||
routes.push({
|
||||
route,
|
||||
isIndex: item.isIndex,
|
||||
type: item.isPage ? "page" : "endpoint",
|
||||
pattern,
|
||||
segments,
|
||||
params,
|
||||
component,
|
||||
pathname: pathname || void 0,
|
||||
prerender,
|
||||
fallbackRoutes: [],
|
||||
distURL: [],
|
||||
origin: "project"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
walk("", [], []);
|
||||
return routes;
|
||||
}
|
||||
function groupEntriesByDir(entries) {
|
||||
const entriesByDir = /* @__PURE__ */ new Map();
|
||||
for (const entry of entries) {
|
||||
const normalizedPath = slash(entry.path);
|
||||
const dir = path.posix.dirname(normalizedPath);
|
||||
const key = dir === "." ? "" : dir;
|
||||
const list = entriesByDir.get(key);
|
||||
const normalizedEntry = normalizedPath === entry.path ? entry : { ...entry, path: normalizedPath };
|
||||
if (list) {
|
||||
list.push(normalizedEntry);
|
||||
} else {
|
||||
entriesByDir.set(key, [normalizedEntry]);
|
||||
}
|
||||
}
|
||||
return entriesByDir;
|
||||
}
|
||||
const trailingSlashForPath = (pathname, config, type) => type === "endpoint" && pathname && hasFileExtension(pathname) ? "never" : config.trailingSlash;
|
||||
function createInjectedRoutes({ settings, cwd }) {
|
||||
const { config } = settings;
|
||||
const prerender = getPrerenderDefault(config);
|
||||
const routes = [];
|
||||
for (const injectedRoute of settings.injectedRoutes) {
|
||||
const { pattern: name, entrypoint, prerender: prerenderInjected, origin } = injectedRoute;
|
||||
const { resolved, component } = resolveInjectedRoute(entrypoint.toString(), config.root, cwd);
|
||||
const segments = removeLeadingForwardSlash(name).split(path.posix.sep).filter(Boolean).map((s) => {
|
||||
validateSegment(s);
|
||||
return getParts(s, component);
|
||||
});
|
||||
const type = resolved.endsWith(".astro") ? "page" : "endpoint";
|
||||
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
|
||||
const trailingSlash = trailingSlashForPath(pathname, config, type);
|
||||
const pattern = getPattern(segments, settings.config.base, trailingSlash);
|
||||
const params = segments.flat().filter((p) => p.dynamic).map((p) => p.content);
|
||||
const route = joinSegments(segments);
|
||||
routes.push({
|
||||
type,
|
||||
// For backwards compatibility, an injected route is never considered an index route.
|
||||
isIndex: false,
|
||||
route,
|
||||
pattern,
|
||||
segments,
|
||||
params,
|
||||
component,
|
||||
pathname: pathname || void 0,
|
||||
prerender: prerenderInjected ?? prerender,
|
||||
fallbackRoutes: [],
|
||||
distURL: [],
|
||||
origin
|
||||
});
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
function createRedirectRoutes({ settings }, routeMap) {
|
||||
const { config } = settings;
|
||||
const trailingSlash = config.trailingSlash;
|
||||
const routes = [];
|
||||
for (const [from, to] of Object.entries(settings.config.redirects)) {
|
||||
const segments = removeLeadingForwardSlash(from).split(path.posix.sep).filter(Boolean).map((s) => {
|
||||
validateSegment(s);
|
||||
return getParts(s, from);
|
||||
});
|
||||
const pattern = getPattern(segments, settings.config.base, trailingSlash);
|
||||
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
|
||||
const params = segments.flat().filter((p) => p.dynamic).map((p) => p.content);
|
||||
const route = joinSegments(segments);
|
||||
let destination;
|
||||
if (typeof to === "string") {
|
||||
destination = to;
|
||||
} else {
|
||||
destination = to.destination;
|
||||
}
|
||||
if (URL.canParse(destination) && !/^https?:\/\//.test(destination)) {
|
||||
throw new AstroError({
|
||||
...UnsupportedExternalRedirect,
|
||||
message: UnsupportedExternalRedirect.message(from, destination)
|
||||
});
|
||||
}
|
||||
const redirectRoute = routeMap.get(destination);
|
||||
if (params.length > 0 && !redirectRoute && !URL.canParse(destination) && getPrerenderDefault(config)) {
|
||||
throw new AstroError({
|
||||
...InvalidRedirectDestination,
|
||||
message: InvalidRedirectDestination.message(from, destination)
|
||||
});
|
||||
}
|
||||
routes.push({
|
||||
type: "redirect",
|
||||
// For backwards compatibility, a redirect is never considered an index route.
|
||||
isIndex: false,
|
||||
route,
|
||||
pattern,
|
||||
segments,
|
||||
params,
|
||||
component: from,
|
||||
pathname: pathname || void 0,
|
||||
prerender: getPrerenderDefault(config),
|
||||
redirect: to,
|
||||
redirectRoute,
|
||||
fallbackRoutes: [],
|
||||
distURL: [],
|
||||
origin: "project"
|
||||
});
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
function isStaticSegment(segment) {
|
||||
return segment.every((part) => !part.dynamic && !part.spread);
|
||||
}
|
||||
function detectRouteCollision(a, b, _config, logger) {
|
||||
if (a.type === "fallback" || b.type === "fallback") {
|
||||
return;
|
||||
}
|
||||
if (a.route === b.route && a.segments.every(isStaticSegment) && b.segments.every(isStaticSegment)) {
|
||||
logger.warn(
|
||||
"router",
|
||||
`The route "${a.route}" is defined in both "${a.component}" and "${b.component}". A static route cannot be defined more than once.`
|
||||
);
|
||||
logger.warn(
|
||||
"router",
|
||||
"A collision will result in a hard error in following versions of Astro."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (a.prerender || b.prerender) {
|
||||
return;
|
||||
}
|
||||
if (a.segments.length !== b.segments.length) {
|
||||
return;
|
||||
}
|
||||
const segmentCount = a.segments.length;
|
||||
for (let index = 0; index < segmentCount; index++) {
|
||||
const segmentA = a.segments[index];
|
||||
const segmentB = b.segments[index];
|
||||
if (!isSemanticallyEqualSegment(segmentA, segmentB)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.warn(
|
||||
"router",
|
||||
`The route "${a.route}" is defined in both "${a.component}" and "${b.component}" using SSR mode. A dynamic SSR route cannot be defined more than once.`
|
||||
);
|
||||
logger.warn("router", "A collision will result in a hard error in following versions of Astro.");
|
||||
}
|
||||
async function createRoutesList(params, logger, {
|
||||
dev = false
|
||||
} = {}) {
|
||||
const { settings } = params;
|
||||
const { config } = settings;
|
||||
const routeMap = /* @__PURE__ */ new Map();
|
||||
const fileBasedRoutes = createFileBasedRoutes(params, logger);
|
||||
for (const route of fileBasedRoutes) {
|
||||
routeMap.set(route.route, route);
|
||||
}
|
||||
const injectedRoutes = createInjectedRoutes(params);
|
||||
for (const route of injectedRoutes) {
|
||||
routeMap.set(route.route, route);
|
||||
}
|
||||
const redirectRoutes = createRedirectRoutes(params, routeMap);
|
||||
const filteredFiledBasedRoutes = fileBasedRoutes.filter((fileBasedRoute) => {
|
||||
const isRedirect = redirectRoutes.findIndex((rd) => rd.route === fileBasedRoute.route);
|
||||
return isRedirect < 0;
|
||||
});
|
||||
const routes = [
|
||||
...[...filteredFiledBasedRoutes, ...injectedRoutes, ...redirectRoutes].sort(routeComparator)
|
||||
];
|
||||
const limit = pLimit(10);
|
||||
let promises = [];
|
||||
for (const route of routes) {
|
||||
promises.push(
|
||||
limit(async () => {
|
||||
if (route.type !== "page" && route.type !== "endpoint" && route.type !== "redirect") return;
|
||||
if (route.type === "redirect" && !route.redirectRoute) return;
|
||||
const localFs = params.fsMod ?? nodeFs;
|
||||
const componentUrl = new URL(
|
||||
// The destination redirect might be a prerendered
|
||||
route.type === "redirect" && route.redirectRoute ? route.redirectRoute.component : route.component,
|
||||
settings.config.root
|
||||
);
|
||||
const content = await localFs.promises.readFile(componentUrl, "utf-8");
|
||||
await getRoutePrerenderOption(content, route, settings, logger);
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
for (const [index, higherRoute] of routes.entries()) {
|
||||
for (const lowerRoute of routes.slice(index + 1)) {
|
||||
detectRouteCollision(higherRoute, lowerRoute, config, logger);
|
||||
}
|
||||
}
|
||||
const i18n = settings.config.i18n;
|
||||
if (i18n) {
|
||||
const strategy = toRoutingStrategy(i18n.routing, i18n.domains);
|
||||
if (strategy === "pathname-prefix-always") {
|
||||
let index = routes.find((route) => route.route === "/");
|
||||
if (!index) {
|
||||
let relativePath = path.relative(
|
||||
fileURLToPath(settings.config.root),
|
||||
fileURLToPath(new URL("pages", settings.config.srcDir))
|
||||
);
|
||||
throw new AstroError({
|
||||
...MissingIndexForInternationalization,
|
||||
message: MissingIndexForInternationalization.message(i18n.defaultLocale),
|
||||
hint: MissingIndexForInternationalization.hint(relativePath)
|
||||
});
|
||||
}
|
||||
}
|
||||
createI18nFallbackRoutes(routes, i18n, config);
|
||||
}
|
||||
if (dev) {
|
||||
ensure404Route({ routes });
|
||||
}
|
||||
if (dev || settings.buildOutput === "server") {
|
||||
injectImageEndpoint(settings, { routes }, dev ? "dev" : "build");
|
||||
}
|
||||
if (dev || settings.config.adapter) {
|
||||
injectServerIslandRoute(settings.config, { routes });
|
||||
}
|
||||
await runHookRoutesResolved({ routes, settings, logger });
|
||||
return {
|
||||
routes
|
||||
};
|
||||
}
|
||||
function createI18nFallbackRoutes(routes, i18n, config) {
|
||||
const strategy = toRoutingStrategy(i18n.routing, i18n.domains);
|
||||
const routesByLocale = /* @__PURE__ */ new Map();
|
||||
const setRoutes = new Set(routes.filter((route) => route.type === "page"));
|
||||
const filteredLocales = i18n.locales.filter((loc) => {
|
||||
if (typeof loc === "string") {
|
||||
return loc !== i18n.defaultLocale;
|
||||
}
|
||||
return loc.path !== i18n.defaultLocale;
|
||||
}).map((locale) => {
|
||||
if (typeof locale === "string") {
|
||||
return locale;
|
||||
}
|
||||
return locale.path;
|
||||
});
|
||||
for (const locale of filteredLocales) {
|
||||
for (const route of setRoutes) {
|
||||
const hasLocaleInRoute = route.route.split("/").includes(locale);
|
||||
if (!hasLocaleInRoute) {
|
||||
continue;
|
||||
}
|
||||
const currentRoutes = routesByLocale.get(locale);
|
||||
if (currentRoutes) {
|
||||
currentRoutes.push(route);
|
||||
routesByLocale.set(locale, currentRoutes);
|
||||
} else {
|
||||
routesByLocale.set(locale, [route]);
|
||||
}
|
||||
setRoutes.delete(route);
|
||||
}
|
||||
}
|
||||
for (const route of setRoutes) {
|
||||
const currentRoutes = routesByLocale.get(i18n.defaultLocale);
|
||||
if (currentRoutes) {
|
||||
currentRoutes.push(route);
|
||||
routesByLocale.set(i18n.defaultLocale, currentRoutes);
|
||||
} else {
|
||||
routesByLocale.set(i18n.defaultLocale, [route]);
|
||||
}
|
||||
setRoutes.delete(route);
|
||||
}
|
||||
if (strategy === "pathname-prefix-always") {
|
||||
const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale);
|
||||
if (defaultLocaleRoutes) {
|
||||
const indexDefaultRoute = defaultLocaleRoutes.find(({ route }) => route === "/") ?? defaultLocaleRoutes.find(({ route }) => route === `/${i18n.defaultLocale}`);
|
||||
if (indexDefaultRoute) {
|
||||
const pathname = "/";
|
||||
const route = "/";
|
||||
const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
|
||||
validateSegment(s);
|
||||
return getParts(s, route);
|
||||
});
|
||||
routes.push({
|
||||
...indexDefaultRoute,
|
||||
pathname,
|
||||
route,
|
||||
segments,
|
||||
pattern: getPattern(segments, config.base, config.trailingSlash),
|
||||
type: "fallback"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i18n.fallback) {
|
||||
const fallback = Object.entries(i18n.fallback);
|
||||
if (fallback.length > 0) {
|
||||
for (const [fallbackFromLocale, fallbackToLocale] of fallback) {
|
||||
let fallbackToRoutes;
|
||||
if (fallbackToLocale === i18n.defaultLocale) {
|
||||
fallbackToRoutes = routesByLocale.get(i18n.defaultLocale);
|
||||
} else {
|
||||
fallbackToRoutes = routesByLocale.get(fallbackToLocale);
|
||||
}
|
||||
const fallbackFromRoutes = routesByLocale.get(fallbackFromLocale);
|
||||
if (!fallbackToRoutes) {
|
||||
continue;
|
||||
}
|
||||
for (const fallbackToRoute of fallbackToRoutes) {
|
||||
const hasRoute = fallbackFromRoutes && fallbackFromRoutes.some((route) => {
|
||||
if (fallbackToLocale === i18n.defaultLocale) {
|
||||
return route.route === `/${fallbackFromLocale}${fallbackToRoute.route}` || route.route.replace(`/${fallbackFromLocale}`, "") === fallbackToRoute.route;
|
||||
} else {
|
||||
const expectedRoute = replaceOrKeep(
|
||||
fallbackToRoute.route,
|
||||
fallbackToLocale,
|
||||
fallbackFromLocale
|
||||
);
|
||||
return route.route === expectedRoute;
|
||||
}
|
||||
});
|
||||
if (!hasRoute) {
|
||||
let pathname;
|
||||
let route;
|
||||
if (fallbackToLocale === i18n.defaultLocale && strategy === "pathname-prefix-other-locales") {
|
||||
if (fallbackToRoute.pathname) {
|
||||
pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`;
|
||||
}
|
||||
route = `/${fallbackFromLocale}${fallbackToRoute.route}`;
|
||||
} else {
|
||||
pathname = fallbackToRoute.pathname ? replaceOrKeep(fallbackToRoute.pathname, fallbackToLocale, fallbackFromLocale) : void 0;
|
||||
route = replaceOrKeep(fallbackToRoute.route, fallbackToLocale, fallbackFromLocale);
|
||||
}
|
||||
const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
|
||||
validateSegment(s);
|
||||
return getParts(s, route);
|
||||
});
|
||||
const index = routes.findIndex((r) => r === fallbackToRoute);
|
||||
if (index >= 0) {
|
||||
const fallbackRoute = {
|
||||
...fallbackToRoute,
|
||||
pathname,
|
||||
route,
|
||||
segments,
|
||||
pattern: getPattern(segments, config.base, config.trailingSlash),
|
||||
type: "fallback",
|
||||
fallbackRoutes: []
|
||||
};
|
||||
const routeData = routes[index];
|
||||
routeData.fallbackRoutes.push(fallbackRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function resolveInjectedRoute(entrypoint, root, cwd) {
|
||||
let resolved;
|
||||
try {
|
||||
resolved = require2.resolve(entrypoint, { paths: [cwd || fileURLToPath(root)] });
|
||||
} catch {
|
||||
resolved = fileURLToPath(new URL(entrypoint, root));
|
||||
}
|
||||
return {
|
||||
resolved,
|
||||
component: slash(path.relative(cwd || fileURLToPath(root), resolved))
|
||||
};
|
||||
}
|
||||
function joinSegments(segments) {
|
||||
const arr = segments.map((segment) => {
|
||||
return segment.map((rp) => rp.dynamic ? `[${rp.content}]` : rp.content).join("");
|
||||
});
|
||||
return `/${arr.join("/")}`.toLowerCase();
|
||||
}
|
||||
function replaceOrKeep(original, from, to) {
|
||||
if (original.startsWith(`/${to}/`) || original === `/${to}`) return original;
|
||||
return original.replace(`/${from}/`, `/${to}/`).replace(`/${from}`, `/${to}`);
|
||||
}
|
||||
export {
|
||||
createI18nFallbackRoutes,
|
||||
createRoutesFromEntries,
|
||||
createRoutesList,
|
||||
resolveInjectedRoute
|
||||
};
|
||||
10
node_modules/astro/dist/core/routing/default.d.ts
generated
vendored
Normal file
10
node_modules/astro/dist/core/routing/default.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ComponentInstance } from '../../types/astro.js';
|
||||
import type { SSRManifest } from '../app/types.js';
|
||||
export interface DefaultRouteParams {
|
||||
instance: ComponentInstance;
|
||||
matchesComponent(filePath: URL): boolean;
|
||||
route: string;
|
||||
component: string;
|
||||
}
|
||||
export declare const DEFAULT_COMPONENTS: string[];
|
||||
export declare function createDefaultRoutes(manifest: SSRManifest): DefaultRouteParams[];
|
||||
29
node_modules/astro/dist/core/routing/default.js
generated
vendored
Normal file
29
node_modules/astro/dist/core/routing/default.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import { DEFAULT_404_COMPONENT } from "../constants.js";
|
||||
import {
|
||||
createEndpoint as createServerIslandEndpoint,
|
||||
SERVER_ISLAND_COMPONENT,
|
||||
SERVER_ISLAND_ROUTE
|
||||
} from "../server-islands/endpoint.js";
|
||||
import { DEFAULT_404_ROUTE, default404Instance } from "./internal/astro-designed-error-pages.js";
|
||||
const DEFAULT_COMPONENTS = [DEFAULT_404_COMPONENT, SERVER_ISLAND_COMPONENT];
|
||||
function createDefaultRoutes(manifest) {
|
||||
const root = new URL(manifest.rootDir);
|
||||
return [
|
||||
{
|
||||
instance: default404Instance,
|
||||
matchesComponent: (filePath) => filePath.href === new URL(DEFAULT_404_COMPONENT, root).href,
|
||||
route: DEFAULT_404_ROUTE.route,
|
||||
component: DEFAULT_404_COMPONENT
|
||||
},
|
||||
{
|
||||
instance: createServerIslandEndpoint(manifest),
|
||||
matchesComponent: (filePath) => filePath.href === new URL(SERVER_ISLAND_COMPONENT, root).href,
|
||||
route: SERVER_ISLAND_ROUTE,
|
||||
component: SERVER_ISLAND_COMPONENT
|
||||
}
|
||||
];
|
||||
}
|
||||
export {
|
||||
DEFAULT_COMPONENTS,
|
||||
createDefaultRoutes
|
||||
};
|
||||
14
node_modules/astro/dist/core/routing/dev.d.ts
generated
vendored
Normal file
14
node_modules/astro/dist/core/routing/dev.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Use this module only to have functions needed in development
|
||||
*/
|
||||
import type { RoutesList } from '../../types/astro.js';
|
||||
import type { SSRManifest } from '../app/types.js';
|
||||
import type { RouteData } from '../../types/public/index.js';
|
||||
import type { RunnablePipeline } from '../../vite-plugin-app/pipeline.js';
|
||||
interface MatchedRoute {
|
||||
route: RouteData;
|
||||
filePath: URL;
|
||||
resolvedPathname: string;
|
||||
}
|
||||
export declare function matchRoute(pathname: string, routesList: RoutesList, pipeline: RunnablePipeline, manifest: SSRManifest): Promise<MatchedRoute | undefined>;
|
||||
export {};
|
||||
66
node_modules/astro/dist/core/routing/dev.js
generated
vendored
Normal file
66
node_modules/astro/dist/core/routing/dev.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import { matchAllRoutes } from "./match.js";
|
||||
import { getSortedPreloadedMatches } from "../../prerender/routing.js";
|
||||
import { getProps } from "../render/index.js";
|
||||
import { getCustom404Route } from "./helpers.js";
|
||||
import { NoMatchingStaticPathFound } from "../errors/errors-data.js";
|
||||
import { isAstroError } from "../errors/errors.js";
|
||||
async function matchRoute(pathname, routesList, pipeline, manifest) {
|
||||
const { logger, routeCache } = pipeline;
|
||||
const matches = matchAllRoutes(pathname, routesList);
|
||||
const preloadedMatches = getSortedPreloadedMatches({
|
||||
matches,
|
||||
manifest
|
||||
});
|
||||
for await (const { route: maybeRoute, filePath } of preloadedMatches) {
|
||||
try {
|
||||
await getProps({
|
||||
mod: await pipeline.getComponentByRoute(maybeRoute),
|
||||
routeData: maybeRoute,
|
||||
routeCache,
|
||||
pathname,
|
||||
logger,
|
||||
serverLike: pipeline.manifest.serverLike,
|
||||
base: manifest.base,
|
||||
trailingSlash: manifest.trailingSlash
|
||||
});
|
||||
return {
|
||||
route: maybeRoute,
|
||||
filePath,
|
||||
resolvedPathname: pathname
|
||||
};
|
||||
} catch (e) {
|
||||
if (isAstroError(e) && e.title === NoMatchingStaticPathFound.title) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const altPathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
|
||||
if (altPathname !== pathname) {
|
||||
return await matchRoute(altPathname, routesList, pipeline, manifest);
|
||||
}
|
||||
if (matches.length) {
|
||||
const possibleRoutes = matches.flatMap((route) => route.component);
|
||||
logger.warn(
|
||||
"router",
|
||||
`${NoMatchingStaticPathFound.message(
|
||||
pathname
|
||||
)}
|
||||
|
||||
${NoMatchingStaticPathFound.hint(possibleRoutes)}`
|
||||
);
|
||||
}
|
||||
const custom404 = getCustom404Route(routesList);
|
||||
if (custom404) {
|
||||
const filePath = new URL(`./${custom404.component}`, manifest.rootDir);
|
||||
return {
|
||||
route: custom404,
|
||||
filePath,
|
||||
resolvedPathname: pathname
|
||||
};
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
export {
|
||||
matchRoute
|
||||
};
|
||||
5
node_modules/astro/dist/core/routing/generator.d.ts
generated
vendored
Normal file
5
node_modules/astro/dist/core/routing/generator.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { AstroConfig } from '../../types/public/config.js';
|
||||
import type { RoutePart } from '../../types/public/internal.js';
|
||||
type RouteGenerator = (data?: any) => string;
|
||||
export declare function getRouteGenerator(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']): RouteGenerator;
|
||||
export {};
|
||||
41
node_modules/astro/dist/core/routing/generator.js
generated
vendored
Normal file
41
node_modules/astro/dist/core/routing/generator.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import { collapseDuplicateLeadingSlashes } from "@astrojs/internal-helpers/path";
|
||||
function sanitizeParams(params) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return [key, value.normalize().replace(/#/g, "%23").replace(/\?/g, "%3F")];
|
||||
}
|
||||
return [key, value];
|
||||
})
|
||||
);
|
||||
}
|
||||
function getParameter(part, params) {
|
||||
if (part.spread) {
|
||||
return params[part.content.slice(3)] || "";
|
||||
}
|
||||
if (part.dynamic) {
|
||||
if (!params[part.content]) {
|
||||
throw new TypeError(`Missing parameter: ${part.content}`);
|
||||
}
|
||||
return params[part.content];
|
||||
}
|
||||
return part.content.normalize().replace(/\?/g, "%3F").replace(/#/g, "%23").replace(/%5B/g, "[").replace(/%5D/g, "]");
|
||||
}
|
||||
function getSegment(segment, params) {
|
||||
const segmentPath = segment.map((part) => getParameter(part, params)).join("");
|
||||
return segmentPath ? collapseDuplicateLeadingSlashes("/" + segmentPath) : "";
|
||||
}
|
||||
function getRouteGenerator(segments, addTrailingSlash) {
|
||||
return (params) => {
|
||||
const sanitizedParams = sanitizeParams(params);
|
||||
let trailing = "";
|
||||
if (addTrailingSlash === "always" && segments.length) {
|
||||
trailing = "/";
|
||||
}
|
||||
const path = segments.map((segment) => getSegment(segment, sanitizedParams)).join("") + trailing;
|
||||
return path || "/";
|
||||
};
|
||||
}
|
||||
export {
|
||||
getRouteGenerator
|
||||
};
|
||||
17
node_modules/astro/dist/core/routing/handler.d.ts
generated
vendored
Normal file
17
node_modules/astro/dist/core/routing/handler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { FetchState } from '../fetch/fetch-state.js';
|
||||
import type { BaseApp } from '../app/base.js';
|
||||
import { type Pipeline } from '../base-pipeline.js';
|
||||
export declare class AstroHandler {
|
||||
#private;
|
||||
constructor(app: BaseApp<Pipeline>);
|
||||
handle(state: FetchState): Promise<Response>;
|
||||
/**
|
||||
* Renders a response for the given `FetchState`. Assumes
|
||||
* trailing-slash redirects and routeData resolution have already run.
|
||||
*
|
||||
* User-triggered rewrites (`Astro.rewrite` / `ctx.rewrite`) go through
|
||||
* `Rewrites.execute` on the current `FetchState` — they mutate the
|
||||
* existing state in place and re-run middleware + page dispatch.
|
||||
*/
|
||||
render(state: FetchState): Promise<Response>;
|
||||
}
|
||||
172
node_modules/astro/dist/core/routing/handler.js
generated
vendored
Normal file
172
node_modules/astro/dist/core/routing/handler.js
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import { ActionHandler } from "../../actions/handler.js";
|
||||
import {
|
||||
REROUTABLE_STATUS_CODES,
|
||||
REROUTE_DIRECTIVE_HEADER,
|
||||
REWRITE_DIRECTIVE_HEADER_KEY
|
||||
} from "../constants.js";
|
||||
import { TrailingSlashHandler } from "./trailing-slash-handler.js";
|
||||
import { CacheHandler, provideCache } from "../cache/handler.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 { provideSession } from "../session/handler.js";
|
||||
import { prepareResponse } from "../app/prepare-response.js";
|
||||
import { PipelineFeatures } from "../base-pipeline.js";
|
||||
class AstroHandler {
|
||||
#app;
|
||||
#trailingSlashHandler;
|
||||
#actionHandler;
|
||||
#astroMiddleware;
|
||||
#pagesHandler;
|
||||
#cacheHandler;
|
||||
/** Bound callback for the middleware chain — created once, reused per request. */
|
||||
#renderRouteCallback;
|
||||
/**
|
||||
* i18n post-processor. Only set when the app has i18n configured and
|
||||
* the strategy is not `manual` — for the manual strategy users wire
|
||||
* `astro:i18n.middleware(...)` into their own `onRequest`.
|
||||
*/
|
||||
#i18n;
|
||||
/** Whether sessions are configured on the manifest. */
|
||||
#hasSession;
|
||||
constructor(app) {
|
||||
this.#app = app;
|
||||
this.#trailingSlashHandler = new TrailingSlashHandler(app);
|
||||
this.#actionHandler = new ActionHandler();
|
||||
this.#astroMiddleware = new AstroMiddleware(app.pipeline);
|
||||
this.#pagesHandler = new PagesHandler(app.pipeline);
|
||||
this.#cacheHandler = new CacheHandler(app);
|
||||
this.#renderRouteCallback = this.#actionsAndPages.bind(this);
|
||||
this.#hasSession = !!app.manifest.sessionConfig;
|
||||
const i18n = app.manifest.i18n;
|
||||
if (i18n && i18n.strategy !== "manual") {
|
||||
this.#i18n = new I18n(
|
||||
i18n,
|
||||
app.manifest.base,
|
||||
app.manifest.trailingSlash,
|
||||
app.manifest.buildFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Runs actions then pages — the callback at the bottom of the
|
||||
* middleware chain. Bound once in the constructor to avoid
|
||||
* per-request closure allocation.
|
||||
*/
|
||||
#actionsAndPages(state, ctx) {
|
||||
if (!state.skipMiddleware) {
|
||||
const actionResult = this.#actionHandler.handle(ctx, state);
|
||||
if (actionResult) {
|
||||
return actionResult.then((response) => response ?? this.#pagesHandler.handle(state, ctx));
|
||||
}
|
||||
}
|
||||
return this.#pagesHandler.handle(state, ctx);
|
||||
}
|
||||
async handle(state) {
|
||||
const trailingSlashRedirect = this.#trailingSlashHandler.handle(state);
|
||||
if (trailingSlashRedirect) {
|
||||
return trailingSlashRedirect;
|
||||
}
|
||||
if (!state.routeData) {
|
||||
return this.#app.renderError(state.request, {
|
||||
...state.renderOptions,
|
||||
status: 404,
|
||||
pathname: state.pathname
|
||||
});
|
||||
}
|
||||
return this.render(state);
|
||||
}
|
||||
/**
|
||||
* Renders a response for the given `FetchState`. Assumes
|
||||
* trailing-slash redirects and routeData resolution have already run.
|
||||
*
|
||||
* User-triggered rewrites (`Astro.rewrite` / `ctx.rewrite`) go through
|
||||
* `Rewrites.execute` on the current `FetchState` — they mutate the
|
||||
* existing state in place and re-run middleware + page dispatch.
|
||||
*/
|
||||
async render(state) {
|
||||
const routeData = state.routeData;
|
||||
const pathname = state.pathname;
|
||||
const request = state.request;
|
||||
const { addCookieHeader } = state.renderOptions;
|
||||
const defaultStatus = this.#app.getDefaultStatusCode(routeData, pathname);
|
||||
state.status = defaultStatus;
|
||||
let response;
|
||||
try {
|
||||
if (this.#hasSession || this.#app.pipeline.cacheConfig) {
|
||||
const sessionP = this.#hasSession ? provideSession(state) : void 0;
|
||||
const cacheP = this.#app.pipeline.cacheConfig ? provideCache(state) : void 0;
|
||||
if (sessionP || cacheP) await Promise.all([sessionP, cacheP]);
|
||||
}
|
||||
state.pipeline.usedFeatures |= PipelineFeatures.sessions;
|
||||
if (routeData.type === "redirect") {
|
||||
const redirectResponse = await renderRedirect(state);
|
||||
this.#app.logThisRequest({
|
||||
pathname,
|
||||
method: request.method,
|
||||
statusCode: redirectResponse.status,
|
||||
isRewrite: false,
|
||||
timeStart: state.timeStart
|
||||
});
|
||||
prepareResponse(redirectResponse, { addCookieHeader });
|
||||
this.#app.pipeline.logger.flush();
|
||||
return redirectResponse;
|
||||
}
|
||||
if (!this.#app.pipeline.cacheProvider) {
|
||||
this.#app.pipeline.usedFeatures |= PipelineFeatures.cache;
|
||||
response = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
|
||||
if (this.#i18n) {
|
||||
response = await this.#i18n.finalize(state, response);
|
||||
}
|
||||
} else {
|
||||
const runPipeline = async () => {
|
||||
let res = await this.#astroMiddleware.handle(state, this.#renderRouteCallback);
|
||||
if (this.#i18n) {
|
||||
res = await this.#i18n.finalize(state, res);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
response = await this.#cacheHandler.handle(state, runPipeline);
|
||||
}
|
||||
const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
|
||||
this.#app.logThisRequest({
|
||||
pathname,
|
||||
method: request.method,
|
||||
statusCode: response.status,
|
||||
isRewrite,
|
||||
timeStart: state.timeStart
|
||||
});
|
||||
} catch (err) {
|
||||
this.#app.logger.error(null, err.stack || err.message || String(err));
|
||||
return this.#app.renderError(request, {
|
||||
...state.renderOptions,
|
||||
status: 500,
|
||||
error: err,
|
||||
pathname: state.pathname
|
||||
});
|
||||
} finally {
|
||||
const finalize = state.finalizeAll();
|
||||
if (finalize) await finalize;
|
||||
}
|
||||
if (REROUTABLE_STATUS_CODES.includes(response.status) && // If the body isn't null, that means the user sets the 404 status
|
||||
// but uses the current route to handle the 404
|
||||
response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
|
||||
return this.#app.renderError(request, {
|
||||
...state.renderOptions,
|
||||
response,
|
||||
status: response.status,
|
||||
// We don't have an error to report here. Passing null means we pass nothing intentionally
|
||||
// while undefined means there's no error
|
||||
error: response.status === 500 ? null : void 0,
|
||||
pathname: state.pathname
|
||||
});
|
||||
}
|
||||
prepareResponse(response, { addCookieHeader });
|
||||
this.#app.pipeline.logger.flush();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
export {
|
||||
AstroHandler
|
||||
};
|
||||
49
node_modules/astro/dist/core/routing/helpers.d.ts
generated
vendored
Normal file
49
node_modules/astro/dist/core/routing/helpers.d.ts
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
import type { IntegrationResolvedRoute } from '../../types/public/integrations.js';
|
||||
import type { RouteInfo } from '../app/types.js';
|
||||
import type { RoutesList } from '../../types/astro.js';
|
||||
type RedirectRouteData = RouteData & {
|
||||
redirect: string;
|
||||
};
|
||||
/**
|
||||
* Function guard that checks if a route is redirect. If so, `RouteData.redirectRoute` and
|
||||
* `RouteData.redirect` aren't `undefined` anymore
|
||||
* @param route
|
||||
*/
|
||||
export declare function routeIsRedirect(route: RouteData | undefined): route is RedirectRouteData;
|
||||
/**
|
||||
* True if the route represents a fallback entry.
|
||||
*/
|
||||
export declare function routeIsFallback(route: RouteData | undefined): boolean;
|
||||
/**
|
||||
* Give a route, it returns its fallback routes from a `list` of `RouteInfo[]`.
|
||||
*
|
||||
* It throws an error if no fallback routes were found. This means there's an error
|
||||
* when we construct the list of routes
|
||||
* @param route
|
||||
* @param routeList
|
||||
*/
|
||||
export declare function getFallbackRoute(route: RouteData, routeList: RouteInfo[]): RouteData;
|
||||
/**
|
||||
* Return a user-provided 404 route if one exists.
|
||||
*/
|
||||
export declare function getCustom404Route(manifestData: RoutesList): RouteData | undefined;
|
||||
/**
|
||||
* Return a user-provided 500 route if one exists.
|
||||
*/
|
||||
export declare function getCustom500Route(manifestData: RoutesList): RouteData | undefined;
|
||||
/**
|
||||
* Returns true if the route definition contains `.html` as a static segment part,
|
||||
* as is the case for routes like `[slug].html.astro`. Used to avoid stripping the
|
||||
* `.html` suffix from pathnames that intentionally include it.
|
||||
*/
|
||||
export declare function routeHasHtmlExtension(route: RouteData): boolean;
|
||||
export declare function hasNonPrerenderedRoute(routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>, options?: {
|
||||
includeEndpoints?: boolean;
|
||||
includeExternal?: boolean;
|
||||
}): boolean;
|
||||
export declare function hasNonPrerenderedRoute(routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>, options?: {
|
||||
includeEndpoints?: boolean;
|
||||
includeExternal?: boolean;
|
||||
}): boolean;
|
||||
export {};
|
||||
51
node_modules/astro/dist/core/routing/helpers.js
generated
vendored
Normal file
51
node_modules/astro/dist/core/routing/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import { isRoute404, isRoute500 } from "./internal/route-errors.js";
|
||||
function routeIsRedirect(route) {
|
||||
return route?.type === "redirect";
|
||||
}
|
||||
function routeIsFallback(route) {
|
||||
return route?.type === "fallback";
|
||||
}
|
||||
function getFallbackRoute(route, routeList) {
|
||||
const fallbackRoute = routeList.find((r) => {
|
||||
if (route.route === "/" && r.routeData.route === "/") {
|
||||
return true;
|
||||
}
|
||||
return r.routeData.fallbackRoutes.find((f) => {
|
||||
return f.route === route.route;
|
||||
});
|
||||
});
|
||||
if (!fallbackRoute) {
|
||||
throw new Error(`No fallback route found for route ${route.route}`);
|
||||
}
|
||||
return fallbackRoute.routeData;
|
||||
}
|
||||
function getCustom404Route(manifestData) {
|
||||
return manifestData.routes.find((r) => isRoute404(r.route));
|
||||
}
|
||||
function getCustom500Route(manifestData) {
|
||||
return manifestData.routes.find((r) => isRoute500(r.route));
|
||||
}
|
||||
function routeHasHtmlExtension(route) {
|
||||
return route.segments.some(
|
||||
(segment) => segment.some((part) => !part.dynamic && part.content.includes(".html"))
|
||||
);
|
||||
}
|
||||
function hasNonPrerenderedRoute(routes, options) {
|
||||
const includeEndpoints = options?.includeEndpoints ?? true;
|
||||
const includeExternal = options?.includeExternal ?? false;
|
||||
const routeTypes = includeEndpoints ? ["page", "endpoint"] : ["page"];
|
||||
const origins = includeExternal ? ["project", "external"] : ["project"];
|
||||
return routes.some((route) => {
|
||||
const isPrerendered = "isPrerendered" in route ? route.isPrerendered : route.prerender;
|
||||
return routeTypes.includes(route.type) && origins.includes(route.origin) && !isPrerendered;
|
||||
});
|
||||
}
|
||||
export {
|
||||
getCustom404Route,
|
||||
getCustom500Route,
|
||||
getFallbackRoute,
|
||||
hasNonPrerenderedRoute,
|
||||
routeHasHtmlExtension,
|
||||
routeIsFallback,
|
||||
routeIsRedirect
|
||||
};
|
||||
4
node_modules/astro/dist/core/routing/internal/astro-designed-error-pages.d.ts
generated
vendored
Normal file
4
node_modules/astro/dist/core/routing/internal/astro-designed-error-pages.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { ComponentInstance } from '../../../types/astro.js';
|
||||
import type { RouteData } from '../../../types/public/internal.js';
|
||||
export declare const DEFAULT_404_ROUTE: RouteData;
|
||||
export declare const default404Instance: ComponentInstance;
|
||||
35
node_modules/astro/dist/core/routing/internal/astro-designed-error-pages.js
generated
vendored
Normal file
35
node_modules/astro/dist/core/routing/internal/astro-designed-error-pages.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import notFoundTemplate from "../../../template/4xx.js";
|
||||
import { DEFAULT_404_COMPONENT } from "../../constants.js";
|
||||
const DEFAULT_404_ROUTE = {
|
||||
component: DEFAULT_404_COMPONENT,
|
||||
params: [],
|
||||
pattern: /^\/404\/?$/,
|
||||
prerender: false,
|
||||
pathname: "/404",
|
||||
segments: [[{ content: "404", dynamic: false, spread: false }]],
|
||||
type: "page",
|
||||
route: "/404",
|
||||
fallbackRoutes: [],
|
||||
isIndex: false,
|
||||
origin: "internal",
|
||||
distURL: []
|
||||
};
|
||||
async function default404Page({ pathname }) {
|
||||
return new Response(
|
||||
notFoundTemplate({
|
||||
statusCode: 404,
|
||||
title: "Not found",
|
||||
tabTitle: "404: Not Found",
|
||||
pathname
|
||||
}),
|
||||
{ status: 404, headers: { "Content-Type": "text/html" } }
|
||||
);
|
||||
}
|
||||
default404Page.isAstroComponentFactory = true;
|
||||
const default404Instance = {
|
||||
default: default404Page
|
||||
};
|
||||
export {
|
||||
DEFAULT_404_ROUTE,
|
||||
default404Instance
|
||||
};
|
||||
2
node_modules/astro/dist/core/routing/internal/route-errors.d.ts
generated
vendored
Normal file
2
node_modules/astro/dist/core/routing/internal/route-errors.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export declare function isRoute404(route: string): boolean;
|
||||
export declare function isRoute500(route: string): boolean;
|
||||
12
node_modules/astro/dist/core/routing/internal/route-errors.js
generated
vendored
Normal file
12
node_modules/astro/dist/core/routing/internal/route-errors.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
const ROUTE404_RE = /^\/404\/?$/;
|
||||
const ROUTE500_RE = /^\/500\/?$/;
|
||||
function isRoute404(route) {
|
||||
return ROUTE404_RE.test(route);
|
||||
}
|
||||
function isRoute500(route) {
|
||||
return ROUTE500_RE.test(route);
|
||||
}
|
||||
export {
|
||||
isRoute404,
|
||||
isRoute500
|
||||
};
|
||||
2
node_modules/astro/dist/core/routing/internal/validation.d.ts
generated
vendored
Normal file
2
node_modules/astro/dist/core/routing/internal/validation.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Throws error for invalid parameter in getStaticPaths() response */
|
||||
export declare function validateGetStaticPathsParameter([key, value]: [string, any], route: string): void;
|
||||
16
node_modules/astro/dist/core/routing/internal/validation.js
generated
vendored
Normal file
16
node_modules/astro/dist/core/routing/internal/validation.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { AstroError, AstroErrorData } from "../../errors/index.js";
|
||||
const VALID_PARAM_TYPES = ["string", "undefined"];
|
||||
function validateGetStaticPathsParameter([key, value], route) {
|
||||
if (!VALID_PARAM_TYPES.includes(typeof value)) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.GetStaticPathsInvalidRouteParam,
|
||||
message: AstroErrorData.GetStaticPathsInvalidRouteParam.message(key, value, typeof value),
|
||||
location: {
|
||||
file: route
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export {
|
||||
validateGetStaticPathsParameter
|
||||
};
|
||||
20
node_modules/astro/dist/core/routing/match.d.ts
generated
vendored
Normal file
20
node_modules/astro/dist/core/routing/match.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { RoutesList } from '../../types/astro.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/** Find matching route from pathname */
|
||||
export declare function matchRoute(pathname: string, manifest: RoutesList): RouteData | undefined;
|
||||
/** Finds all matching routes from pathname */
|
||||
export declare function matchAllRoutes(pathname: string, manifest: RoutesList): RouteData[];
|
||||
/**
|
||||
* Determines if the given route matches a 404 or 500 error page.
|
||||
*
|
||||
* @param {RouteData} route - The route data to check.
|
||||
* @returns {boolean} `true` if the route matches a 404 or 500 error page; otherwise, `false`.
|
||||
*/
|
||||
export declare function isRoute404or500(route: RouteData): boolean;
|
||||
/**
|
||||
* Determines if a given route is associated with the server island component.
|
||||
*
|
||||
* @param {RouteData} route - The route data object to evaluate.
|
||||
* @return {boolean} Returns true if the route's component is the server island component; otherwise, false.
|
||||
*/
|
||||
export declare function isRouteServerIsland(route: RouteData): boolean;
|
||||
30
node_modules/astro/dist/core/routing/match.js
generated
vendored
Normal file
30
node_modules/astro/dist/core/routing/match.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { SERVER_ISLAND_COMPONENT } from "../server-islands/endpoint.js";
|
||||
import { isRoute404, isRoute500 } from "./internal/route-errors.js";
|
||||
function matchRoute(pathname, manifest) {
|
||||
if (isRoute404(pathname)) {
|
||||
const errorRoute = manifest.routes.find((route) => isRoute404(route.route));
|
||||
if (errorRoute) return errorRoute;
|
||||
}
|
||||
if (isRoute500(pathname)) {
|
||||
const errorRoute = manifest.routes.find((route) => isRoute500(route.route));
|
||||
if (errorRoute) return errorRoute;
|
||||
}
|
||||
return manifest.routes.find((route) => {
|
||||
return route.pattern.test(pathname) || route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
|
||||
});
|
||||
}
|
||||
function matchAllRoutes(pathname, manifest) {
|
||||
return manifest.routes.filter((route) => route.pattern.test(pathname));
|
||||
}
|
||||
function isRoute404or500(route) {
|
||||
return isRoute404(route.route) || isRoute500(route.route);
|
||||
}
|
||||
function isRouteServerIsland(route) {
|
||||
return route.component === SERVER_ISLAND_COMPONENT;
|
||||
}
|
||||
export {
|
||||
isRoute404or500,
|
||||
isRouteServerIsland,
|
||||
matchAllRoutes,
|
||||
matchRoute
|
||||
};
|
||||
9
node_modules/astro/dist/core/routing/params.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/core/routing/params.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { GetStaticPathsItem } from '../../types/public/common.js';
|
||||
import type { AstroConfig } from '../../types/public/index.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/**
|
||||
* given a route's Params object, validate parameter
|
||||
* values and create a stringified key for the route
|
||||
* that can be used to match request routes
|
||||
*/
|
||||
export declare function stringifyParams(params: GetStaticPathsItem['params'], route: RouteData, trailingSlash: AstroConfig['trailingSlash']): string;
|
||||
16
node_modules/astro/dist/core/routing/params.js
generated
vendored
Normal file
16
node_modules/astro/dist/core/routing/params.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { trimSlashes } from "../path.js";
|
||||
import { getRouteGenerator } from "./generator.js";
|
||||
import { validateGetStaticPathsParameter } from "./internal/validation.js";
|
||||
function stringifyParams(params, route, trailingSlash) {
|
||||
const validatedParams = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
validateGetStaticPathsParameter([key, value], route.component);
|
||||
if (value !== void 0) {
|
||||
validatedParams[key] = trimSlashes(value);
|
||||
}
|
||||
}
|
||||
return getRouteGenerator(route.segments, trailingSlash)(validatedParams);
|
||||
}
|
||||
export {
|
||||
stringifyParams
|
||||
};
|
||||
22
node_modules/astro/dist/core/routing/parse-route.d.ts
generated
vendored
Normal file
22
node_modules/astro/dist/core/routing/parse-route.d.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { AstroSettings } from '../../types/astro.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/**
|
||||
* Settings needed to parse a route path into RouteData.
|
||||
*/
|
||||
type ParseRouteConfig = Pick<AstroSettings, 'config' | 'pageExtensions'>;
|
||||
/**
|
||||
* Options for building the RouteData output.
|
||||
*/
|
||||
type ParseRouteOptions = {
|
||||
component: string;
|
||||
type?: RouteData['type'];
|
||||
origin?: RouteData['origin'];
|
||||
isIndex?: boolean;
|
||||
prerender?: boolean;
|
||||
params?: string[];
|
||||
};
|
||||
/**
|
||||
* Parse a file path-like route into RouteData, respecting extensions and config.
|
||||
*/
|
||||
export declare function parseRoute(route: string, options: ParseRouteConfig, parseOptions: ParseRouteOptions): RouteData;
|
||||
export {};
|
||||
65
node_modules/astro/dist/core/routing/parse-route.js
generated
vendored
Normal file
65
node_modules/astro/dist/core/routing/parse-route.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { fileExtension } from "@astrojs/internal-helpers/path";
|
||||
import { removeLeadingForwardSlash, removeTrailingForwardSlash } from "../path.js";
|
||||
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "../constants.js";
|
||||
import { getPattern } from "./pattern.js";
|
||||
import { getParts } from "./parts.js";
|
||||
import { validateSegment } from "./segment.js";
|
||||
const ROUTE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
||||
".astro",
|
||||
".md",
|
||||
".mdx",
|
||||
".markdown",
|
||||
".js",
|
||||
".ts",
|
||||
...SUPPORTED_MARKDOWN_FILE_EXTENSIONS
|
||||
]);
|
||||
function parseRoute(route, options, parseOptions) {
|
||||
const routeFileExtensions = options.pageExtensions?.length ? /* @__PURE__ */ new Set([...ROUTE_FILE_EXTENSIONS, ...options.pageExtensions]) : ROUTE_FILE_EXTENSIONS;
|
||||
const normalizedRoute = removeTrailingForwardSlash(removeLeadingForwardSlash(route));
|
||||
const segments = [];
|
||||
const rawSegments = normalizedRoute ? normalizedRoute.split("/") : [];
|
||||
let isIndex = parseOptions.isIndex ?? false;
|
||||
if (rawSegments.length > 0) {
|
||||
const last = rawSegments.at(-1);
|
||||
const ext = fileExtension(last);
|
||||
if (ext && routeFileExtensions.has(ext)) {
|
||||
const base = last.slice(0, -ext.length);
|
||||
rawSegments[rawSegments.length - 1] = base;
|
||||
if (base === "index") {
|
||||
isIndex = true;
|
||||
rawSegments.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const segment of rawSegments) {
|
||||
validateSegment(segment, route);
|
||||
segments.push(getParts(segment, route));
|
||||
}
|
||||
const routePath = joinSegments(segments);
|
||||
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) ? `/${segments.map((segment) => segment[0].content).join("/")}` : null;
|
||||
const params = parseOptions.params ?? segments.flat().filter((part) => part.dynamic).map((part) => part.content);
|
||||
return {
|
||||
route: routePath,
|
||||
component: parseOptions.component,
|
||||
params,
|
||||
pathname: pathname || void 0,
|
||||
pattern: getPattern(segments, options.config.base, options.config.trailingSlash),
|
||||
segments,
|
||||
type: parseOptions.type ?? "page",
|
||||
prerender: parseOptions.prerender ?? false,
|
||||
fallbackRoutes: [],
|
||||
distURL: [],
|
||||
isIndex,
|
||||
origin: parseOptions.origin ?? "project"
|
||||
};
|
||||
}
|
||||
function joinSegments(segments) {
|
||||
if (segments.length === 0) return "/";
|
||||
const arr = segments.map((segment) => {
|
||||
return segment.map((part) => part.dynamic ? `[${part.content}]` : part.content).join("");
|
||||
});
|
||||
return `/${arr.join("/")}`.toLowerCase();
|
||||
}
|
||||
export {
|
||||
parseRoute
|
||||
};
|
||||
2
node_modules/astro/dist/core/routing/parts.d.ts
generated
vendored
Normal file
2
node_modules/astro/dist/core/routing/parts.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import type { RoutePart } from '../../types/public/index.js';
|
||||
export declare function getParts(part: string, file: string): RoutePart[];
|
||||
22
node_modules/astro/dist/core/routing/parts.js
generated
vendored
Normal file
22
node_modules/astro/dist/core/routing/parts.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
|
||||
const ROUTE_SPREAD = /^\.{3}.+$/;
|
||||
function getParts(part, file) {
|
||||
const result = [];
|
||||
part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
|
||||
if (!str) return;
|
||||
const dynamic = i % 2 === 1;
|
||||
const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
|
||||
if (!content || dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content)) {
|
||||
throw new Error(`Invalid route ${file} \u2014 parameter name must match /^[a-zA-Z0-9_$]+$/`);
|
||||
}
|
||||
result.push({
|
||||
content,
|
||||
dynamic,
|
||||
spread: dynamic && ROUTE_SPREAD.test(content)
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
export {
|
||||
getParts
|
||||
};
|
||||
3
node_modules/astro/dist/core/routing/pattern.d.ts
generated
vendored
Normal file
3
node_modules/astro/dist/core/routing/pattern.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { AstroConfig } from '../../types/public/config.js';
|
||||
import type { RoutePart } from '../../types/public/internal.js';
|
||||
export declare function getPattern(segments: RoutePart[][], base: AstroConfig['base'], addTrailingSlash: AstroConfig['trailingSlash']): RegExp;
|
||||
35
node_modules/astro/dist/core/routing/pattern.js
generated
vendored
Normal file
35
node_modules/astro/dist/core/routing/pattern.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
function getPattern(segments, base, addTrailingSlash) {
|
||||
const pathname = segments.map((segment) => {
|
||||
if (segment.length === 1 && segment[0].spread) {
|
||||
return "(?:\\/(.*?))?";
|
||||
} else {
|
||||
return "\\/" + segment.map((part) => {
|
||||
if (part.spread) {
|
||||
return "(.*?)";
|
||||
} else if (part.dynamic) {
|
||||
return "([^/]+?)";
|
||||
} else {
|
||||
return part.content.normalize().replace(/\?/g, "%3F").replace(/#/g, "%23").replace(/%5B/g, "[").replace(/%5D/g, "]").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
}).join("");
|
||||
}
|
||||
}).join("");
|
||||
const trailing = addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : "$";
|
||||
let initial = "\\/";
|
||||
if (addTrailingSlash === "never" && base !== "/" && pathname !== "") {
|
||||
initial = "";
|
||||
}
|
||||
return new RegExp(`^${pathname || initial}${trailing}`);
|
||||
}
|
||||
function getTrailingSlashPattern(addTrailingSlash) {
|
||||
if (addTrailingSlash === "always") {
|
||||
return "\\/$";
|
||||
}
|
||||
if (addTrailingSlash === "never") {
|
||||
return "$";
|
||||
}
|
||||
return "\\/?$";
|
||||
}
|
||||
export {
|
||||
getPattern
|
||||
};
|
||||
9
node_modules/astro/dist/core/routing/prerender.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/core/routing/prerender.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { AstroSettings } from '../../types/astro.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
import type { AstroLogger } from '../logger/core.js';
|
||||
/**
|
||||
* Parses the `export const prerender = true|false` declaration from a route's
|
||||
* source content. Returns `true`, `false`, or `undefined` if not present.
|
||||
*/
|
||||
export declare function parsePrerenderExport(content: string): boolean | undefined;
|
||||
export declare function getRoutePrerenderOption(content: string, route: RouteData, settings: AstroSettings, logger: AstroLogger): Promise<void>;
|
||||
26
node_modules/astro/dist/core/routing/prerender.js
generated
vendored
Normal file
26
node_modules/astro/dist/core/routing/prerender.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { runHookRouteSetup } from "../../integrations/hooks.js";
|
||||
import { getPrerenderDefault } from "../../prerender/utils.js";
|
||||
const PRERENDER_REGEX = /^\s*export\s+const\s+prerender\s*=\s*(true|false);?/m;
|
||||
function parsePrerenderExport(content) {
|
||||
const match = PRERENDER_REGEX.exec(content);
|
||||
if (!match) return void 0;
|
||||
return match[1] === "true";
|
||||
}
|
||||
async function getRoutePrerenderOption(content, route, settings, logger) {
|
||||
const match = PRERENDER_REGEX.exec(content);
|
||||
if (match) {
|
||||
route.prerender = match[1] === "true";
|
||||
if (route.redirectRoute) {
|
||||
route.redirectRoute.prerender = match[1] === "true";
|
||||
}
|
||||
}
|
||||
await runHookRouteSetup({ route, settings, logger });
|
||||
if (typeof route.prerender === void 0) {
|
||||
route.prerender = getPrerenderDefault(settings.config);
|
||||
}
|
||||
if (!route.prerender) settings.buildOutput = "server";
|
||||
}
|
||||
export {
|
||||
getRoutePrerenderOption,
|
||||
parsePrerenderExport
|
||||
};
|
||||
23
node_modules/astro/dist/core/routing/priority.d.ts
generated
vendored
Normal file
23
node_modules/astro/dist/core/routing/priority.d.ts
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/**
|
||||
* Comparator for sorting routes in resolution order.
|
||||
*
|
||||
* The routes are sorted in by the following rules in order, following the first rule that
|
||||
* applies:
|
||||
* - More specific routes are sorted before less specific routes. Here, "specific" means
|
||||
* the number of segments in the route, so a parent route is always sorted after its children.
|
||||
* For example, `/foo/bar` is sorted before `/foo`.
|
||||
* Index routes, originating from a file named `index.astro`, are considered to have one more
|
||||
* segment than the URL they represent.
|
||||
* - Static routes are sorted before dynamic routes.
|
||||
* For example, `/foo/bar` is sorted before `/foo/[bar]`.
|
||||
* - Dynamic routes with single parameters are sorted before dynamic routes with rest parameters.
|
||||
* For example, `/foo/[bar]` is sorted before `/foo/[...bar]`.
|
||||
* - Prerendered routes are sorted before non-prerendered routes.
|
||||
* - Endpoints are sorted before pages.
|
||||
* For example, a file `/foo.ts` is sorted before `/bar.astro`.
|
||||
* - If both routes are equal regarding all previous conditions, they are sorted alphabetically.
|
||||
* For example, `/bar` is sorted before `/foo`.
|
||||
* The definition of "alphabetically" is dependent on the default locale of the running system.
|
||||
*/
|
||||
export declare function routeComparator(a: RouteData, b: RouteData): number;
|
||||
51
node_modules/astro/dist/core/routing/priority.js
generated
vendored
Normal file
51
node_modules/astro/dist/core/routing/priority.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
function routeComparator(a, b) {
|
||||
const commonLength = Math.min(a.segments.length, b.segments.length);
|
||||
for (let index = 0; index < commonLength; index++) {
|
||||
const aSegment = a.segments[index];
|
||||
const bSegment = b.segments[index];
|
||||
const aIsStatic = aSegment.every((part) => !part.dynamic && !part.spread);
|
||||
const bIsStatic = bSegment.every((part) => !part.dynamic && !part.spread);
|
||||
if (aIsStatic && bIsStatic) {
|
||||
const aContent = aSegment.map((part) => part.content).join("");
|
||||
const bContent = bSegment.map((part) => part.content).join("");
|
||||
if (aContent !== bContent) {
|
||||
return aContent.localeCompare(bContent);
|
||||
}
|
||||
}
|
||||
if (aIsStatic !== bIsStatic) {
|
||||
return aIsStatic ? -1 : 1;
|
||||
}
|
||||
const aAllDynamic = aSegment.every((part) => part.dynamic);
|
||||
const bAllDynamic = bSegment.every((part) => part.dynamic);
|
||||
if (aAllDynamic !== bAllDynamic) {
|
||||
return aAllDynamic ? 1 : -1;
|
||||
}
|
||||
const aHasSpread = aSegment.some((part) => part.spread);
|
||||
const bHasSpread = bSegment.some((part) => part.spread);
|
||||
if (aHasSpread !== bHasSpread) {
|
||||
return aHasSpread ? 1 : -1;
|
||||
}
|
||||
}
|
||||
const aLength = a.segments.length;
|
||||
const bLength = b.segments.length;
|
||||
if (aLength !== bLength) {
|
||||
const aEndsInRest = a.segments.at(-1)?.some((part) => part.spread);
|
||||
const bEndsInRest = b.segments.at(-1)?.some((part) => part.spread);
|
||||
if (aEndsInRest !== bEndsInRest && Math.abs(aLength - bLength) === 1) {
|
||||
if (aLength > bLength && aEndsInRest) {
|
||||
return 1;
|
||||
}
|
||||
if (bLength > aLength && bEndsInRest) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return aLength > bLength ? -1 : 1;
|
||||
}
|
||||
if (a.type === "endpoint" !== (b.type === "endpoint")) {
|
||||
return a.type === "endpoint" ? -1 : 1;
|
||||
}
|
||||
return a.route.localeCompare(b.route);
|
||||
}
|
||||
export {
|
||||
routeComparator
|
||||
};
|
||||
46
node_modules/astro/dist/core/routing/rewrite.d.ts
generated
vendored
Normal file
46
node_modules/astro/dist/core/routing/rewrite.d.ts
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { RewritePayload } from '../../types/public/common.js';
|
||||
import type { AstroConfig } from '../../types/public/config.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
import type { AstroLogger } from '../logger/core.js';
|
||||
type FindRouteToRewrite = {
|
||||
payload: RewritePayload;
|
||||
routes: RouteData[];
|
||||
request: Request;
|
||||
trailingSlash: AstroConfig['trailingSlash'];
|
||||
buildFormat: AstroConfig['build']['format'];
|
||||
base: AstroConfig['base'];
|
||||
outDir: URL | string;
|
||||
};
|
||||
interface FindRouteToRewriteResult {
|
||||
routeData: RouteData;
|
||||
newUrl: URL;
|
||||
pathname: string;
|
||||
}
|
||||
/**
|
||||
* Shared logic to retrieve the rewritten route. It returns a tuple that represents:
|
||||
* 1. The new `Request` object. It contains `base`
|
||||
* 2.
|
||||
*/
|
||||
export declare function findRouteToRewrite({ payload, routes, request, trailingSlash, buildFormat, base, outDir, }: FindRouteToRewrite): FindRouteToRewriteResult;
|
||||
/**
|
||||
* Utility function that creates a new `Request` with a new URL from an old `Request`.
|
||||
*
|
||||
* @param newUrl The new `URL`
|
||||
* @param oldRequest The old `Request`
|
||||
* @param isPrerendered It needs to be the flag of the previous routeData, before the rewrite
|
||||
* @param logger
|
||||
* @param routePattern
|
||||
*/
|
||||
export declare function copyRequest(newUrl: URL, oldRequest: Request, isPrerendered: boolean, logger: AstroLogger, routePattern: string): Request;
|
||||
export declare function setOriginPathname(request: Request, pathname: string, trailingSlash: AstroConfig['trailingSlash'], buildFormat: AstroConfig['build']['format']): void;
|
||||
export declare function getOriginPathname(request: Request): string;
|
||||
/**
|
||||
* Pure function that normalizes a rewrite target pathname by stripping the base,
|
||||
* handling trailing slashes, removing `.html` suffixes, and computing the final
|
||||
* full URL pathname (with base re-prepended).
|
||||
*/
|
||||
export declare function normalizeRewritePathname(urlPathname: string, base: AstroConfig['base'], trailingSlash: AstroConfig['trailingSlash'], buildFormat: AstroConfig['build']['format']): {
|
||||
pathname: string;
|
||||
resolvedUrlPathname: string;
|
||||
};
|
||||
export {};
|
||||
163
node_modules/astro/dist/core/routing/rewrite.js
generated
vendored
Normal file
163
node_modules/astro/dist/core/routing/rewrite.js
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
import { collapseDuplicateSlashes } from "@astrojs/internal-helpers/path";
|
||||
import { shouldAppendForwardSlash } from "../build/util.js";
|
||||
import { originPathnameSymbol } from "../constants.js";
|
||||
import { AstroError, AstroErrorData } from "../errors/index.js";
|
||||
import {
|
||||
appendForwardSlash,
|
||||
joinPaths,
|
||||
prependForwardSlash,
|
||||
removeTrailingForwardSlash,
|
||||
trimSlashes
|
||||
} from "../path.js";
|
||||
import { createRequest } from "../request.js";
|
||||
import { DEFAULT_404_ROUTE } from "./internal/astro-designed-error-pages.js";
|
||||
import { isRoute404, isRoute500 } from "./internal/route-errors.js";
|
||||
function findRouteToRewrite({
|
||||
payload,
|
||||
routes,
|
||||
request,
|
||||
trailingSlash,
|
||||
buildFormat,
|
||||
base,
|
||||
outDir
|
||||
}) {
|
||||
let newUrl = void 0;
|
||||
if (payload instanceof URL) {
|
||||
newUrl = payload;
|
||||
} else if (payload instanceof Request) {
|
||||
newUrl = new URL(payload.url);
|
||||
} else {
|
||||
newUrl = new URL(collapseDuplicateSlashes(payload), new URL(request.url).origin);
|
||||
}
|
||||
const { pathname, resolvedUrlPathname } = normalizeRewritePathname(
|
||||
newUrl.pathname,
|
||||
base,
|
||||
trailingSlash,
|
||||
buildFormat
|
||||
);
|
||||
newUrl.pathname = resolvedUrlPathname;
|
||||
const decodedPathname = decodeURI(pathname);
|
||||
if (isRoute404(decodedPathname)) {
|
||||
const errorRoute = routes.find((route) => route.route === "/404");
|
||||
if (errorRoute) {
|
||||
return { routeData: errorRoute, newUrl, pathname: decodedPathname };
|
||||
}
|
||||
}
|
||||
if (isRoute500(decodedPathname)) {
|
||||
const errorRoute = routes.find((route) => route.route === "/500");
|
||||
if (errorRoute) {
|
||||
return { routeData: errorRoute, newUrl, pathname: decodedPathname };
|
||||
}
|
||||
}
|
||||
let foundRoute;
|
||||
for (const route of routes) {
|
||||
if (route.pattern.test(decodedPathname)) {
|
||||
if (route.params && route.params.length !== 0 && route.distURL && route.distURL.length !== 0) {
|
||||
if (!route.distURL.find(
|
||||
(url) => url.href.replace(outDir.toString(), "").replace(/(?:\/index\.html|\.html)$/, "") === trimSlashes(pathname)
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foundRoute = route;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundRoute) {
|
||||
return {
|
||||
routeData: foundRoute,
|
||||
newUrl,
|
||||
pathname: decodedPathname
|
||||
};
|
||||
} else {
|
||||
const custom404 = routes.find((route) => route.route === "/404");
|
||||
if (custom404) {
|
||||
return { routeData: custom404, newUrl, pathname };
|
||||
} else {
|
||||
return { routeData: DEFAULT_404_ROUTE, newUrl, pathname };
|
||||
}
|
||||
}
|
||||
}
|
||||
function copyRequest(newUrl, oldRequest, isPrerendered, logger, routePattern) {
|
||||
if (oldRequest.bodyUsed) {
|
||||
throw new AstroError(AstroErrorData.RewriteWithBodyUsed);
|
||||
}
|
||||
return createRequest({
|
||||
url: newUrl,
|
||||
method: oldRequest.method,
|
||||
body: oldRequest.body,
|
||||
isPrerendered,
|
||||
logger,
|
||||
headers: isPrerendered ? {} : oldRequest.headers,
|
||||
routePattern,
|
||||
init: {
|
||||
referrer: oldRequest.referrer,
|
||||
referrerPolicy: oldRequest.referrerPolicy,
|
||||
mode: oldRequest.mode,
|
||||
credentials: oldRequest.credentials,
|
||||
cache: oldRequest.cache,
|
||||
redirect: oldRequest.redirect,
|
||||
integrity: oldRequest.integrity,
|
||||
signal: oldRequest.signal,
|
||||
keepalive: oldRequest.keepalive,
|
||||
// https://fetch.spec.whatwg.org/#dom-request-duplex
|
||||
// @ts-expect-error It isn't part of the types, but undici accepts it and it allows to carry over the body to a new request
|
||||
duplex: "half"
|
||||
}
|
||||
});
|
||||
}
|
||||
function setOriginPathname(request, pathname, trailingSlash, buildFormat) {
|
||||
if (!pathname) {
|
||||
pathname = "/";
|
||||
}
|
||||
const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);
|
||||
let finalPathname;
|
||||
if (pathname === "/") {
|
||||
finalPathname = "/";
|
||||
} else if (shouldAppendSlash) {
|
||||
finalPathname = appendForwardSlash(pathname);
|
||||
} else {
|
||||
finalPathname = removeTrailingForwardSlash(pathname);
|
||||
}
|
||||
Reflect.set(request, originPathnameSymbol, encodeURIComponent(finalPathname));
|
||||
}
|
||||
function getOriginPathname(request) {
|
||||
const origin = Reflect.get(request, originPathnameSymbol);
|
||||
if (origin) {
|
||||
return decodeURIComponent(origin);
|
||||
}
|
||||
return new URL(request.url).pathname;
|
||||
}
|
||||
function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat) {
|
||||
let pathname = collapseDuplicateSlashes(urlPathname);
|
||||
const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);
|
||||
if (base !== "/") {
|
||||
const isBasePathRequest = urlPathname === base || urlPathname === removeTrailingForwardSlash(base);
|
||||
if (isBasePathRequest) {
|
||||
pathname = "/";
|
||||
} else if (urlPathname.startsWith(base)) {
|
||||
pathname = shouldAppendSlash ? appendForwardSlash(urlPathname) : removeTrailingForwardSlash(urlPathname);
|
||||
pathname = pathname.slice(base.length);
|
||||
}
|
||||
}
|
||||
if (!pathname.startsWith("/") && shouldAppendSlash && urlPathname.endsWith("/")) {
|
||||
pathname = prependForwardSlash(pathname);
|
||||
}
|
||||
if (buildFormat === "file") {
|
||||
pathname = pathname.replace(/\.html$/, "");
|
||||
}
|
||||
let resolvedUrlPathname;
|
||||
if (base !== "/" && (pathname === "" || pathname === "/") && !shouldAppendSlash) {
|
||||
resolvedUrlPathname = removeTrailingForwardSlash(base);
|
||||
} else {
|
||||
resolvedUrlPathname = joinPaths(...[base, pathname].filter(Boolean));
|
||||
}
|
||||
return { pathname, resolvedUrlPathname };
|
||||
}
|
||||
export {
|
||||
copyRequest,
|
||||
findRouteToRewrite,
|
||||
getOriginPathname,
|
||||
normalizeRewritePathname,
|
||||
setOriginPathname
|
||||
};
|
||||
50
node_modules/astro/dist/core/routing/router.d.ts
generated
vendored
Normal file
50
node_modules/astro/dist/core/routing/router.d.ts
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { AstroConfig } from '../../types/public/config.js';
|
||||
import type { Params } from '../../types/public/common.js';
|
||||
import type { ValidRedirectStatus } from '../../types/public/config.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/**
|
||||
* Router options derived from the active Astro config.
|
||||
* Controls base matching, trailing slash handling, and build output format.
|
||||
*/
|
||||
export interface RouterOptions {
|
||||
base: AstroConfig['base'];
|
||||
trailingSlash: AstroConfig['trailingSlash'];
|
||||
buildFormat: NonNullable<AstroConfig['build']>['format'];
|
||||
}
|
||||
interface RouterMatchRoute {
|
||||
type: 'match';
|
||||
route: RouteData;
|
||||
params: Params;
|
||||
pathname: string;
|
||||
}
|
||||
interface RouterMatchRedirect {
|
||||
type: 'redirect';
|
||||
location: string;
|
||||
status: ValidRedirectStatus;
|
||||
}
|
||||
interface RouterMatchNone {
|
||||
type: 'none';
|
||||
reason: 'no-match' | 'outside-base';
|
||||
}
|
||||
/**
|
||||
* Result of routing a pathname.
|
||||
* - match: route was found, includes route data and params.
|
||||
* - redirect: canonical redirect (trailing slash or leading slash normalization).
|
||||
* - none: no match (either outside base or no route pattern matched).
|
||||
*/
|
||||
export type RouterMatch = RouterMatchRoute | RouterMatchRedirect | RouterMatchNone;
|
||||
/**
|
||||
* Matches request pathnames against a route list with base and trailing slash rules.
|
||||
*/
|
||||
export declare class Router {
|
||||
#private;
|
||||
constructor(routes: RouteData[], options: RouterOptions);
|
||||
/**
|
||||
* Match an input pathname against the route list.
|
||||
* If allowWithoutBase is true, a non-base-prefixed path is still considered.
|
||||
*/
|
||||
match(inputPathname: string, { allowWithoutBase }?: {
|
||||
allowWithoutBase?: boolean;
|
||||
}): RouterMatch;
|
||||
}
|
||||
export {};
|
||||
101
node_modules/astro/dist/core/routing/router.js
generated
vendored
Normal file
101
node_modules/astro/dist/core/routing/router.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
import { prependForwardSlash, removeTrailingForwardSlash } from "../path.js";
|
||||
import { getParams } from "../render/params-and-props.js";
|
||||
import { routeComparator } from "./priority.js";
|
||||
class Router {
|
||||
#routes;
|
||||
#base;
|
||||
#baseWithoutTrailingSlash;
|
||||
#buildFormat;
|
||||
#trailingSlash;
|
||||
constructor(routes, options) {
|
||||
this.#routes = [...routes].sort(routeComparator);
|
||||
this.#base = normalizeBase(options.base);
|
||||
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#base);
|
||||
this.#buildFormat = options.buildFormat;
|
||||
this.#trailingSlash = options.trailingSlash;
|
||||
}
|
||||
/**
|
||||
* Match an input pathname against the route list.
|
||||
* If allowWithoutBase is true, a non-base-prefixed path is still considered.
|
||||
*/
|
||||
match(inputPathname, { allowWithoutBase = false } = {}) {
|
||||
const normalized = getRedirectForPathname(inputPathname);
|
||||
if (normalized.redirect) {
|
||||
return { type: "redirect", location: normalized.redirect, status: 301 };
|
||||
}
|
||||
if (this.#base !== "/") {
|
||||
const baseWithSlash = `${this.#baseWithoutTrailingSlash}/`;
|
||||
if (this.#trailingSlash === "always" && (normalized.pathname === this.#baseWithoutTrailingSlash || normalized.pathname === this.#base)) {
|
||||
return { type: "redirect", location: baseWithSlash, status: 301 };
|
||||
}
|
||||
if (this.#trailingSlash === "never" && normalized.pathname === baseWithSlash) {
|
||||
return { type: "redirect", location: this.#baseWithoutTrailingSlash, status: 301 };
|
||||
}
|
||||
}
|
||||
const baseResult = stripBase(
|
||||
normalized.pathname,
|
||||
this.#base,
|
||||
this.#baseWithoutTrailingSlash,
|
||||
this.#trailingSlash
|
||||
);
|
||||
if (!baseResult) {
|
||||
if (!allowWithoutBase) {
|
||||
return { type: "none", reason: "outside-base" };
|
||||
}
|
||||
}
|
||||
let pathname = baseResult ?? normalized.pathname;
|
||||
if (this.#buildFormat === "file") {
|
||||
pathname = normalizeFileFormatPathname(pathname);
|
||||
}
|
||||
const route = this.#routes.find((candidate) => {
|
||||
if (candidate.pattern.test(pathname)) return true;
|
||||
return candidate.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
|
||||
});
|
||||
if (!route) {
|
||||
return { type: "none", reason: "no-match" };
|
||||
}
|
||||
const params = getParams(route, pathname);
|
||||
return { type: "match", route, params, pathname };
|
||||
}
|
||||
}
|
||||
function normalizeBase(base) {
|
||||
if (!base) return "/";
|
||||
if (base === "/") return base;
|
||||
return prependForwardSlash(base);
|
||||
}
|
||||
function getRedirectForPathname(pathname) {
|
||||
let value = prependForwardSlash(pathname);
|
||||
if (value.startsWith("//")) {
|
||||
const collapsed = `/${value.replace(/^\/+/, "")}`;
|
||||
return { pathname: value, redirect: collapsed };
|
||||
}
|
||||
return { pathname: value };
|
||||
}
|
||||
function stripBase(pathname, base, baseWithoutTrailingSlash, trailingSlash) {
|
||||
if (base === "/") return pathname;
|
||||
const baseWithSlash = `${baseWithoutTrailingSlash}/`;
|
||||
if (pathname === baseWithoutTrailingSlash || pathname === base) {
|
||||
return trailingSlash === "always" ? null : "/";
|
||||
}
|
||||
if (pathname === baseWithSlash) {
|
||||
return trailingSlash === "never" ? null : "/";
|
||||
}
|
||||
if (pathname.startsWith(baseWithSlash)) {
|
||||
return pathname.slice(baseWithoutTrailingSlash.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function normalizeFileFormatPathname(pathname) {
|
||||
if (pathname.endsWith("/index.html")) {
|
||||
const trimmed = pathname.slice(0, -"/index.html".length);
|
||||
return trimmed === "" ? "/" : trimmed;
|
||||
}
|
||||
if (pathname.endsWith(".html")) {
|
||||
const trimmed = pathname.slice(0, -".html".length);
|
||||
return trimmed === "" ? "/" : trimmed;
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
export {
|
||||
Router
|
||||
};
|
||||
1
node_modules/astro/dist/core/routing/segment.d.ts
generated
vendored
Normal file
1
node_modules/astro/dist/core/routing/segment.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function validateSegment(segment: string, file?: string): void;
|
||||
22
node_modules/astro/dist/core/routing/segment.js
generated
vendored
Normal file
22
node_modules/astro/dist/core/routing/segment.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
function validateSegment(segment, file = "") {
|
||||
if (!file) file = segment;
|
||||
if (segment.includes("][")) {
|
||||
throw new Error(`Invalid route ${file} \u2014 parameters must be separated`);
|
||||
}
|
||||
if (countOccurrences("[", segment) !== countOccurrences("]", segment)) {
|
||||
throw new Error(`Invalid route ${file} \u2014 brackets are unbalanced`);
|
||||
}
|
||||
if ((/.+\[\.\.\.[^\]]+\]/.test(segment) || /\[\.\.\.[^\]]+\].+/.test(segment)) && file.endsWith(".astro")) {
|
||||
throw new Error(`Invalid route ${file} \u2014 rest parameter must be a standalone segment`);
|
||||
}
|
||||
}
|
||||
function countOccurrences(needle, haystack) {
|
||||
let count = 0;
|
||||
for (const hay of haystack) {
|
||||
if (hay === needle) count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
export {
|
||||
validateSegment
|
||||
};
|
||||
18
node_modules/astro/dist/core/routing/trailing-slash-handler.d.ts
generated
vendored
Normal file
18
node_modules/astro/dist/core/routing/trailing-slash-handler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { BaseApp } from '../app/base.js';
|
||||
import type { Pipeline } from '../base-pipeline.js';
|
||||
import type { FetchState } from '../fetch/fetch-state.js';
|
||||
/**
|
||||
* Handles trailing-slash normalization for incoming requests. If the
|
||||
* request's pathname does not match the app's configured `trailingSlash`
|
||||
* policy, a redirect response is returned. Otherwise, returns `undefined`
|
||||
* so the caller can continue processing the request.
|
||||
*/
|
||||
export declare class TrailingSlashHandler {
|
||||
#private;
|
||||
constructor(app: BaseApp<Pipeline>);
|
||||
/**
|
||||
* Returns a redirect `Response` if the request pathname needs
|
||||
* normalization, or `undefined` if no redirect is required.
|
||||
*/
|
||||
handle(state: FetchState): Response | undefined;
|
||||
}
|
||||
67
node_modules/astro/dist/core/routing/trailing-slash-handler.js
generated
vendored
Normal file
67
node_modules/astro/dist/core/routing/trailing-slash-handler.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
appendForwardSlash,
|
||||
collapseDuplicateTrailingSlashes,
|
||||
hasFileExtension,
|
||||
isInternalPath,
|
||||
removeTrailingForwardSlash
|
||||
} from "@astrojs/internal-helpers/path";
|
||||
import { prepareResponse } from "../app/prepare-response.js";
|
||||
import { redirectTemplate } from "./3xx.js";
|
||||
class TrailingSlashHandler {
|
||||
#app;
|
||||
constructor(app) {
|
||||
this.#app = app;
|
||||
}
|
||||
/**
|
||||
* Returns a redirect `Response` if the request pathname needs
|
||||
* normalization, or `undefined` if no redirect is required.
|
||||
*/
|
||||
handle(state) {
|
||||
const url = new URL(state.request.url);
|
||||
const redirect = this.#redirectTrailingSlash(url.pathname);
|
||||
if (redirect === url.pathname) {
|
||||
return void 0;
|
||||
}
|
||||
const addCookieHeader = state.renderOptions.addCookieHeader;
|
||||
const status = state.request.method === "GET" ? 301 : 308;
|
||||
const response = new Response(
|
||||
redirectTemplate({
|
||||
status,
|
||||
relativeLocation: url.pathname,
|
||||
absoluteLocation: redirect,
|
||||
from: state.request.url
|
||||
}),
|
||||
{
|
||||
status,
|
||||
headers: {
|
||||
location: redirect + url.search
|
||||
}
|
||||
}
|
||||
);
|
||||
prepareResponse(response, { addCookieHeader });
|
||||
return response;
|
||||
}
|
||||
#redirectTrailingSlash(pathname) {
|
||||
const { trailingSlash } = this.#app.manifest;
|
||||
if (pathname === "/" || isInternalPath(pathname)) {
|
||||
return pathname;
|
||||
}
|
||||
const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
|
||||
if (path !== pathname) {
|
||||
return path;
|
||||
}
|
||||
if (trailingSlash === "ignore") {
|
||||
return pathname;
|
||||
}
|
||||
if (trailingSlash === "always" && !hasFileExtension(pathname)) {
|
||||
return appendForwardSlash(pathname);
|
||||
}
|
||||
if (trailingSlash === "never") {
|
||||
return removeTrailingForwardSlash(pathname);
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
}
|
||||
export {
|
||||
TrailingSlashHandler
|
||||
};
|
||||
10
node_modules/astro/dist/core/routing/validation.d.ts
generated
vendored
Normal file
10
node_modules/astro/dist/core/routing/validation.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ComponentInstance } from '../../types/astro.js';
|
||||
import type { GetStaticPathsResult } from '../../types/public/common.js';
|
||||
import type { RouteData } from '../../types/public/internal.js';
|
||||
/** Error for deprecated or malformed route components */
|
||||
export declare function validateDynamicRouteModule(mod: ComponentInstance, { ssr, route, }: {
|
||||
ssr: boolean;
|
||||
route: RouteData;
|
||||
}): void;
|
||||
/** Throw error and log warnings for malformed getStaticPaths() response */
|
||||
export declare function validateGetStaticPathsResult(result: GetStaticPathsResult, route: RouteData): void;
|
||||
45
node_modules/astro/dist/core/routing/validation.js
generated
vendored
Normal file
45
node_modules/astro/dist/core/routing/validation.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AstroError, AstroErrorData } from "../errors/index.js";
|
||||
function validateDynamicRouteModule(mod, {
|
||||
ssr,
|
||||
route
|
||||
}) {
|
||||
if ((!ssr || route.prerender) && !mod.getStaticPaths) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.GetStaticPathsRequired,
|
||||
location: { file: route.component }
|
||||
});
|
||||
}
|
||||
}
|
||||
function validateGetStaticPathsResult(result, route) {
|
||||
if (!Array.isArray(result)) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.InvalidGetStaticPathsReturn,
|
||||
message: AstroErrorData.InvalidGetStaticPathsReturn.message(typeof result),
|
||||
location: {
|
||||
file: route.component
|
||||
}
|
||||
});
|
||||
}
|
||||
result.forEach((pathObject) => {
|
||||
if (typeof pathObject === "object" && Array.isArray(pathObject) || pathObject === null) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.InvalidGetStaticPathsEntry,
|
||||
message: AstroErrorData.InvalidGetStaticPathsEntry.message(
|
||||
Array.isArray(pathObject) ? "array" : typeof pathObject
|
||||
)
|
||||
});
|
||||
}
|
||||
if (pathObject.params === void 0 || pathObject.params === null || pathObject.params && Object.keys(pathObject.params).length === 0) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.GetStaticPathsExpectedParams,
|
||||
location: {
|
||||
file: route.component
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
export {
|
||||
validateDynamicRouteModule,
|
||||
validateGetStaticPathsResult
|
||||
};
|
||||
Reference in New Issue
Block a user