agent-orchestrator/scripts/check-publishable-deps.mjs

83 lines
2.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Pre-publish sanity check.
*
* Asserts that every `workspace:*` runtime dependency of a publishable
* package (one without `"private": true`) is itself publishable. Without
* this, pnpm would rewrite the workspace dep on publish to a literal
* version pointing at a package that doesn't exist on npm, and consumers
* doing `npm install -g @aoagents/ao` would fail.
*
* Concretely: this catches the case where `@aoagents/ao-cli` has
* `"@aoagents/ao-web": "workspace:*"` while `@aoagents/ao-web` is
* `"private": true` — the dashboard would never reach consumers.
*
* Run from CI before `changeset publish`.
*/
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..");
/** Find every package.json under packages/, regardless of nesting depth. */
function collectPackages(root) {
const found = [];
function walk(dir) {
if (dir.includes("node_modules")) return;
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const e of entries) {
const full = join(dir, e.name);
if (e.isDirectory()) {
walk(full);
} else if (e.name === "package.json") {
const parsed = JSON.parse(readFileSync(full, "utf-8"));
if (typeof parsed.name === "string") {
found.push({ path: full, pkg: parsed });
}
}
}
}
walk(join(root, "packages"));
return found;
}
const packages = collectPackages(repoRoot);
const byName = new Map(packages.map((p) => [p.pkg.name, p]));
const problems = [];
for (const { pkg, path } of packages) {
if (pkg.private === true) continue;
const deps = { ...pkg.dependencies, ...pkg.peerDependencies };
for (const [depName, depSpec] of Object.entries(deps)) {
if (typeof depSpec !== "string" || !depSpec.startsWith("workspace:")) continue;
const target = byName.get(depName);
if (!target) continue;
if (target.pkg.private === true) {
problems.push(
` ${pkg.name} (${path}) depends on ${depName} via workspace:*, ` +
`but ${depName} is private — install would fail on publish.`,
);
}
}
}
if (problems.length > 0) {
console.error("✗ Publishable-dependency check failed:\n");
for (const p of problems) console.error(p);
console.error(
"\nFix by making the dependency publishable (drop `private: true` and add" +
" it to the changeset linked group) OR by removing the runtime dependency.",
);
process.exit(1);
}
console.log(`✓ Publishable-dependency check passed (${packages.length} packages scanned).`);