Initial commit: New MoreminiMore website with fresh design

This commit is contained in:
MoreminiMore
2026-04-22 01:59:05 +07:00
commit 76409638cc
14010 changed files with 2052041 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
import type * as vite from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
/**
* Outcome of the base-URL evaluation for a dev-server request.
*
* - **`rewrite`** — The request URL starts with the configured `base` path.
* Strip the base prefix so downstream handlers see a root-relative URL
* (e.g. `/docs/about` → `/about` when `base: '/docs'`).
* - **`not-found-subpath`** — The user navigated to `/` or `/index.html` but
* the project has a non-root `base`. Respond with a 404 explaining that the
* site lives under the base path, so the developer knows to update the URL.
* - **`not-found`** — The URL doesn't start with the base and the browser
* expects HTML (`Accept: text/html`). Respond with a generic 404 page.
* - **`check-public`** — The URL doesn't match the base and the browser is
* requesting a non-HTML asset (image, script, font, etc.). The middleware
* must do an async `fs.stat` to decide whether the file exists in
* `publicDir` (and show a helpful base-path hint) or just pass through.
* This variant cannot be resolved purely.
*/
export type BaseRewriteDecision = {
action: 'rewrite';
newUrl: string;
} | {
action: 'not-found-subpath';
pathname: string;
devRoot: string;
} | {
action: 'not-found';
pathname: string;
} | {
action: 'check-public';
};
/**
* Computes the `devRoot` path used to match and strip the base prefix.
*
* The `devRoot` is the pathname portion of the base URL (resolved against the
* `site` if present, otherwise against `http://localhost`). For example:
* - `base: '/docs'`, no site → `/docs`
* - `base: '/docs'`, `site: 'https://example.com'` → `/docs`
* - `base: '/'` → `/`
*/
export declare function resolveDevRoot(base: string, site?: string): {
devRoot: string;
devRootReplacement: string;
};
/**
* Pure decision function for base-URL dev-server rewriting.
*
* Evaluates whether the incoming `url` starts with the project's `base` path
* and returns the action the middleware should take. The async `fs.stat` branch
* (checking `publicDir`) is represented as `check-public` and must be handled
* by the caller.
*/
export declare function evaluateBaseRewrite(url: string, pathname: string, acceptHeader: string | undefined, devRoot: string, devRootReplacement: string): BaseRewriteDecision;
export declare function baseMiddleware(settings: AstroSettings, logger: AstroLogger): vite.Connect.NextHandleFunction;

View File

@@ -0,0 +1,88 @@
import * as fs from "node:fs";
import path from "node:path";
import { appendForwardSlash, prependForwardSlash } from "@astrojs/internal-helpers/path";
import colors from "piccolore";
import { notFoundTemplate, subpathNotUsedTemplate } from "../template/4xx.js";
import { writeHtmlResponse } from "./response.js";
function resolveDevRoot(base, site) {
const effectiveBase = base || "/";
const siteUrl = site ? new URL(effectiveBase, site) : void 0;
const devRootURL = new URL(effectiveBase, "http://localhost");
const devRoot = siteUrl ? siteUrl.pathname : devRootURL.pathname;
const devRootReplacement = devRoot.endsWith("/") ? "/" : "";
return { devRoot, devRootReplacement };
}
function evaluateBaseRewrite(url, pathname, acceptHeader, devRoot, devRootReplacement) {
if (pathname.startsWith(devRoot)) {
let newUrl = url.replace(devRoot, devRootReplacement);
if (!newUrl.startsWith("/")) newUrl = prependForwardSlash(newUrl);
return { action: "rewrite", newUrl };
}
if (pathname === "/" || pathname === "/index.html") {
return { action: "not-found-subpath", pathname, devRoot };
}
if (acceptHeader?.includes("text/html")) {
return { action: "not-found", pathname };
}
return { action: "check-public" };
}
function baseMiddleware(settings, logger) {
const { config } = settings;
const { devRoot, devRootReplacement } = resolveDevRoot(config.base, config.site);
return function devBaseMiddleware(req, res, next) {
const url = req.url;
let pathname;
try {
pathname = decodeURI(new URL(url, "http://localhost").pathname);
} catch (e) {
return next(e);
}
const decision = evaluateBaseRewrite(
url,
pathname,
req.headers.accept,
devRoot,
devRootReplacement
);
switch (decision.action) {
case "rewrite":
req.url = decision.newUrl;
return next();
case "not-found-subpath": {
const html = subpathNotUsedTemplate(decision.devRoot, decision.pathname);
return writeHtmlResponse(res, 404, html);
}
case "not-found": {
const html = notFoundTemplate(decision.pathname);
return writeHtmlResponse(res, 404, html);
}
case "check-public": {
const publicPath = new URL("." + req.url, config.publicDir);
fs.stat(publicPath, (_err, stats) => {
if (stats) {
const publicDir = appendForwardSlash(
path.posix.relative(config.root.pathname, config.publicDir.pathname)
);
const devRootURL = new URL(devRoot, "http://localhost");
const expectedLocation = new URL(devRootURL.pathname + url, devRootURL).pathname;
logger.error(
"router",
`Request URLs for ${colors.bold(
publicDir
)} assets must also include your base. "${expectedLocation}" expected, but received "${url}".`
);
const html = subpathNotUsedTemplate(devRoot, pathname);
return writeHtmlResponse(res, 404, html);
} else {
next();
}
});
}
}
};
}
export {
baseMiddleware,
evaluateBaseRewrite,
resolveDevRoot
};

