- generate-complete-framework-report.mjs no longer claims legacyEmit/ legacyEventProps/legacyComponentDiscovery/stringLayouts/ functions.legacyDefaultRuntime are usable compatibility flags; they were removed before the first public release and a config setting them is now rejected. Regenerated docs/WRNEXUS-COMPLETE-FEATURE-REPORT.md. - Restored the update.test.ts coverage lost in the sub-0.8.0 migration cleanup: a 0.8.x fixture now asserts refreshFrameworkFiles' still-live behaviour (public/llms.txt and CLAUDE.md creation) and that pkg.wrnexus.version stays at its old value after an unverified update. The .gitignore refresh and build/start/production script backfill were themselves removed as part of dropping the sub-0.8.0 migrations that implemented them, so there is nothing left to cover for those two. - updateApp now logs a clear warning when the detected project version is below 0.8.0, naming the version and stating that automated migration from below 0.8.0 is no longer supported, without failing the command (a marker-less app, like examples/basic-app, is benign and must still upgrade cleanly). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
243 lines
8.1 KiB
TypeScript
243 lines
8.1 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import {
|
|
blockingVerificationChecks,
|
|
bumpDeps,
|
|
updateApp,
|
|
verificationCommands,
|
|
} from "../src/update.ts";
|
|
|
|
test("dependency updates use each package's independently published version", () => {
|
|
const pkg = {
|
|
dependencies: {
|
|
"@wrnexus/ui": "^0.8.8",
|
|
"@wrnexus/core": "^0.8.8",
|
|
"@wrnexus/unavailable": "^0.8.7",
|
|
},
|
|
devDependencies: { "@wrnexus/cli": "^0.8.8" },
|
|
};
|
|
const changes = bumpDeps(
|
|
pkg,
|
|
"0.8.9",
|
|
new Map([
|
|
["@wrnexus/ui", "0.8.10"],
|
|
["@wrnexus/core", "0.8.8"],
|
|
["@wrnexus/cli", "0.8.9"],
|
|
]),
|
|
);
|
|
|
|
expect(pkg.dependencies["@wrnexus/ui"]).toBe("^0.8.10");
|
|
expect(pkg.dependencies["@wrnexus/core"]).toBe("^0.8.8");
|
|
expect(pkg.dependencies["@wrnexus/unavailable"]).toBe("^0.8.7");
|
|
expect(pkg.devDependencies["@wrnexus/cli"]).toBe("^0.8.9");
|
|
expect(changes).toHaveLength(2);
|
|
});
|
|
|
|
test("update verification formats before checking and building", () => {
|
|
expect(
|
|
verificationCommands({
|
|
format: "prettier . --write",
|
|
check: "bun run lint && bun run format:check",
|
|
build: "wrnexus build .",
|
|
}),
|
|
).toEqual([
|
|
["run", "format"],
|
|
["run", "check"],
|
|
["run", "build"],
|
|
]);
|
|
});
|
|
|
|
test("update verification skips unavailable scripts", () => {
|
|
expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]);
|
|
});
|
|
|
|
test("0.3 WRN source normalization is conservative and idempotent", async () => {
|
|
const { migrateWrnSource } = await import("../src/update.ts");
|
|
const source = `component Card {
|
|
props { eyebrow = "" title = "" description = "" align = "start" class = "" }
|
|
view {
|
|
<FeatureList items={items} emptyLabel="No items" />
|
|
<button @click={save()} data-config={{ nested: true }}>Save</button>
|
|
}
|
|
}`;
|
|
|
|
const migrated = migrateWrnSource(source);
|
|
expect(migrated).toContain("props {\n");
|
|
expect(migrated).toContain(' eyebrow = ""');
|
|
expect(migrated).toContain("items='{items}'");
|
|
expect(migrated).toContain("@click='{save()}'");
|
|
// Nested brace expressions are deliberately left untouched for manual review.
|
|
expect(migrated).toContain("data-config={{ nested: true }}");
|
|
expect(migrateWrnSource(migrated)).toBe(migrated);
|
|
});
|
|
test("update verification allows pending marker and configuration warnings", () => {
|
|
expect(
|
|
blockingVerificationChecks([
|
|
{
|
|
name: "update marker",
|
|
ok: false,
|
|
detail: "project last migrated to 0.2.79",
|
|
level: "warning",
|
|
},
|
|
{
|
|
name: "configuration",
|
|
ok: false,
|
|
detail: "No wrnexus.config file; framework defaults will be used",
|
|
level: "warning",
|
|
},
|
|
]),
|
|
).toEqual([]);
|
|
});
|
|
|
|
test("update verification still blocks real doctor errors", () => {
|
|
const fatal = {
|
|
name: "WRN language",
|
|
ok: false,
|
|
detail: "10 files, 1 errors, 0 warnings",
|
|
};
|
|
|
|
expect(
|
|
blockingVerificationChecks([
|
|
fatal,
|
|
{
|
|
name: "update marker",
|
|
ok: false,
|
|
detail: "project last migrated to 0.2.79",
|
|
level: "warning",
|
|
},
|
|
]),
|
|
).toEqual([fatal]);
|
|
});
|
|
|
|
test("updateApp refreshes framework-owned files without marking an unverified update complete", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-update-unverified-"));
|
|
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
|
writeFileSync(
|
|
join(root, "package.json"),
|
|
JSON.stringify({
|
|
name: "current-app",
|
|
dependencies: { "@wrnexus/core": "0.8.5" },
|
|
wrnexus: { version: "0.8.5" },
|
|
}),
|
|
);
|
|
|
|
try {
|
|
const result = updateApp(root, "0.8.6", false);
|
|
expect(result).not.toBeNull();
|
|
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
expect(pkg.dependencies["@wrnexus/core"]).toBe("^0.8.6");
|
|
// updateApp only bumps dependency versions; the `wrnexus.version` marker is
|
|
// set later, once install/build/doctor verification actually succeeds.
|
|
expect(pkg.wrnexus.version).toBe("0.8.5");
|
|
expect(readFileSync(join(root, "public", "llms.txt"), "utf8")).toContain("# WrNexus");
|
|
expect(readFileSync(join(root, "CLAUDE.md"), "utf8").length).toBeGreaterThan(0);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("0.8 migration modernizes every WRN source with imports and a review report", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-update-current-source-"));
|
|
mkdirSync(join(root, "app", "components"), { recursive: true });
|
|
mkdirSync(join(root, "app", "layouts"), { recursive: true });
|
|
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
|
writeFileSync(
|
|
join(root, "package.json"),
|
|
JSON.stringify({
|
|
name: "source-app",
|
|
dependencies: { "@wrnexus/core": "^0.7.0" },
|
|
wrnexus: { version: "0.7.0" },
|
|
}),
|
|
);
|
|
writeFileSync(
|
|
join(root, "app", "components", "Notice.wrn"),
|
|
'component Notice {\r\n props { label = "Ready" count = 1 } \r\n view { <p>{label}</p> }\r\n}',
|
|
);
|
|
writeFileSync(
|
|
join(root, "app", "layouts", "shell.wrn"),
|
|
"layout Shell {\n view { <main><slot /></main> }\n}\n",
|
|
);
|
|
writeFileSync(
|
|
join(root, "app", "pages", "index.wrn"),
|
|
'page Home {\n layout = "shell"\n view { <Notice label={"Updated"} /> <Missing /> }\n}\n',
|
|
);
|
|
|
|
try {
|
|
updateApp(root, "0.8.0", false);
|
|
const first = readFileSync(join(root, "app", "pages", "index.wrn"), "utf8");
|
|
expect(first).toContain('import Notice from "@/components/Notice.wrn"');
|
|
expect(first).toContain('import Shell from "@/layouts/shell.wrn"');
|
|
expect(first).toContain("layout = Shell");
|
|
expect(first).toContain("label='{\"Updated\"}'");
|
|
expect(first.endsWith("\n")).toBe(true);
|
|
|
|
const component = readFileSync(join(root, "app", "components", "Notice.wrn"), "utf8");
|
|
expect(component).toContain('props {\n label = "Ready"\n count = 1\n }');
|
|
expect(component).not.toContain("\r");
|
|
|
|
const reportPath = join(root, ".wrnexus", "migrations", "0.8.0-source-modernization.json");
|
|
const report = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
expect(report.changedFiles).toContain("app/pages/index.wrn");
|
|
expect(report.unresolvedImports).toContain(
|
|
"app/pages/index.wrn: component 'Missing' could not be resolved",
|
|
);
|
|
|
|
updateApp(root, "0.8.0", false);
|
|
expect(readFileSync(join(root, "app", "pages", "index.wrn"), "utf8")).toBe(first);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("updateApp warns when a marker-less project is below 0.8.0, and stays quiet at 0.8.x", () => {
|
|
const belowRoot = mkdtempSync(join(tmpdir(), "wrnexus-update-below-"));
|
|
const currentRoot = mkdtempSync(join(tmpdir(), "wrnexus-update-current-"));
|
|
try {
|
|
// No `wrnexus.version` marker and no installed @wrnexus/cli resolves to
|
|
// "0.0.0" — the common, benign case (e.g. examples/basic-app).
|
|
writeFileSync(
|
|
join(belowRoot, "package.json"),
|
|
JSON.stringify({ name: "marker-less-app", dependencies: {} }),
|
|
);
|
|
writeFileSync(
|
|
join(currentRoot, "package.json"),
|
|
JSON.stringify({
|
|
name: "current-app",
|
|
dependencies: { "@wrnexus/core": "^0.8.5" },
|
|
wrnexus: { version: "0.8.5" },
|
|
}),
|
|
);
|
|
|
|
const originalLog = console.log;
|
|
const capture = (): string[] => {
|
|
const logs: string[] = [];
|
|
console.log = (...args: unknown[]) => {
|
|
logs.push(args.join(" "));
|
|
};
|
|
return logs;
|
|
};
|
|
|
|
let belowLogs: string[];
|
|
let currentLogs: string[];
|
|
try {
|
|
belowLogs = capture();
|
|
updateApp(belowRoot, "0.8.9", true);
|
|
|
|
currentLogs = capture();
|
|
updateApp(currentRoot, "0.8.9", true);
|
|
} finally {
|
|
console.log = originalLog;
|
|
}
|
|
|
|
expect(belowLogs.some((l) => l.includes("no longer supported") && l.includes("0.0.0"))).toBe(
|
|
true,
|
|
);
|
|
expect(currentLogs.some((l) => l.includes("no longer supported"))).toBe(false);
|
|
} finally {
|
|
rmSync(belowRoot, { recursive: true, force: true });
|
|
rmSync(currentRoot, { recursive: true, force: true });
|
|
}
|
|
});
|