scripts/check-dependency-catalog.test.mjs
Rama main · réplica de lectura del repositorio original.
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { catalogViolations, parseWorkspaceManifest } from "./check-dependency-catalog.mjs";
const workspace = (dir, manifest) => ({ dir, manifest });
test("parses packages and the default catalog, ignoring comments", () => {
const { packages, catalog } = parseWorkspaceManifest(`packages:
- apps/*
- "packages/*"
catalog:
'@scope/pkg': ^1.2.0
# Comment about the next entry.
typescript: npm:@typescript/typescript6@6.0.2
react: 19.2.3 # trailing comment
onlyBuiltDependencies:
- esbuild
`);
assert.deepEqual(packages, ["apps/*", "packages/*"]);
assert.deepEqual(
[...catalog],
[
["@scope/pkg", "^1.2.0"],
["typescript", "npm:@typescript/typescript6@6.0.2"],
["react", "19.2.3"],
],
);
});
test("rejects a dependency shared by two workspaces without catalog:", () => {
const violations = catalogViolations({
catalog: new Map(),
workspaces: [
workspace("packages/ui", { dependencies: { sonner: "^2.0.7" } }),
workspace("packages/views", { dependencies: { sonner: "catalog:" } }),
],
});
assert.equal(violations.length, 1);
assert.match(violations[0], /^sonner: declared by 2 workspaces.*packages\/ui dependencies \^2\.0\.7$/);
});
test("rejects a plain range for a dependency the catalog defines, even in one workspace", () => {
const violations = catalogViolations({
catalog: new Map([["tailwindcss", "^4"]]),
workspaces: [workspace("apps/desktop", { devDependencies: { tailwindcss: "^4" } })],
});
assert.equal(violations.length, 1);
assert.match(violations[0], /^tailwindcss: catalog defines \^4/);
});
test("accepts catalog:, workspace:, single-workspace deps, peers, and mobile pins", () => {
const violations = catalogViolations({
catalog: new Map([["react", "19.2.3"]]),
workspaces: [
workspace("apps/web", {
dependencies: { react: "catalog:", "@multica/core": "workspace:*", "web-only": "^1.0.0" },
}),
workspace("packages/views", {
peerDependencies: { react: "^19", eslint: "^9.0.0" },
devDependencies: { eslint: "^9.0.0" },
}),
workspace("packages/eslint-config", { peerDependencies: { eslint: "^9.0.0" } }),
workspace("apps/mobile", { dependencies: { react: "19.2.3", "web-only": "^0.9.0" } }),
],
});
assert.deepEqual(violations, []);
});
test("allowlisted drift passes until it is gone, then must be removed", () => {
const allowlist = new Map([["fumadocs-core", "pending upgrade"]]);
const drifted = [
workspace("apps/web", { dependencies: { "fumadocs-core": "^15.5.2" } }),
workspace("apps/docs", { dependencies: { "fumadocs-core": "^16.17.8" } }),
];
assert.deepEqual(catalogViolations({ catalog: new Map(), workspaces: drifted, allowlist }), []);
assert.deepEqual(
catalogViolations({
catalog: new Map([["fumadocs-core", "^16.17.8"]]),
workspaces: drifted,
allowlist,
}),
[],
);
const aligned = catalogViolations({
catalog: new Map([["fumadocs-core", "^16.17.8"]]),
workspaces: drifted.map(({ dir }) => workspace(dir, { dependencies: { "fumadocs-core": "catalog:" } })),
allowlist,
});
assert.equal(aligned.length, 1);
assert.match(aligned[0], /^fumadocs-core: allowlisted but no longer/);
});
test("still checks when run through a symlinked path", (t) => {
// Node gives the main module its real path but keeps argv[1] as typed, so a
// guard comparing the two unresolved would skip main() and exit 0 silently.
const root = mkdtempSync(join(tmpdir(), "dependency-catalog-entry-"));
t.after(() => rmSync(root, { recursive: true, force: true }));
const repo = join(root, "repo");
mkdirSync(join(repo, "scripts"), { recursive: true });
copyFileSync(
fileURLToPath(new URL("./check-dependency-catalog.mjs", import.meta.url)),
join(repo, "scripts", "check-dependency-catalog.mjs"),
);
writeFileSync(join(repo, "pnpm-workspace.yaml"), "packages:\n - packages/*\n");
writeFileSync(join(repo, "package.json"), "{}");
for (const name of ["a", "b"]) {
mkdirSync(join(repo, "packages", name), { recursive: true });
writeFileSync(join(repo, "packages", name, "package.json"), JSON.stringify({ dependencies: { sonner: "^2.0.7" } }));
}
symlinkSync(repo, join(root, "link"), "dir");
const run = (dir) =>
spawnSync(process.execPath, [join(root, dir, "scripts", "check-dependency-catalog.mjs")], { encoding: "utf8" });
const linked = run("link");
assert.equal(linked.status, 1);
assert.match(linked.stderr, /sonner: declared by 2 workspaces/);
const direct = run("repo");
assert.deepEqual([linked.status, linked.stdout, linked.stderr], [direct.status, direct.stdout, direct.stderr]);
});