View File

@@ -0,0 +1,22 @@
import type { LoaderEvents, ModuleLoader } from '../core/module-loader/index.js';
import type { ServerState } from './server-state.js';
type ReloadFn = () => void;
export interface DevServerController {
state: ServerState;
onFileChange: LoaderEvents['file-change'];
onHMRError: LoaderEvents['hmr-error'];
}
type CreateControllerParams = {
loader: ModuleLoader;
} | {
reload: ReloadFn;
};
export declare function createController(params: CreateControllerParams): DevServerController;
interface RunWithErrorHandlingParams {
controller: DevServerController;
pathname: string;
run: () => Promise<any>;
onError: (error: unknown) => Error | undefined;
}
export declare function runWithErrorHandling({ controller: { state }, pathname, run, onError, }: RunWithErrorHandlingParams): Promise<void>;
export {};

View File

@@ -0,0 +1,74 @@
import {
clearRouteError,
createServerState,
setRouteError,
setServerError
} from "./server-state.js";
function createController(params) {
if ("loader" in params) {
return createLoaderController(params.loader);
} else {
return createBaseController(params);
}
}
function createBaseController({ reload }) {
const serverState = createServerState();
const onFileChange = () => {
if (serverState.state === "error") {
reload();
}
};
const onHMRError = (payload) => {
let msg = payload?.err?.message ?? "Unknown error";
let stack = payload?.err?.stack ?? "Unknown stack";
let error = new Error(msg);
Object.defineProperty(error, "stack", {
value: stack
});
setServerError(serverState, error);
};
return {
state: serverState,
onFileChange,
onHMRError
};
}
function createLoaderController(loader) {
const controller = createBaseController({
reload() {
loader.clientReload();
}
});
const baseOnFileChange = controller.onFileChange;
controller.onFileChange = (...args) => {
if (controller.state.state === "error") {
loader.eachModule((mod) => {
if (mod.ssrError) {
loader.invalidateModule(mod);
}
});
}
baseOnFileChange(...args);
};
loader.events.on("file-change", controller.onFileChange);
loader.events.on("hmr-error", controller.onHMRError);
return controller;
}
async function runWithErrorHandling({
controller: { state },
pathname,
run,
onError
}) {
try {
await run();
clearRouteError(state, pathname);
} catch (err) {
const error = onError(err);
setRouteError(state, pathname, error);
}
}
export {
createController,
runWithErrorHandling
};

View File

@@ -0,0 +1,6 @@
import type { SSRManifest } from '../core/app/types.js';
import type { AstroLogger } from '../core/logger/core.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
export declare function recordServerError(loader: ModuleLoader, manifest: SSRManifest, logger: AstroLogger, err: Error): {
errorWithMetadata: import("../core/errors/errors.js").ErrorWithMetadata;
};

