import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { join, relative } from "node:path"; import process from "node:process"; const root = process.cwd(); const VERSION = "0.3.0"; const errors = []; const notes = []; const readJson = (file) => JSON.parse(readFileSync(file, "utf8")); function walk(dir, name = "package.json") { const out = []; if (!existsSync(dir)) return out; for (const entry of readdirSync(dir)) { if (entry === "node_modules" || entry === ".git" || entry === "dist") continue; const path = join(dir, entry); const stat = statSync(path); if (stat.isDirectory()) out.push(...walk(path, name)); else if (entry === name) out.push(path); } return out; } const packageFiles = [join(root, "package.json"), ...walk(join(root, "packages"))]; const packages = new Map(); for (const file of packageFiles) { const pkg = readJson(file); if (pkg.name) packages.set(pkg.name, { file, pkg }); if (file.includes(`${join(root, "packages")}/`) && pkg.version !== VERSION) { errors.push(`${relative(root, file)} has version ${pkg.version ?? ""}`); } } if (readJson(join(root, "package.json")).version !== VERSION) { errors.push(`root package version is not ${VERSION}`); } for (const { file, pkg } of packages.values()) { for (const field of ["dependencies", "devDependencies", "peerDependencies"]) { for (const [name, range] of Object.entries(pkg[field] ?? {})) { if (String(range).startsWith("workspace:") && !packages.has(name)) { errors.push(`${relative(root, file)} references missing workspace ${name}`); } } } } for (const required of [ "packages/syntax/src/index.ts", "packages/plugin/src/index.ts", "scripts/build-editor-compiler.mjs", "docs/WRN-LANGUAGE-SPEC-1.0.md", "docs/ARCHITECTURE-0.3.md", "docs/UPGRADE-0.3.md", "docs/40-POINT-IMPLEMENTATION-0.3.md", "docs/TEST-CHECKLIST-0.3.md", "docs/AUDIT-0.3.md", ]) { if (!existsSync(join(root, required))) errors.push(`missing ${required}`); } const updateSource = readFileSync(join(root, "packages/cli/src/update.ts"), "utf8"); const migrationPairs = [ ...updateSource.matchAll(/version:\s*"([^"]+)"[\s\S]*?id:\s*"([^"]+)"/g), ].map((match) => ({ version: match[1], id: match[2] })); const ids = new Set(); for (const migration of migrationPairs) { if (ids.has(migration.id)) errors.push(`duplicate migration id ${migration.id}`); ids.add(migration.id); } if (!migrationPairs.some((migration) => migration.version === VERSION)) { errors.push(`missing ${VERSION} updater migration`); } for (const jsonFile of [ "editors/vscode/package.json", "editors/vscode/package-lock.json", "editors/vscode/snippets/wrn.json", "editors/vscode/syntaxes/wrn.tmLanguage.json", ]) { try { readJson(join(root, jsonFile)); } catch (error) { errors.push(`${jsonFile} is invalid JSON: ${error instanceof Error ? error.message : error}`); } } try { const lockText = readFileSync(join(root, "bun.lock"), "utf8").replace(/,\s*([}\]])/g, "$1"); const lock = JSON.parse(lockText); for (const workspace of ["packages/syntax", "packages/plugin"]) { if (!lock.workspaces?.[workspace]) errors.push(`bun.lock missing ${workspace}`); } for (const name of ["@wrnexus/syntax", "@wrnexus/plugin"]) { if (!lock.packages?.[name]) errors.push(`bun.lock missing workspace link ${name}`); } } catch (error) { errors.push(`bun.lock structure is invalid: ${error instanceof Error ? error.message : error}`); } const compilerParser = readFileSync(join(root, "packages/compiler/src/parser.ts"), "utf8"); if (!compilerParser.includes("@wrnexus/syntax")) { errors.push("compiler parser is not a compatibility re-export of @wrnexus/syntax"); } const tsconfig = readJson(join(root, "tsconfig.json")); for (const name of packages.keys()) { if (name.startsWith("@wrnexus/") && !tsconfig.compilerOptions?.paths?.[name]) { errors.push(`tsconfig paths missing ${name}`); } } notes.push(`${packages.size} named packages checked`); notes.push(`${migrationPairs.length} migration entries checked`); notes.push("editor JSON and Bun workspace lock structure checked"); if (errors.length) { process.stderr.write( [ `WRNexusJS ${VERSION} verification failed:`, ...errors.map((error) => ` - ${error}`), "", ].join("\n"), ); process.exitCode = 1; } else { process.stdout.write( [ `WRNexusJS ${VERSION} structural verification passed.`, ...notes.map((note) => ` ✓ ${note}`), "", ].join("\n"), ); }