import { readdir, readFile, writeFile } from "node:fs/promises"; import { createHash } from "node:crypto"; import { basename, join, relative } from "node:path"; const rawRoot = "research/jpw7/demo"; const decodedRoot = "research/jpw7/decoded"; const manifestPath = "fixtures/manifest.json"; const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex"); const sorted = (values) => [...values].sort((left, right) => left.localeCompare(right, "zh-Hans-CN")); const walk = async (root, prefix = "") => { const entries = await readdir(join(root, prefix), { withFileTypes: true }); const paths = []; for (const entry of entries) { const child = join(prefix, entry.name); if (entry.isDirectory()) paths.push(...await walk(root, child)); else paths.push(child); } return paths; }; const rawPaths = sorted((await walk(rawRoot)).filter((path) => path.toLowerCase().endsWith(".jpwabc"))); const samples = []; for (const rawRelativePath of rawPaths) { const rawName = basename(rawRelativePath); const candidates = [ `${rawName}.txt`, `${rawName.replace(/\.jpwabc$/i, "")}.txt`, `${rawRelativePath}.txt`, rawName ]; let decodedName; for (const candidate of candidates) { try { await readFile(join(decodedRoot, candidate)); decodedName = candidate; break; } catch { // Try the next conventional decoded filename. } } if (!decodedName) { throw new Error(`No decoded pair found for ${rawName}`); } const rawPath = join(rawRoot, rawRelativePath); const decodedPath = join(decodedRoot, decodedName); const rawBytes = await readFile(rawPath); const decodedBytes = await readFile(decodedPath); samples.push({ id: rawRelativePath.replace(/\.jpwabc$/i, "").replaceAll("/", "::"), sourceReadOnly: true, rawPath: relative(".", rawPath), decodedPath: relative(".", decodedPath), rawBytes: rawBytes.byteLength, decodedBytes: decodedBytes.byteLength, rawSha256: sha256(rawBytes), decodedSha256: sha256(decodedBytes), classification: { status: "pending", firstResponsibleMilestone: "M1", expected: [ "encoding-and-bom", "section-inventory", "core-semantics", "unknown-content-retained", "diagnostics-emitted" ] } }); } const manifest = { manifestVersion: 1, purpose: "M0 read-only fixture baseline", sampleCount: samples.length, generatedBy: "scripts/generate-fixture-manifest.mjs", sourceRoots: { raw: rawRoot, decoded: decodedRoot }, samples }; await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); console.log(`Wrote ${manifestPath} with ${samples.length} sample pairs`);