import { test, describe } from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { execSync } from "node:child_process"; import { checkLibraryDecisions } from "./check.mjs"; /** Create a temp git repo with one initial commit so HEAD exists. */ function makeRepo() { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "libcheck-")); const g = (cmd) => execSync(cmd, { cwd: dir, stdio: "pipe" }); g("git init"); g("git config user.email test@test.com"); g("git config user.name Test"); g("git config commit.gpgsign false"); fs.writeFileSync(path.join(dir, ".gitkeep"), ""); g("git add .gitkeep"); g("git commit -m init"); return { dir, g }; } /** Write a package.json under relDir and commit it as the baseline. */ function commitPkg(dir, g, relDir, pkg) { const pkgDir = path.join(dir, relDir); fs.mkdirSync(pkgDir, { recursive: true }); fs.writeFileSync( path.join(pkgDir, "package.json"), JSON.stringify(pkg, null, 2), ); g(`git add ${relDir}/package.json`); g("git commit -m add-pkg"); } /** Overwrite a package.json and stage the result (no new commit). */ function stagePkg(dir, g, relDir, pkg) { fs.writeFileSync( path.join(dir, relDir, "package.json"), JSON.stringify(pkg, null, 2), ); g(`git add ${relDir}/package.json`); } function traceFm(depName, decision) { return `package: ${depName} version: "^1.0.0" tier: feature decision: ${decision} date: 2026-05-14 deciders: [alice] adr: null filter-results: license: MIT types: native maintenance: active boundary-fit: pass shadow-check: pass eu-residency: ok cve-scan: clean named-consumer: pass verification-commands: - pnpm audit --audit-level=moderate`; } /** Write a trace file and stage it. */ function stageTrace(dir, g, depName, decision = "approved") { const traceDir = path.join(dir, "docs", "library-decisions"); fs.mkdirSync(traceDir, { recursive: true }); const file = `2026-05-14-${depName}.md`; fs.writeFileSync( path.join(traceDir, file), `---\n${traceFm(depName, decision)}\n---\n\n`, ); g(`git add docs/library-decisions/${file}`); } describe("checkLibraryDecisions", () => { test("new feature-tier dep without trace → exit 1", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", { dependencies: {} }); stagePkg(dir, g, "packages/feat-a", { dependencies: { "new-lib": "^1.0.0" }, }); const errs = checkLibraryDecisions(dir); assert.equal(errs.length, 1); assert.equal(errs[0].dep, "new-lib"); assert.equal(errs[0].reason, "no-trace"); }); test("new feature-tier dep with approved trace staged → exit 0", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", { dependencies: {} }); stagePkg(dir, g, "packages/feat-a", { dependencies: { "new-lib": "^1.0.0" }, }); stageTrace(dir, g, "new-lib", "approved"); assert.deepEqual(checkLibraryDecisions(dir), []); }); test("new feature-tier dep with rejected-decision trace staged → exit 1", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", { dependencies: {} }); stagePkg(dir, g, "packages/feat-a", { dependencies: { "new-lib": "^1.0.0" }, }); stageTrace(dir, g, "new-lib", "rejected"); const errs = checkLibraryDecisions(dir); assert.equal(errs.length, 1); assert.equal(errs[0].dep, "new-lib"); assert.equal(errs[0].reason, "not-approved"); assert.equal(errs[0].decision, "rejected"); }); test("new app-tier dep → exit 0", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "apps/web", { dependencies: {} }); stagePkg(dir, g, "apps/web", { dependencies: { "new-lib": "^1.0.0" } }); assert.deepEqual(checkLibraryDecisions(dir), []); }); test("new devDependency → exit 0", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", {}); stagePkg(dir, g, "packages/feat-a", { devDependencies: { "test-lib": "^1.0.0" }, }); assert.deepEqual(checkLibraryDecisions(dir), []); }); test("multi-file diff with mixed pass/fail → exit 1 with per-package report", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", { dependencies: {} }); commitPkg(dir, g, "packages/feat-b", { dependencies: {} }); stagePkg(dir, g, "packages/feat-a", { dependencies: { "lib-a": "^1.0.0" }, }); stagePkg(dir, g, "packages/feat-b", { dependencies: { "lib-b": "^1.0.0" }, }); stageTrace(dir, g, "lib-a", "approved"); // feat-a passes; no trace for lib-b const errs = checkLibraryDecisions(dir); assert.equal(errs.length, 1); assert.equal(errs[0].pkgJson, "packages/feat-b/package.json"); assert.equal(errs[0].dep, "lib-b"); assert.equal(errs[0].reason, "no-trace"); }); test("peerDependencies-only change → exit 0", () => { const { dir, g } = makeRepo(); commitPkg(dir, g, "packages/feat-a", {}); stagePkg(dir, g, "packages/feat-a", { peerDependencies: { react: "^18.0.0" }, }); assert.deepEqual(checkLibraryDecisions(dir), []); }); test("--staged-against mode: new feature-tier dep without trace → exit 1", () => { const { dir, g } = makeRepo(); // Baseline commit: feature package with no deps commitPkg(dir, g, "packages/feat-a", { dependencies: {} }); // Second commit: adds new-lib — no trace file committed alongside it commitPkg(dir, g, "packages/feat-a", { dependencies: { "new-lib": "^1.0.0" }, }); // HEAD has new-lib; HEAD~1 doesn't — no trace in the diff → exit 1 const errs = checkLibraryDecisions(dir, { stagedAgainst: "HEAD~1" }); assert.equal(errs.length, 1); assert.equal(errs[0].dep, "new-lib"); assert.equal(errs[0].reason, "no-trace"); }); });