mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-26 16:04:02 +00:00
fix(ruvector): verify-dist guards package.json entrypoints (#376)
`verify-dist.js` was added after #399 to fail the publish when files required by `bin/cli.js` weren't built. It would NOT have caught #376 because that regression hit `package.json#main` (`dist/index.js`) — a path the previous guard never inspected. The published 0.2.23 declared `main: dist/index.js` but shipped without it, so every consumer doing `require('ruvector')` or `await import('ruvector')` crashed. Strengthen the guard to also check: - package.json `main`, `types`, `module`, and every `bin.*` entry resolves to a real file under the package root. - The `main` entry actually loads — `node -e "require(<main>)"` runs as a final smoke (skippable via VERIFY_DIST_SKIP_SMOKE=1 for emergencies). Verified end-to-end against ruvector@0.2.25 on Node 22.22.2: $ node scripts/verify-dist.js verify-dist: 13 dist path(s) referenced by bin/cli.js present. verify-dist: 3 package.json entrypoint(s) present. verify-dist: require('dist/index.js') smoke OK. $ mv dist/index.js dist/index.js.bak # simulate the #376 regression $ node scripts/verify-dist.js verify-dist: package would publish broken: - 1 dist file(s) referenced by bin/cli.js are missing: - dist/index.js - 1 package.json entrypoint(s) point at missing files: - main → dist/index.js exit code: 1 The current 0.2.25 tarball already includes `dist/index.js`, so end users on the latest release are not affected; this PR is the regression guard that would have prevented the publish in the first place. Closes #376 Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
e383476014
commit
9ec433bc32
1 changed files with 108 additions and 35 deletions
|
|
@ -1,51 +1,124 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* verify-dist.js — pre-publish gate that fails the build if any file
|
||||
* `bin/cli.js` requires from `../dist/...` is missing.
|
||||
* verify-dist.js — pre-publish gate that fails the build if the published
|
||||
* tarball would be unusable.
|
||||
*
|
||||
* Why: 0.2.23 was published without a `dist/` directory at all (issue #399),
|
||||
* which silently broke `ruvector doctor`, the entire `embed` subsystem, and
|
||||
* `rvf` commands. tsc was supposed to run via `prepublishOnly`, but the
|
||||
* hook didn't fire (or the build failed silently). This script makes the
|
||||
* publish itself fail loudly when the artifact is incomplete.
|
||||
* History:
|
||||
* - #399: 0.2.23 published without `dist/` at all — `ruvector doctor`,
|
||||
* `embed`, and `rvf` commands all crashed because tsc didn't
|
||||
* run via `prepublishOnly` (or failed silently).
|
||||
* - #376: same release (0.2.23) — `main: dist/index.js` was declared but
|
||||
* the tarball didn't contain it, so any consumer doing
|
||||
* `require('ruvector')` or `await import('ruvector')` blew up
|
||||
* on a fresh install.
|
||||
*
|
||||
* Both regressions are guarded here. The script asserts:
|
||||
* 1. Every `require('../dist/...')` in `bin/cli.js` resolves to a file.
|
||||
* 2. `package.json#main`, `types`, and every `bin` entry resolve to a
|
||||
* file (this is what would have caught #376).
|
||||
* 3. Optional smoke check: `node -e "require('<main>')"` succeeds when
|
||||
* run with the repo as cwd. Skipped under VERIFY_DIST_SKIP_SMOKE=1
|
||||
* so a hot fix can publish even if a peer dep is unsatisfied.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const pkgRoot = path.resolve(__dirname, '..');
|
||||
const pkgJsonPath = path.join(pkgRoot, 'package.json');
|
||||
const cliPath = path.join(pkgRoot, 'bin', 'cli.js');
|
||||
|
||||
if (!fs.existsSync(pkgJsonPath)) {
|
||||
console.error('verify-dist: package.json not found — package layout is broken.');
|
||||
process.exit(1);
|
||||
}
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
||||
|
||||
const errors = [];
|
||||
|
||||
// 1. cli.js dist requires (#399).
|
||||
if (!fs.existsSync(cliPath)) {
|
||||
console.error('verify-dist: bin/cli.js not found — package layout is broken.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cliSource = fs.readFileSync(cliPath, 'utf8');
|
||||
|
||||
// Collect every `require('../dist/...')` referenced by the CLI.
|
||||
const distRequires = Array.from(
|
||||
cliSource.matchAll(/require\(['"]\.\.\/(dist\/[^'"]+\.js)['"]\)/g),
|
||||
(m) => m[1],
|
||||
);
|
||||
const unique = Array.from(new Set(distRequires)).sort();
|
||||
|
||||
const missing = unique.filter(
|
||||
(rel) => !fs.existsSync(path.join(pkgRoot, rel)),
|
||||
);
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error(
|
||||
`verify-dist: ${missing.length} dist file(s) referenced by bin/cli.js are missing:`,
|
||||
errors.push(`bin/cli.js not found at ${path.relative(pkgRoot, cliPath)}`);
|
||||
} else {
|
||||
const cliSource = fs.readFileSync(cliPath, 'utf8');
|
||||
const distRequires = Array.from(
|
||||
cliSource.matchAll(/require\(['"]\.\.\/(dist\/[^'"]+\.js)['"]\)/g),
|
||||
(m) => m[1],
|
||||
);
|
||||
for (const rel of missing) {
|
||||
console.error(` - ${rel}`);
|
||||
const unique = Array.from(new Set(distRequires)).sort();
|
||||
const missing = unique.filter(
|
||||
(rel) => !fs.existsSync(path.join(pkgRoot, rel)),
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
errors.push(
|
||||
`${missing.length} dist file(s) referenced by bin/cli.js are missing:\n` +
|
||||
missing.map((r) => ` - ${r}`).join('\n'),
|
||||
);
|
||||
} else {
|
||||
console.log(`verify-dist: ${unique.length} dist path(s) referenced by bin/cli.js present.`);
|
||||
}
|
||||
console.error(
|
||||
"\nRun `npm run build` and confirm tsc emitted under dist/. If a path was renamed,",
|
||||
);
|
||||
console.error('update bin/cli.js to match.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`verify-dist: ${unique.length} dist path(s) present.`);
|
||||
// 2. package.json entrypoints (#376).
|
||||
const entrypointFields = [];
|
||||
if (typeof pkg.main === 'string') entrypointFields.push(['main', pkg.main]);
|
||||
if (typeof pkg.types === 'string') entrypointFields.push(['types', pkg.types]);
|
||||
if (typeof pkg.module === 'string') entrypointFields.push(['module', pkg.module]);
|
||||
if (pkg.bin && typeof pkg.bin === 'object') {
|
||||
for (const [name, rel] of Object.entries(pkg.bin)) {
|
||||
if (typeof rel === 'string') entrypointFields.push([`bin.${name}`, rel]);
|
||||
}
|
||||
} else if (typeof pkg.bin === 'string') {
|
||||
entrypointFields.push(['bin', pkg.bin]);
|
||||
}
|
||||
|
||||
const missingEntries = entrypointFields.filter(
|
||||
([, rel]) => !fs.existsSync(path.join(pkgRoot, rel)),
|
||||
);
|
||||
if (missingEntries.length > 0) {
|
||||
errors.push(
|
||||
`${missingEntries.length} package.json entrypoint(s) point at missing files:\n` +
|
||||
missingEntries.map(([f, r]) => ` - ${f} → ${r}`).join('\n'),
|
||||
);
|
||||
} else {
|
||||
console.log(`verify-dist: ${entrypointFields.length} package.json entrypoint(s) present.`);
|
||||
}
|
||||
|
||||
// 3. Smoke: actually require the main entry (catches "file present but
|
||||
// syntactically broken" regressions). Skipped if main is missing —
|
||||
// error already reported above.
|
||||
if (
|
||||
!process.env.VERIFY_DIST_SKIP_SMOKE &&
|
||||
typeof pkg.main === 'string' &&
|
||||
fs.existsSync(path.join(pkgRoot, pkg.main))
|
||||
) {
|
||||
const mainAbs = path.join(pkgRoot, pkg.main);
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
['-e', `require(${JSON.stringify(mainAbs)})`],
|
||||
{ cwd: pkgRoot, encoding: 'utf8' },
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
errors.push(
|
||||
`\`require('${pkg.main}')\` failed (exit ${result.status}):\n` +
|
||||
(result.stderr || result.stdout || '<no output>')
|
||||
.split('\n')
|
||||
.slice(0, 6)
|
||||
.map((l) => ` ${l}`)
|
||||
.join('\n'),
|
||||
);
|
||||
} else {
|
||||
console.log(`verify-dist: require('${pkg.main}') smoke OK.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('\nverify-dist: package would publish broken:\n');
|
||||
for (const e of errors) console.error(` - ${e}`);
|
||||
console.error(
|
||||
'\nRun `npm run build` and confirm tsc emitted under dist/. If a path was renamed,',
|
||||
);
|
||||
console.error('update package.json or bin/cli.js to match.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue