219 lines
7.7 KiB
JavaScript
219 lines
7.7 KiB
JavaScript
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.4.0";
|
|
const errors = [];
|
|
const notes = [];
|
|
const read = (file) => readFileSync(join(root, file), "utf8");
|
|
const readJson = (file) => JSON.parse(read(file));
|
|
|
|
function files(dir, filename = "package.json") {
|
|
const output = [];
|
|
if (!existsSync(dir)) return output;
|
|
for (const entry of readdirSync(dir)) {
|
|
if (["node_modules", ".git", ".publish", "dist", ".wrnexus"].includes(entry)) {
|
|
continue;
|
|
}
|
|
const path = join(dir, entry);
|
|
const stat = statSync(path);
|
|
if (stat.isDirectory()) output.push(...files(path, filename));
|
|
else if (entry === filename) output.push(path);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
const rootPackage = readJson("package.json");
|
|
if (rootPackage.version !== VERSION) {
|
|
errors.push(`root package version is ${rootPackage.version ?? "<missing>"}, expected ${VERSION}`);
|
|
}
|
|
|
|
const packageFiles = files(join(root, "packages"));
|
|
const packages = new Map();
|
|
for (const file of packageFiles) {
|
|
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
if (!manifest.name?.startsWith("@wrnexus/")) continue;
|
|
packages.set(manifest.name, { file, manifest });
|
|
if (manifest.version !== VERSION) {
|
|
errors.push(`${relative(root, file)} has version ${manifest.version ?? "<missing>"}`);
|
|
}
|
|
}
|
|
if (packages.size !== 30) errors.push(`expected 30 framework packages, found ${packages.size}`);
|
|
|
|
const serviceManifest = readJson("services/managed-captcha/package.json");
|
|
if (serviceManifest.version !== VERSION) {
|
|
errors.push(`managed CAPTCHA service has version ${serviceManifest.version ?? "<missing>"}`);
|
|
}
|
|
|
|
for (const { file, manifest } of packages.values()) {
|
|
for (const field of [
|
|
"dependencies",
|
|
"devDependencies",
|
|
"peerDependencies",
|
|
"optionalDependencies",
|
|
]) {
|
|
for (const [name, range] of Object.entries(manifest[field] ?? {})) {
|
|
if (String(range).startsWith("workspace:") && !packages.has(name)) {
|
|
errors.push(`${relative(root, file)} references missing workspace ${name}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const required of [
|
|
"packages/plugin/src/types.ts",
|
|
"packages/plugin/src/manifest.ts",
|
|
"packages/plugin/src/discovery.ts",
|
|
"packages/dev-server/src/plugin-assets.ts",
|
|
"packages/dev-server/src/plugin-migrations.ts",
|
|
"packages/cli/src/inspect.ts",
|
|
"packages/cli/src/system.ts",
|
|
"packages/captcha/src/plugin.ts",
|
|
"packages/captcha/assets/client/captcha.js",
|
|
"packages/captcha/components/Captcha.wrn",
|
|
"integration/platform-upgrade.test.ts",
|
|
"docs/ARCHITECTURE-0.4.md",
|
|
"docs/UPGRADE-0.4.md",
|
|
"docs/PACKAGE-RUNTIMES-0.4.md",
|
|
"docs/TEST-CHECKLIST-0.4.md",
|
|
"CHANGELOG-0.4.0.md",
|
|
]) {
|
|
if (!existsSync(join(root, required))) errors.push(`missing ${required}`);
|
|
}
|
|
|
|
const updateSource = read("packages/cli/src/update.ts");
|
|
const migrations = [...updateSource.matchAll(/version:\s*"([^"]+)"[\s\S]*?id:\s*"([^"]+)"/g)].map(
|
|
(match) => ({ version: match[1], id: match[2] }),
|
|
);
|
|
const migrationIds = new Set();
|
|
for (const migration of migrations) {
|
|
if (migrationIds.has(migration.id)) errors.push(`duplicate migration id ${migration.id}`);
|
|
migrationIds.add(migration.id);
|
|
}
|
|
if (!migrations.some((migration) => migration.version === VERSION)) {
|
|
errors.push(`missing ${VERSION} update migration`);
|
|
}
|
|
|
|
const captchaComponent = read("packages/captcha/components/Captcha.wrn");
|
|
if (!captchaComponent.includes('data-wrnexus-runtime="captcha"')) {
|
|
errors.push("Captcha.wrn does not declare the captcha client runtime marker");
|
|
}
|
|
if (/<script\b/i.test(captchaComponent)) {
|
|
errors.push("Captcha.wrn still contains a manual script tag");
|
|
}
|
|
for (const obsolete of [
|
|
"packages/ui/components/Captcha.wrn",
|
|
"examples/captcha-showcase/app/components/Captcha.wrn",
|
|
"examples/captcha-showcase/public/assets/wrnexus/captcha.js",
|
|
]) {
|
|
if (existsSync(join(root, obsolete)))
|
|
errors.push(`obsolete copied CAPTCHA file remains: ${obsolete}`);
|
|
}
|
|
|
|
const captchaManifest = readJson("packages/captcha/package.json");
|
|
if (!captchaManifest.wrnexus?.plugin?.plugin) {
|
|
errors.push("@wrnexus/captcha does not publish a wrnexus.plugin manifest");
|
|
}
|
|
|
|
const pluginSource = read("packages/plugin/src/index.ts");
|
|
for (const marker of ["clientRuntimes", "componentDirs", "migrations", "PUBLIC-PATH"]) {
|
|
if (!pluginSource.includes(marker)) errors.push(`plugin runner is missing ${marker}`);
|
|
}
|
|
|
|
const runtimeSource = read("packages/dev-server/src/runtime.ts");
|
|
if (!runtimeSource.includes("runtimeScriptsForMarkup")) {
|
|
errors.push("server runtime is not injecting package runtimes from rendered markers");
|
|
}
|
|
if (!runtimeSource.includes("JSON.stringify(scripts)")) {
|
|
errors.push("page ETags do not include structured script metadata");
|
|
}
|
|
|
|
const navSource = read("packages/csr/src/nav-runtime.ts");
|
|
for (const marker of ["__wrnexusRuntimes", "mountPackageRuntimes", "unmountPackageRuntimes"]) {
|
|
if (!navSource.includes(marker)) errors.push(`CSR navigation is missing ${marker}`);
|
|
}
|
|
|
|
const uiReference = readJson("packages/ui/component-reference.json");
|
|
if (uiReference.components?.some((component) => component.name === "Captcha")) {
|
|
errors.push("Captcha is still duplicated in the core UI component reference");
|
|
}
|
|
|
|
const tsconfig = readJson("tsconfig.json");
|
|
for (const name of packages.keys()) {
|
|
if (!tsconfig.compilerOptions?.paths?.[name]) errors.push(`tsconfig paths missing ${name}`);
|
|
}
|
|
|
|
try {
|
|
const lock = JSON.parse(read("bun.lock").replace(/,\s*([}\]])/g, "$1"));
|
|
for (const { file, manifest } of packages.values()) {
|
|
const workspace = relative(root, file)
|
|
.replace(/\\/g, "/")
|
|
.replace(/\/package\.json$/, "");
|
|
if (lock.workspaces?.[workspace]?.version !== VERSION) {
|
|
errors.push(`bun.lock has a stale version for ${workspace}`);
|
|
}
|
|
if (!lock.packages?.[manifest.name]) errors.push(`bun.lock missing ${manifest.name}`);
|
|
}
|
|
} catch (error) {
|
|
errors.push(`bun.lock structure is invalid: ${error instanceof Error ? error.message : error}`);
|
|
}
|
|
|
|
const editorManifest = readJson("editors/vscode/package.json");
|
|
if (editorManifest.version !== VERSION) {
|
|
errors.push(`VS Code extension has version ${editorManifest.version ?? "<missing>"}`);
|
|
}
|
|
const editorLock = readJson("editors/vscode/package-lock.json");
|
|
if (editorLock.version !== VERSION || editorLock.packages?.[""]?.version !== VERSION) {
|
|
errors.push("VS Code extension package-lock version is stale");
|
|
}
|
|
|
|
for (const obsolete of [
|
|
".publish",
|
|
"AUDIO-PLAYBACK-FIX.md",
|
|
"CAPTCHA-FIX8-README.md",
|
|
"CAPTCHA-FIXED7-INSTRUCTIONS.md",
|
|
"WRNexusJS-0.3.0-test-fixes.patch",
|
|
]) {
|
|
if (existsSync(join(root, obsolete)))
|
|
errors.push(`obsolete release artifact remains: ${obsolete}`);
|
|
}
|
|
|
|
for (const file of [
|
|
"editors/vscode/package.json",
|
|
"editors/vscode/package-lock.json",
|
|
"editors/vscode/snippets/wrn.json",
|
|
"editors/vscode/syntaxes/wrn.tmLanguage.json",
|
|
]) {
|
|
try {
|
|
readJson(file);
|
|
} catch (error) {
|
|
errors.push(`${file} is invalid JSON: ${error instanceof Error ? error.message : error}`);
|
|
}
|
|
}
|
|
|
|
notes.push(`${packages.size} framework packages aligned to ${VERSION}`);
|
|
notes.push(`${migrations.length} unique updater migrations checked`);
|
|
notes.push("automatic component/runtime/asset/migration discovery checked");
|
|
notes.push("CAPTCHA has no copied component or public JavaScript requirement");
|
|
notes.push("Bun workspace lock and editor JSON 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"),
|
|
);
|
|
}
|