Files
ruvnet__ruflo/scripts/audit-package-dep-overlap.mjs
Reuven 1430b4c76e fix(embeddings): #2018 — drop @huggingface/transformers from optionalDependencies (npx Invalid Version)
Same failure shape as #1147: `npx @claude-flow/cli@latest` crashes with
`npm error Invalid Version: ` on npm 11.x / Node 24+.

Root cause: @claude-flow/embeddings declared @huggingface/transformers in
both `optionalDependencies` AND `peerDependencies` (with
peerDependenciesMeta.optional = true). When npm 11.x arborist runs
PlaceDep.pruneDedupable → Node.canDedupe → semver.eq, the overlap plus a
"-dev." prerelease transitive (onnxruntime-web@1.26.0-dev.…) trips the
empty-version crash inside @npmcli/arborist.

Fix:
- @claude-flow/embeddings: keep only the optional peerDependency, drop
  the duplicate optionalDependencies entry. Bumped to 3.0.0-alpha.17.
- @claude-flow/cli: pin embeddings to ^3.0.0-alpha.17 so existing
  lockfiles re-resolve to the fixed version. Bumped to 3.7.0-alpha.39.

Regression guards (so this can't recur):
- scripts/audit-package-dep-overlap.mjs — static check that no package
  declares the same name in BOTH optionalDependencies and
  peerDependencies. Wired into v3-ci.yml as part of the
  plugin-package-audit job.
- scripts/smoke-cli-npx-install.mjs — behavioural smoke: pack the CLI
  tarball, install it into a scratch project with optionals enabled,
  fail if `npm install` prints "Invalid Version:" or exits non-zero.
  New v3-ci.yml job `cli-npx-install-smoke` runs on Node 22 + 24
  (Node 24 ships npm 11.x, where the bug surfaces) and gates witness-verify.

Verified end-to-end:
  $ npx -y @claude-flow/cli@3.7.0-alpha.39 doctor
  ✓ MCP Servers, AIDefence, Disk Space, TypeScript … 10 passed
2026-05-15 21:44:39 -04:00

95 lines
3.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Package dependency overlap audit (regression guard for ruvnet/ruflo#1147 and #2018).
*
* Both issues are the same failure shape:
* `npm error Invalid Version: ` (empty) thrown by `new SemVer('')` inside
* `@npmcli/arborist`'s `PlaceDep.pruneDedupable → Node.canDedupe → semver.eq`.
*
* Root cause: a package declares the SAME dependency name in both
* `optionalDependencies` and `peerDependencies` (typically with
* `peerDependenciesMeta[name].optional: true`). Newer npm (>=11) trips on
* this overlap during the dedupe pass when the dep itself has a "-dev." /
* prerelease tagged transitive (`onnxruntime-web@1.26.0-dev.…`), producing
* the user-facing empty-version crash.
*
* Fix: any name should appear in EITHER `optionalDependencies` OR as an
* optional peer — never both. This script scans every package.json under
* `v3/@claude-flow/*` and `v3/plugins/*` and fails on the overlap.
*
* Usage:
* node scripts/audit-package-dep-overlap.mjs # exit 1 on any overlap
* node scripts/audit-package-dep-overlap.mjs --json # machine-readable
*/
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
import { join } from 'node:path';
const REPO_ROOT = process.cwd();
const SCAN_DIRS = [
join(REPO_ROOT, 'v3', '@claude-flow'),
join(REPO_ROOT, 'v3', 'plugins'),
];
const JSON_OUT = process.argv.includes('--json');
const issues = [];
const note = (pkg, code, message) => issues.push({ pkg, code, message });
function listPackages(root) {
if (!existsSync(root)) return [];
return readdirSync(root)
.map((name) => join(root, name))
.filter((p) => {
try { return statSync(p).isDirectory() && existsSync(join(p, 'package.json')); }
catch { return false; }
});
}
let scanned = 0;
for (const root of SCAN_DIRS) {
for (const dir of listPackages(root)) {
const pkgPath = join(dir, 'package.json');
let pkg;
try { pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); }
catch (e) { note(dir, 'PARSE', `invalid JSON: ${e.message}`); continue; }
scanned++;
const label = pkg.name || dir;
const opt = pkg.optionalDependencies || {};
const peers = pkg.peerDependencies || {};
// --- Overlap check: same name in optionalDependencies AND peerDependencies.
// npm 11.x arborist dedupe pass crashes with "Invalid Version: " on this
// pattern when any transitive carries a prerelease tag — that's #1147 / #2018.
for (const name of Object.keys(opt)) {
if (peers[name] !== undefined) {
note(label, 'OVERLAP',
`"${name}" is in BOTH optionalDependencies and peerDependencies — ` +
`npm 11.x arborist crashes with "Invalid Version: " on dedupe. ` +
`Remove it from optionalDependencies and keep only the optional peer.`);
}
}
}
}
const report = {
scanned,
issueCount: issues.length,
issues,
};
if (JSON_OUT) {
console.log(JSON.stringify(report, null, 2));
} else {
console.log(`package dep-overlap audit — scanned ${scanned} package(s)`);
if (issues.length === 0) {
console.log(' ok: no optionalDependencies/peerDependencies overlaps');
} else {
for (const i of issues) {
console.log(` fail [${i.code}] ${i.pkg}: ${i.message}`);
}
console.log(`\n${issues.length} issue(s) — see ruvnet/ruflo#1147 and #2018 for context`);
}
}
process.exit(issues.length > 0 ? 1 : 0);