fix(scripts): validate the bin name and that its file exists
Copilot review on #1997: - A string-form `bin` is npm's shorthand for ONE command, named after the package, so ignoring `binName` let a typo silently resolve the package's only executable while bypassing the documented "declares no such bin" failure. Match it against the manifest's unscoped name instead. - A declared bin whose file is absent (partial install) was returned unchecked. `process.execPath <missing>` still spawns and exits 1 with empty stdout, which `rawProjectListing` records as "no diagnostic captured" — reproducing the bogus every-file-uncovered report this helper exists to eliminate. Throw here, where the remedy is actionable. Also restores `rawProjectListing`'s docblock, which the new `tscEntry` block had orphaned above itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01387r3MotGk52hrkfjzHFsN Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
2333a98fe7
commit
4b143ce043
1 changed files with 121 additions and 20 deletions
|
|
@ -10,7 +10,7 @@
|
|||
// (no npx resolution), and pinned to the locally installed package exactly as
|
||||
// `npx --no-install` was.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
|
||||
|
|
@ -24,18 +24,42 @@ import path from "node:path";
|
|||
* export `./bin/vite.js`, so `require.resolve("vite/bin/vite.js")` throws
|
||||
* `ERR_PACKAGE_PATH_NOT_EXPORTED` (`./package.json` is always exported).
|
||||
*
|
||||
* Throws if the package isn't installed from `fromDir` or declares no such
|
||||
* bin — the caller decides whether that's a hard "cannot measure" error or a
|
||||
* fallback.
|
||||
* Throws if the package isn't installed from `fromDir`, declares no such bin,
|
||||
* or declares one whose file is missing — the caller decides whether that's a
|
||||
* hard "cannot measure" error or a fallback.
|
||||
*/
|
||||
export function resolveNodeBin(pkg, binName, fromDir) {
|
||||
const pkgPath = createRequire(path.join(fromDir, "package.json")).resolve(
|
||||
`${pkg}/package.json`,
|
||||
);
|
||||
const bin = JSON.parse(readFileSync(pkgPath, "utf8")).bin;
|
||||
// A string-form `bin` names a single command (the package's own name).
|
||||
const rel = typeof bin === "string" ? bin : bin?.[binName];
|
||||
const manifest = JSON.parse(readFileSync(pkgPath, "utf8"));
|
||||
const { bin } = manifest;
|
||||
// A string-form `bin` is npm's shorthand for ONE command, named after the
|
||||
// package (unscoped). It does not make every requested `binName` valid, so
|
||||
// match it rather than accepting whatever was asked for — otherwise a typo
|
||||
// silently resolves the package's only executable instead of failing.
|
||||
const rel =
|
||||
typeof bin === "string"
|
||||
? unscopedName(manifest.name ?? pkg) === binName
|
||||
? bin
|
||||
: undefined
|
||||
: bin?.[binName];
|
||||
if (typeof rel !== "string")
|
||||
throw new Error(`${pkg} declares no "${binName}" bin in its package.json`);
|
||||
return path.join(path.dirname(pkgPath), rel);
|
||||
const entry = path.join(path.dirname(pkgPath), rel);
|
||||
// A declared bin whose file is absent is a partial install, and it fails
|
||||
// *silently* downstream: `process.execPath <missing>` still spawns fine and
|
||||
// exits 1 with nothing on stdout, which `rawProjectListing` records as "no
|
||||
// diagnostic captured" and turns into the bogus every-file-uncovered report
|
||||
// this helper exists to eliminate. Fail here, where the remedy is actionable.
|
||||
if (!existsSync(entry))
|
||||
throw new Error(
|
||||
`${pkg}'s "${binName}" bin points at a missing file: ${entry}`,
|
||||
);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** `@scope/name` → `name`; an unscoped name is returned unchanged. */
|
||||
function unscopedName(name) {
|
||||
return name.startsWith("@") ? name.slice(name.indexOf("/") + 1) : name;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue