83 lines
2.7 KiB
JavaScript
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).`);
|