feat: 完善全栈打包质量门禁
在业务开发前补齐 lint、format、verify 与生产运行时契约,确保开发联调和 executable 打包链路可重复验证。
This commit is contained in:
@@ -15,19 +15,31 @@ export function createFetchHandler(options: AppOptions) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === "/health") {
|
||||
return Response.json(createHealthResponse());
|
||||
if (!allowsGetHead(request.method)) {
|
||||
return methodNotAllowedResponse(["GET", "HEAD"], options.mode);
|
||||
}
|
||||
|
||||
return jsonResponse(createHealthResponse(), { method: request.method, mode: options.mode });
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/demo") {
|
||||
return Response.json(createDemoResponse(options.mode));
|
||||
if (!allowsGetHead(request.method)) {
|
||||
return methodNotAllowedResponse(["GET", "HEAD"], options.mode);
|
||||
}
|
||||
|
||||
return jsonResponse(createDemoResponse(options.mode), { method: request.method, mode: options.mode });
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
return Response.json(createApiError("API route not found", 404), { status: 404 });
|
||||
return jsonResponse(createApiError("API route not found", 404), {
|
||||
method: request.method,
|
||||
mode: options.mode,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.staticAssets) {
|
||||
return serveStaticAsset(url.pathname, options.staticAssets);
|
||||
return serveStaticAsset(url.pathname, options.staticAssets, options.mode);
|
||||
}
|
||||
|
||||
return new Response("开发期请通过 Vite 前端地址访问页面。", {
|
||||
@@ -62,38 +74,80 @@ function createApiError(error: string, status: number): ApiErrorResponse {
|
||||
return { error, status };
|
||||
}
|
||||
|
||||
function serveStaticAsset(pathname: string, staticAssets: StaticAssets): Response {
|
||||
function allowsGetHead(method: string): boolean {
|
||||
return method === "GET" || method === "HEAD";
|
||||
}
|
||||
|
||||
function methodNotAllowedResponse(allow: string[], mode: RuntimeMode): Response {
|
||||
return jsonResponse(createApiError("Method not allowed", 405), {
|
||||
mode,
|
||||
status: 405,
|
||||
headers: { Allow: allow.join(", ") },
|
||||
});
|
||||
}
|
||||
|
||||
function jsonResponse(
|
||||
body: ApiErrorResponse | DemoResponse | HealthResponse,
|
||||
options: { method?: string; mode: RuntimeMode; status?: number; headers?: HeadersInit },
|
||||
): Response {
|
||||
const headers = createHeaders(options.mode, {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
...options.headers,
|
||||
});
|
||||
const responseBody = options.method === "HEAD" ? null : JSON.stringify(body);
|
||||
|
||||
return new Response(responseBody, {
|
||||
status: options.status,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
function serveStaticAsset(pathname: string, staticAssets: StaticAssets, mode: RuntimeMode): Response {
|
||||
if (pathname === "/") {
|
||||
return htmlResponse(staticAssets.indexHtml);
|
||||
return htmlResponse(staticAssets.indexHtml, mode);
|
||||
}
|
||||
|
||||
const asset = staticAssets.files[pathname];
|
||||
|
||||
if (asset) {
|
||||
return new Response(asset, {
|
||||
headers: {
|
||||
headers: createHeaders(mode, {
|
||||
"Content-Type": contentTypeFor(pathname),
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/assets/") || hasFileExtension(pathname)) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
return new Response("Not Found", {
|
||||
status: 404,
|
||||
headers: createHeaders(mode, { "Content-Type": "text/plain; charset=utf-8" }),
|
||||
});
|
||||
}
|
||||
|
||||
return htmlResponse(staticAssets.indexHtml);
|
||||
return htmlResponse(staticAssets.indexHtml, mode);
|
||||
}
|
||||
|
||||
function htmlResponse(indexHtml: Blob): Response {
|
||||
function htmlResponse(indexHtml: Blob, mode: RuntimeMode): Response {
|
||||
return new Response(indexHtml, {
|
||||
headers: {
|
||||
headers: createHeaders(mode, {
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function createHeaders(mode: RuntimeMode, init: HeadersInit): Headers {
|
||||
const headers = new Headers(init);
|
||||
|
||||
if (mode === "production") {
|
||||
headers.set("X-Content-Type-Options", "nosniff");
|
||||
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function hasFileExtension(pathname: string): boolean {
|
||||
return /\/[^/]+\.[^/]+$/.test(pathname);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user