NODEDC_DESIGN_GUIDELINE/server/runtime-seed.mjs

62 lines
3.0 KiB
JavaScript

import { cp, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
import { dirname, join, relative, resolve } from "node:path";
const root = resolve(new URL("..", import.meta.url).pathname);
const runtimeDir = process.env.FOUNDRY_RUNTIME_DIR ? resolve(root, process.env.FOUNDRY_RUNTIME_DIR) : join(root, "runtime-data");
const seedDir = resolve(process.env.FOUNDRY_RUNTIME_SEED_DIR || join(root, "runtime-seed"));
const manifestPath = join(seedDir, "seed-manifest.json");
function isInside(parent, target) {
const path = relative(parent, target);
return path && !path.startsWith("..") && !path.includes("../");
}
async function exists(path) {
try { await stat(path); return true; } catch (error) { if (error?.code === "ENOENT") return false; throw error; }
}
async function copySeedFile(file) {
const source = resolve(seedDir, file);
const target = resolve(runtimeDir, file);
if (!isInside(seedDir, source) || !isInside(runtimeDir, target)) throw new Error("runtime_seed_path_invalid");
if (!await exists(source)) throw new Error(`runtime_seed_file_missing:${file}`);
await mkdir(dirname(target), { recursive: true });
await cp(source, target, { force: true, preserveTimestamps: true });
}
async function isGeneratedFallback() {
const profilePath = join(runtimeDir, "design-profiles", "default.json");
if (!await exists(profilePath)) return true;
try {
const profile = JSON.parse(await readFile(profilePath, "utf8"));
// The server fallback has the same default media paths as the canonical
// profile, but it has never been persisted as a real Design Profile
// layout. `savedAt` is written by the baseline/profile save path. A
// subsequently edited user profile must never be replaced implicitly.
return profile?.id === "default"
&& profile?.name === "NODE.DC Default"
&& profile?.status === "draft"
&& !String(profile?.layout?.savedAt || "").trim()
&& String(profile?.timestamps?.createdAt || "") === String(profile?.timestamps?.updatedAt || "");
} catch {
return false;
}
}
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
if (!/^\d+\.\d+\.\d+$/.test(String(manifest.version || ""))) throw new Error("runtime_seed_version_invalid");
if (!Array.isArray(manifest.files) || !manifest.files.every((file) => typeof file === "string" && file.length > 0)) {
throw new Error("runtime_seed_manifest_invalid");
}
const marker = join(runtimeDir, "runtime-seed", `${manifest.version}.json`);
if (!await exists(marker)) {
const shouldApply = await isGeneratedFallback();
if (shouldApply) for (const file of manifest.files) await copySeedFile(file);
await mkdir(dirname(marker), { recursive: true });
const temporaryMarker = `${marker}.tmp`;
await writeFile(temporaryMarker, `${JSON.stringify({ version: manifest.version, appliedAt: new Date().toISOString(), applied: shouldApply }, null, 2)}\n`, "utf8");
await rename(temporaryMarker, marker);
console.log(`Foundry runtime seed ${manifest.version}: ${shouldApply ? "applied" : "preserved existing runtime"}`);
}