fix: complete TechLog migration evidence

This commit is contained in:
DongHyeonka
2026-08-16 04:55:52 +09:00
parent 6c2780b7a7
commit 3a7c5deca0
81 changed files with 1492 additions and 345 deletions
+133
View File
@@ -0,0 +1,133 @@
// @ts-nocheck -- copied byte-for-byte into the self-contained Node deployment artifact.
import { readFile, stat } from "node:fs/promises";
import { createServer } from "node:http";
import path from "node:path";
import process from "node:process";
import { fileURLToPath, pathToFileURL } from "node:url";
const contentTypes = Object.freeze({
".css": "text/css;charset=UTF-8",
".html": "text/html;charset=UTF-8",
".js": "text/javascript;charset=UTF-8",
".json": "application/json",
".png": "image/png",
".svg": "image/svg+xml",
".woff": "font/woff",
".woff2": "font/woff2",
});
function isStudioPath(pathname, prefix) {
return pathname === prefix || pathname.startsWith(`${prefix}/`);
}
async function sendFile(
file,
method,
response,
status = 200,
) {
const body = await readFile(file);
response.writeHead(status, {
"Cache-Control": "no-cache",
"Content-Length": body.byteLength,
"Content-Type": contentTypes[path.extname(file)] ?? "application/octet-stream",
Vary: "Origin",
});
if (method === "HEAD") {
response.end();
return;
}
response.end(body);
}
export function createTechLogProductionServer({
root,
contract,
}) {
const absoluteRoot = path.resolve(root);
const publicSpaPaths = new Set(contract.publicSpaPaths);
const studioSpaPathPatterns = contract.studioSpaPathPatterns.map(
(pattern) => new RegExp(pattern),
);
const server = createServer(async (request, response) => {
if (request.method !== "GET" && request.method !== "HEAD") {
response.writeHead(405, { Allow: "GET, HEAD" }).end();
return;
}
try {
const url = new URL(request.url ?? "/", "http://127.0.0.1");
const pathname = decodeURIComponent(url.pathname);
const requested = path.resolve(absoluteRoot, `.${pathname}`);
if (
requested !== absoluteRoot &&
requested.startsWith(`${absoluteRoot}${path.sep}`)
) {
const details = await stat(requested).catch(() => null);
if (details?.isFile()) {
await sendFile(requested, request.method, response);
return;
}
}
if (
publicSpaPaths.has(pathname) ||
studioSpaPathPatterns.some((pattern) => pattern.test(pathname))
) {
await sendFile(path.join(absoluteRoot, "index.html"), request.method, response);
return;
}
if (isStudioPath(pathname, contract.studioPathPrefix)) {
await sendFile(
path.join(absoluteRoot, "index.html"),
request.method,
response,
contract.notFound.status,
);
return;
}
response.writeHead(contract.notFound.status, {
"Content-Type": contract.notFound.contentType,
});
response.end(request.method === "HEAD" ? undefined : contract.notFound.body);
} catch {
response.writeHead(contract.notFound.status, {
"Content-Type": contract.notFound.contentType,
});
response.end(request.method === "HEAD" ? undefined : contract.notFound.body);
}
});
return server;
}
function optionValue(name) {
const index = process.argv.indexOf(name);
return index >= 0 ? process.argv[index + 1] : undefined;
}
async function startFromCommandLine() {
const root = path.resolve(
optionValue("--root") ?? path.dirname(fileURLToPath(import.meta.url)),
);
const host = optionValue("--host") ?? "127.0.0.1";
const port = Number(optionValue("--port") ?? 4173);
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
throw new TypeError("--port must be an integer from 1 through 65535");
}
const contract = JSON.parse(
await readFile(path.join(root, "tech-log-serving-contract.json"), "utf8"),
);
const server = createTechLogProductionServer({ root, contract });
server.listen(port, host, () => {
process.stdout.write(`TechLog production server: ${root} on http://${host}:${port}\n`);
});
for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, () => server.close(() => process.exit(0)));
}
}
const commandPath = process.argv[1];
if (
commandPath &&
import.meta.url === pathToFileURL(path.resolve(commandPath)).href
) {
await startFromCommandLine();
}
+27
View File
@@ -0,0 +1,27 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import type { TechLogServingContract } from "./tech-log-serving-contract.ts";
type ServingArtifactOptions = Readonly<{
distRoot: string;
contract: TechLogServingContract;
serverSourcePath?: string;
}>;
export async function writeTechLogServingArtifact({
distRoot,
contract,
serverSourcePath = "scripts/lib/tech-log-production-server.ts",
}: ServingArtifactOptions): Promise<void> {
const absoluteDistRoot = path.resolve(distRoot);
const serverSource = await readFile(path.resolve(serverSourcePath), "utf8");
await mkdir(absoluteDistRoot, { recursive: true });
await Promise.all([
writeFile(
path.join(absoluteDistRoot, "tech-log-serving-contract.json"),
`${JSON.stringify(contract, null, 2)}\n`,
),
writeFile(path.join(absoluteDistRoot, "server.mjs"), serverSource),
]);
}
+77
View File
@@ -0,0 +1,77 @@
export type TechLogServingContract = Readonly<{
schemaVersion: 1;
publicSpaPaths: readonly string[];
studioPathPrefix: "/studio";
studioSpaPathPatterns: readonly string[];
notFound: Readonly<{
status: 404;
contentType: "text/plain;charset=UTF-8";
body: "Not Found";
}>;
}>;
type ServingContractInput = Readonly<{
publicRecords: readonly Readonly<{
path: string;
topicSlug: string;
}>[];
projects: readonly Readonly<{ slug: string }>[];
releases: readonly Readonly<{ path: string }>[];
}>;
const staticPublicPaths = Object.freeze([
"/",
"/explore",
"/explore/cases",
"/explore/questions",
"/explore/references",
"/profile",
"/projects",
"/releases",
"/search",
]);
const studioSpaPathPatterns = Object.freeze([
"^/studio$",
"^/studio/documents$",
"^/studio/documents/new$",
"^/studio/documents/[^/]+/(edit|validation|preview|publish)$",
"^/studio/publications$",
"^/studio/publications/[^/]+/preview$",
]);
function asciiCompare(left: string, right: string): number {
return left < right ? -1 : left > right ? 1 : 0;
}
export function createTechLogServingContract({
publicRecords,
projects,
releases,
}: ServingContractInput): TechLogServingContract {
const publicSpaPaths = new Set(staticPublicPaths);
for (const record of publicRecords) {
publicSpaPaths.add(record.path);
publicSpaPaths.add(`/topics/${record.topicSlug}`);
}
for (const project of projects) {
const projectPath = `/projects/${project.slug}`;
publicSpaPaths.add(projectPath);
publicSpaPaths.add(`${projectPath}/activity`);
publicSpaPaths.add(`${projectPath}/decisions`);
publicSpaPaths.add(`${projectPath}/records`);
}
for (const release of releases) publicSpaPaths.add(release.path);
return Object.freeze({
schemaVersion: 1,
publicSpaPaths: Object.freeze([...publicSpaPaths].sort(asciiCompare)),
studioPathPrefix: "/studio",
studioSpaPathPatterns,
notFound: Object.freeze({
status: 404,
contentType: "text/plain;charset=UTF-8",
body: "Not Found",
}),
});
}