View File

@@ -0,0 +1,16 @@
import { collectErrorMetadata } from "../core/errors/dev/index.js";
import { formatErrorMessage } from "../core/messages/runtime.js";
function recordServerError(loader, manifest, logger, err) {
try {
loader.fixStacktrace(err);
} catch {
}
const errorWithMetadata = collectErrorMetadata(err, manifest.rootDir);
logger.error(null, formatErrorMessage(errorWithMetadata, logger.level() === "debug"));
return {
errorWithMetadata
};
}
export {
recordServerError
};

View File

@@ -0,0 +1,2 @@
export { createController, runWithErrorHandling } from './controller.js';
export { default as vitePluginAstroServer } from './plugin.js';

View File

@@ -0,0 +1,7 @@
import { createController, runWithErrorHandling } from "./controller.js";
import { default as default2 } from "./plugin.js";
export {
createController,
runWithErrorHandling,
default2 as vitePluginAstroServer
};

View File

@@ -0,0 +1,3 @@
import type { ModuleLoader } from '../core/module-loader/index.js';
import type { SSRResult } from '../types/public/internal.js';
export declare function getComponentMetadata(filePath: URL, loader: ModuleLoader): Promise<SSRResult['componentMetadata']>;

View File

@@ -0,0 +1,36 @@
import { viteID } from "../core/util.js";
import { getAstroMetadata } from "../vite-plugin-astro/index.js";
import { crawlGraph } from "./vite.js";
async function getComponentMetadata(filePath, loader) {
const map = /* @__PURE__ */ new Map();
const rootID = viteID(filePath);
addMetadata(map, loader.getModuleInfo(rootID));
for await (const moduleNode of crawlGraph(loader.getSSREnvironment(), rootID, true)) {
const id = moduleNode.id;
if (id) {
addMetadata(map, loader.getModuleInfo(id));
}
}
return map;
}
function addMetadata(map, modInfo) {
if (modInfo) {
const astro = getAstroMetadata(modInfo);
if (astro) {
let metadata = {
containsHead: false,
propagation: "none"
};
if (astro.propagation) {
metadata.propagation = astro.propagation;
}
if (astro.containsHead) {
metadata.containsHead = astro.containsHead;
}
map.set(modInfo.id, metadata);
}
}
}
export {
getComponentMetadata
};

View File

@@ -0,0 +1,9 @@
import type * as vite from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
interface AstroPluginOptions {
settings: AstroSettings;
logger: AstroLogger;
}
export default function createVitePluginAstroServer({ settings, logger, }: AstroPluginOptions): vite.Plugin;
export {};

View File

@@ -0,0 +1,143 @@
import { AsyncLocalStorage } from "node:async_hooks";
import { IncomingMessage } from "node:http";
import { isRunnableDevEnvironment } from "vite";
import { ASTRO_VITE_ENVIRONMENT_NAMES, devPrerenderMiddlewareSymbol } from "../core/constants.js";
import { getViteErrorPayload } from "../core/errors/dev/index.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { createViteLoader } from "../core/module-loader/index.js";
import { matchAllRoutes } from "../core/routing/match.js";
import { SERIALIZED_MANIFEST_ID } from "../manifest/serialized.js";
import { ASTRO_DEV_SERVER_APP_ID } from "../vite-plugin-app/index.js";
import { baseMiddleware } from "./base.js";
import { createController } from "./controller.js";
import { recordServerError } from "./error.js";
import { setRouteError } from "./server-state.js";
import { routeGuardMiddleware } from "./route-guard.js";
import { secFetchMiddleware } from "./sec-fetch.js";
import { trailingSlashMiddleware } from "./trailing-slash.js";
function createVitePluginAstroServer({
settings,
logger
}) {
return {
name: "astro:server",
applyToEnvironment(environment) {
return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
},
async configureServer(viteServer) {
const ssrEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
const prerenderEnvironment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.prerender];
const runnableSsrEnvironment = isRunnableDevEnvironment(ssrEnvironment) ? ssrEnvironment : void 0;
const runnablePrerenderEnvironment = isRunnableDevEnvironment(prerenderEnvironment) ? prerenderEnvironment : void 0;
async function createHandler(environment) {
const loader = createViteLoader(viteServer, environment);
const { default: createAstroServerApp } = await environment.runner.import(ASTRO_DEV_SERVER_APP_ID);
const controller = createController({ loader });
const { handler } = await createAstroServerApp(controller, settings, loader, logger);
const { manifest } = await environment.runner.import(SERIALIZED_MANIFEST_ID);
return { controller, handler, loader, manifest, environment };
}
const ssrHandler = runnableSsrEnvironment ? await createHandler(runnableSsrEnvironment) : void 0;
const prerenderHandler = runnablePrerenderEnvironment ? await createHandler(runnablePrerenderEnvironment) : void 0;
const localStorage = new AsyncLocalStorage();
function handleUnhandledRejection(rejection) {
const error = AstroError.is(rejection) ? rejection : new AstroError({
...AstroErrorData.UnhandledRejection,
message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection)
});
const store = localStorage.getStore();
const handlers = [];
if (ssrHandler) handlers.push(ssrHandler);
if (prerenderHandler) handlers.push(prerenderHandler);
for (const currentHandler of handlers) {
if (store instanceof IncomingMessage) {
setRouteError(currentHandler.controller.state, store.url, error);
}
const { errorWithMetadata } = recordServerError(
currentHandler.loader,
currentHandler.manifest,
logger,
error
);
setTimeout(
async () => currentHandler.loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)),
200
);
}
}
if (ssrHandler || prerenderHandler) {
process.on("unhandledRejection", handleUnhandledRejection);
viteServer.httpServer?.on("close", () => {
process.off("unhandledRejection", handleUnhandledRejection);
});
}
return () => {
const shouldHandlePrerenderInCore = Boolean(
viteServer[devPrerenderMiddlewareSymbol]
);
viteServer.middlewares.stack.unshift({
route: "",
handle: baseMiddleware(settings, logger)
});
viteServer.middlewares.stack.unshift({
route: "",
handle: trailingSlashMiddleware(settings)
});
viteServer.middlewares.stack.unshift({
route: "",
handle: routeGuardMiddleware(settings)
});
viteServer.middlewares.stack.unshift({
route: "",
handle: secFetchMiddleware(logger, settings.config.security?.allowedDomains)
});
if (prerenderHandler && shouldHandlePrerenderInCore) {
viteServer.middlewares.use(
async function astroDevPrerenderHandler(request, response, next) {
if (request.url === void 0 || !request.method) {
response.writeHead(500, "Incomplete request");
response.end();
return;
}
if (request.url.startsWith("/@") || request.url.startsWith("/__")) {
return next();
}
if (request.url.includes("/node_modules/")) {
return next();
}
try {
const pathname = decodeURI(new URL(request.url, "http://localhost").pathname);
const { routes } = await prerenderHandler.environment.runner.import("virtual:astro:routes");
const routesList = { routes: routes.map((r) => r.routeData) };
const matches = matchAllRoutes(pathname, routesList);
if (!matches.some((route) => route.prerender)) {
return next();
}
localStorage.run(request, () => {
prerenderHandler.handler(request, response);
});
} catch (err) {
next(err);
}
}
);
}
if (ssrHandler) {
viteServer.middlewares.use(async function astroDevHandler(request, response) {
if (request.url === void 0 || !request.method) {
response.writeHead(500, "Incomplete request");
response.end();
return;
}
localStorage.run(request, () => {
ssrHandler.handler(request, response);
});
});
}
};
}
};
}
export {
createVitePluginAstroServer as default
};

View File

@@ -0,0 +1,2 @@
import type { ModuleLoader } from '../core/module-loader/index.js';
export declare function createResolve(loader: ModuleLoader, root: URL): (s: string) => Promise<string>;

View File

@@ -0,0 +1,9 @@
import { resolveIdToUrl } from "../core/viteUtils.js";
function createResolve(loader, root) {
return async function(s) {
return await resolveIdToUrl(loader, s, root);
};
}
export {
createResolve
};

View File

@@ -0,0 +1,7 @@
import type http from 'node:http';
import type { ErrorWithMetadata } from '../core/errors/index.js';
import type { ModuleLoader } from '../core/module-loader/index.js';
export declare function handle500Response(loader: ModuleLoader, res: http.ServerResponse, err: ErrorWithMetadata): Promise<void>;
export declare function writeHtmlResponse(res: http.ServerResponse, statusCode: number, html: string): void;
export declare function writeRedirectResponse(res: http.ServerResponse, statusCode: number, location: string): void;
export declare function writeSSRResult(webRequest: Request, webResponse: Response, res: http.ServerResponse): Promise<void>;

View File

@@ -0,0 +1,93 @@
import { Http2ServerResponse } from "node:http2";
import { Readable } from "node:stream";
import { getSetCookiesFromResponse } from "../core/cookies/index.js";
import { getViteErrorPayload } from "../core/errors/dev/index.js";
import { redirectTemplate } from "../core/routing/3xx.js";
async function handle500Response(loader, res, err) {
res.on(
"close",
async () => setTimeout(async () => loader.webSocketSend(await getViteErrorPayload(err)), 200)
);
if (res.headersSent) {
res.write(`<script type="module" src="/@vite/client"></script>`);
res.end();
} else {
writeHtmlResponse(
res,
500,
`<title>${err.name}</title><script type="module" src="/@vite/client"></script>`
);
}
}
function writeHtmlResponse(res, statusCode, html) {
res.writeHead(statusCode, {
"Content-Type": "text/html",
"Content-Length": Buffer.byteLength(html, "utf-8")
});
res.write(html);
res.end();
}
function writeRedirectResponse(res, statusCode, location) {
const html = redirectTemplate({
status: statusCode,
absoluteLocation: location,
relativeLocation: location
});
res.writeHead(statusCode, {
Location: location,
"Content-Type": "text/html",
"Content-Length": Buffer.byteLength(html, "utf-8")
});
res.write(html);
res.end();
}
async function writeWebResponse(res, webResponse) {
const { status, headers, body, statusText } = webResponse;
const setCookiesFromResponse = Array.from(getSetCookiesFromResponse(webResponse));
const setCookieHeaders = [...setCookiesFromResponse, ...headers.getSetCookie()];
const _headers = Object.fromEntries(headers.entries());
if (setCookieHeaders.length) {
_headers["set-cookie"] = setCookieHeaders;
}
if (!(res instanceof Http2ServerResponse)) {
res.statusMessage = statusText;
}
res.writeHead(status, _headers);
if (body) {
if (/* @__PURE__ */ Symbol.for("astro.responseBody") in webResponse) {
let stream = webResponse[/* @__PURE__ */ Symbol.for("astro.responseBody")];
for await (const chunk of stream) {
res.write(chunk.toString());
}
} else if (body instanceof Readable) {
body.pipe(res);
return;
} else if (typeof body === "string") {
res.write(body);
} else {
const reader = body.getReader();
res.on("close", () => {
reader.cancel().catch(() => {
});
});
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
res.write(value);
}
}
}
}
res.end();
}
async function writeSSRResult(webRequest, webResponse, res) {
Reflect.set(webRequest, /* @__PURE__ */ Symbol.for("astro.responseSent"), true);
return writeWebResponse(res, webResponse);
}
export {
handle500Response,
writeHtmlResponse,
writeRedirectResponse,
writeSSRResult
};

View File

@@ -0,0 +1,10 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../types/astro.js';
/**
* Middleware that prevents Vite from serving files that exist outside
* of srcDir and publicDir when accessed via direct URL navigation.
*
* This fixes the issue where files like /README.md are served
* when they exist at the project root but aren't part of Astro's routing.
*/
export declare function routeGuardMiddleware(settings: AstroSettings): vite.Connect.NextHandleFunction;

View File

@@ -0,0 +1,58 @@
import * as fs from "node:fs";
import { notFoundTemplate } from "../template/4xx.js";
import { writeHtmlResponse } from "./response.js";
const VITE_INTERNAL_PREFIXES = [
"/@vite/",
"/@fs/",
"/@id/",
"/__vite",
"/@react-refresh",
"/node_modules/",
"/.astro/"
];
function routeGuardMiddleware(settings) {
const { config } = settings;
return function devRouteGuard(req, res, next) {
const url = req.url;
if (!url) {
return next();
}
const accept = req.headers.accept || "";
if (!accept.includes("text/html")) {
return next();
}
let pathname;
try {
pathname = decodeURI(new URL(url, "http://localhost").pathname);
} catch {
return next();
}
if (VITE_INTERNAL_PREFIXES.some((prefix) => pathname.startsWith(prefix))) {
return next();
}
if (url.includes("?")) {
return next();
}
const publicFilePath = new URL("." + pathname, config.publicDir);
if (fs.existsSync(publicFilePath)) {
return next();
}
const srcFilePath = new URL("." + pathname, config.srcDir);
if (fs.existsSync(srcFilePath)) {
return next();
}
const rootFilePath = new URL("." + pathname, config.root);
try {
const stat = fs.statSync(rootFilePath);
if (stat.isFile()) {
const html = notFoundTemplate(pathname);
return writeHtmlResponse(res, 404, html);
}
} catch {
}
return next();
};
}
export {
routeGuardMiddleware
};

View File

@@ -0,0 +1,21 @@
import type { RemotePattern } from '@astrojs/internal-helpers/remote';
import type * as vite from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
/**
* Middleware that validates Sec-Fetch metadata headers on incoming requests
* to block cross-origin subresource requests (e.g. `<script>` tags from
* another origin loading dev server modules).
*
* Navigation requests (`Sec-Fetch-Mode: navigate`) are always allowed through
* because browsers enforce their own security model for top-level navigations.
*
* Requests without `Sec-Fetch-Site` (e.g. from non-browser clients like curl,
* or older browsers that don't send Fetch Metadata) are also allowed through
* to avoid breaking legitimate development workflows.
*
* When `security.allowedDomains` is configured, requests whose `Origin` header
* matches one of the allowed patterns are also permitted. This supports proxied
* dev server setups (e.g. ngrok, Cloudflare Tunnel) where the browser sees a
* different origin than the dev server itself.
*/
export declare function secFetchMiddleware(logger: AstroLogger, allowedDomains?: Partial<RemotePattern>[]): vite.Connect.NextHandleFunction;

View File

@@ -0,0 +1,40 @@
import { BaseApp } from "../core/app/base.js";
function secFetchMiddleware(logger, allowedDomains) {
return function devSecFetch(req, res, next) {
const secFetchSite = req.headers["sec-fetch-site"];
const secFetchMode = req.headers["sec-fetch-mode"];
if (!secFetchSite) {
return next();
}
if (secFetchSite === "same-origin" || secFetchSite === "same-site" || secFetchSite === "none") {
return next();
}
if (secFetchMode === "navigate" || secFetchMode === "nested-navigate") {
return next();
}
if (secFetchMode === "websocket") {
return next();
}
const origin = req.headers["origin"];
if (typeof origin === "string") {
try {
const originUrl = new URL(origin);
const protocol = originUrl.protocol.slice(0, -1);
if (BaseApp.validateForwardedHost(originUrl.host, allowedDomains, protocol)) {
return next();
}
} catch {
}
}
logger.warn(
"router",
`Blocked cross-origin request to ${req.url} (Sec-Fetch-Site: ${secFetchSite}, Sec-Fetch-Mode: ${secFetchMode}). Cross-origin subresource requests are not allowed on the dev server for security reasons.`
);
res.statusCode = 403;
res.setHeader("Content-Type", "text/plain");
res.end("Cross-origin request blocked");
};
}
export {
secFetchMiddleware
};

View File

@@ -0,0 +1,15 @@
type ErrorState = 'fresh' | 'error';
interface RouteState {
state: ErrorState;
error?: Error;
}
export interface ServerState {
routes: Map<string, RouteState>;
state: ErrorState;
error?: Error;
}
export declare function createServerState(): ServerState;
export declare function setRouteError(serverState: ServerState, pathname: string, error: Error | undefined): void;
export declare function setServerError(serverState: ServerState, error: Error): void;
export declare function clearRouteError(serverState: ServerState, pathname: string): void;
export {};

View File

@@ -0,0 +1,38 @@
function createServerState() {
return {
routes: /* @__PURE__ */ new Map(),
state: "fresh"
};
}
function setRouteError(serverState, pathname, error) {
if (serverState.routes.has(pathname)) {
const routeState = serverState.routes.get(pathname);
routeState.state = "error";
routeState.error = error;
} else {
const routeState = {
state: "error",
error
};
serverState.routes.set(pathname, routeState);
}
serverState.state = "error";
serverState.error = error;
}
function setServerError(serverState, error) {
serverState.state = "error";
serverState.error = error;
}
function clearRouteError(serverState, pathname) {
if (serverState.routes.has(pathname)) {
serverState.routes.delete(pathname);
}
serverState.state = "fresh";
serverState.error = void 0;
}
export {
clearRouteError,
createServerState,
setRouteError,
setServerError
};

View File

@@ -0,0 +1,37 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../types/astro.js';
/**
* Outcome of the trailing-slash evaluation for a dev-server request.
*
* - **`next`** — The URL is acceptable. Pass the request through to the next
* middleware / route handler unchanged.
* - **`redirect`** — The URL contains duplicate trailing slashes (e.g.
* `/about//`). The client should be permanently redirected (301) to the
* collapsed form (`/about/`) so crawlers and browsers update their links.
* - **`reject`** — The URL's trailing-slash style conflicts with the project's
* `trailingSlash` config (`'always'` or `'never'`). The dev server responds
* with a 404 and a human-readable error page explaining the mismatch, giving
* the developer immediate feedback that their link is wrong before it reaches
* production.
*/
export type TrailingSlashDecision = {
action: 'next';
} | {
action: 'redirect';
status: 301;
location: string;
} | {
action: 'reject';
status: 404;
pathname: string;
};
/**
* Pure decision function for trailing-slash dev-server behavior.
*
* Evaluates a decoded `pathname`, the query-string portion (including leading
* `?`), and the project's `trailingSlash` config and returns the action the
* middleware should take. The middleware is responsible for translating the
* decision into an HTTP response.
*/
export declare function evaluateTrailingSlash(pathname: string, search: string, trailingSlash: 'always' | 'never' | 'ignore'): TrailingSlashDecision;
export declare function trailingSlashMiddleware(settings: AstroSettings): vite.Connect.NextHandleFunction;

View File

@@ -0,0 +1,47 @@
import {
collapseDuplicateTrailingSlashes,
hasFileExtension,
isInternalPath
} from "@astrojs/internal-helpers/path";
import { trailingSlashMismatchTemplate } from "../template/4xx.js";
import { writeHtmlResponse, writeRedirectResponse } from "./response.js";
function evaluateTrailingSlash(pathname, search, trailingSlash) {
if (isInternalPath(pathname)) {
return { action: "next" };
}
const collapsed = collapseDuplicateTrailingSlashes(pathname, true);
if (pathname && collapsed !== pathname) {
return { action: "redirect", status: 301, location: `${collapsed}${search}` };
}
if (trailingSlash === "never" && pathname.endsWith("/") && pathname !== "/" || trailingSlash === "always" && !pathname.endsWith("/") && !hasFileExtension(pathname)) {
return { action: "reject", status: 404, pathname };
}
return { action: "next" };
}
function trailingSlashMiddleware(settings) {
const { trailingSlash } = settings.config;
return function devTrailingSlash(req, res, next) {
const url = new URL(`http://localhost${req.url}`);
let pathname;
try {
pathname = decodeURI(url.pathname);
} catch (e) {
return next(e);
}
const decision = evaluateTrailingSlash(pathname, url.search, trailingSlash);
switch (decision.action) {
case "redirect":
return writeRedirectResponse(res, decision.status, decision.location);
case "reject": {
const html = trailingSlashMismatchTemplate(decision.pathname, trailingSlash);
return writeHtmlResponse(res, decision.status, html);
}
case "next":
return next();
}
};
}
export {
evaluateTrailingSlash,
trailingSlashMiddleware
};

View File

@@ -0,0 +1,3 @@
export declare const rawRE: RegExp;
export declare const inlineRE: RegExp;
export declare const isBuildableCSSRequest: (request: string) => boolean;

View File

@@ -0,0 +1,9 @@
import { isCSSRequest } from "vite";
const rawRE = /(?:\?|&)raw(?:&|$)/;
const inlineRE = /(?:\?|&)inline\b/;
const isBuildableCSSRequest = (request) => isCSSRequest(request) && !rawRE.test(request) && !inlineRE.test(request);
export {
inlineRE,
isBuildableCSSRequest,
rawRE
};

View File

@@ -0,0 +1,3 @@
import { type EnvironmentModuleNode, type RunnableDevEnvironment } from 'vite';
/** recursively crawl the module graph to get all style files imported by parent id */
export declare function crawlGraph(environment: RunnableDevEnvironment, _id: string, isRootFile: boolean, scanned?: Set<string>): AsyncGenerator<EnvironmentModuleNode, void, unknown>;

View File

@@ -0,0 +1,73 @@
import npath from "node:path";
import { isCSSRequest } from "vite";
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "../core/constants.js";
import { isPropagatedAssetBoundary } from "../core/head-propagation/boundary.js";
import { unwrapId } from "../core/util.js";
import { hasSpecialQueries } from "../vite-plugin-utils/index.js";
const fileExtensionsToSSR = /* @__PURE__ */ new Set([".astro", ".mdoc", ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]);
const STRIP_QUERY_PARAMS_REGEX = /\?.*$/;
async function* crawlGraph(environment, _id, isRootFile, scanned = /* @__PURE__ */ new Set()) {
const id = unwrapId(_id);
const importedModules = /* @__PURE__ */ new Set();
const moduleEntriesForId = isRootFile ? (
// "getModulesByFile" pulls from a delayed module cache (fun implementation detail),
// So we can get up-to-date info on initial server load.
// Needed for slower CSS preprocessing like Tailwind
environment.moduleGraph.getModulesByFile(id) ?? /* @__PURE__ */ new Set()
) : (
// For non-root files, we're safe to pull from "getModuleById" based on testing.
// TODO: Find better invalidation strategy to use "getModuleById" in all cases!
/* @__PURE__ */ new Set([environment.moduleGraph.getModuleById(id)])
);
for (const entry of moduleEntriesForId) {
if (!entry) {
continue;
}
if (id === entry.id) {
scanned.add(id);
if (isCSSRequest(id)) {
continue;
}
if (hasSpecialQueries(id)) {
continue;
}
for (const importedModule of entry.importedModules) {
if (!importedModule.id) continue;
const importedModulePathname = importedModule.id.replace(STRIP_QUERY_PARAMS_REGEX, "");
const isFileTypeNeedingSSR = fileExtensionsToSSR.has(npath.extname(importedModulePathname));
const isPropagationStoppingPoint = isPropagatedAssetBoundary(importedModule.id);
if (isFileTypeNeedingSSR && // Should not SSR a module with ?astroPropagatedAssets
!isPropagationStoppingPoint) {
const mod = environment.moduleGraph.getModuleById(importedModule.id);
if (!mod?.ssrModule) {
try {
await environment.runner.import(importedModule.id);
} catch {
}
}
}
if (isImportedBy(id, importedModule) && !isPropagationStoppingPoint) {
importedModules.add(importedModule);
}
}
}
}
for (const importedModule of importedModules) {
if (!importedModule.id || scanned.has(importedModule.id)) {
continue;
}
yield importedModule;
yield* crawlGraph(environment, importedModule.id, false, scanned);
}
}
function isImportedBy(parent, entry) {
for (const importer of entry.importers) {
if (importer.id === parent) {
return true;
}
}
return false;
}
export {
crawlGraph
};