feat(run): fall back to node_modules/.bin on a script miss#500
feat(run): fall back to node_modules/.bin on a script miss#500afonsojramos wants to merge 5 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — nub run <name> now falls back to node_modules/.bin/<name> when the name matches no package.json script, giving locally installed CLIs a script-free entry point (bun/aube parity).
- Two script-miss hooks in
cli.rs— the single-packageScriptSelection::Nonearm inrun_selected_scriptsand the-w/--workspace-rootNonearm inrun_scriptboth tryrun_bin_fallback_for_plain_namebefore--if-present/error. Recursive (-r/--filter) correctly stays script-only (it returns viarun_workspace_targetbefore the fallback site). - Path-shaped names refused —
is_plain_bin_namerejects/and\, so../foocan't traverse out of.binvia the walk-up and a/regex/selector never falls back. - Resolution reuses
nub execmachinery —find_binwalk-up + shebang-awarelaunch_bin; no lifecycle hooks, nonpm_lifecycle_*env for a bin. - PnP
--probemode inpnp-bin-run.cjs— a resolve-only pass (exit 0/1, no output) keeps a PnP bin miss silent so themissing scripterror owns the double-miss instead of the runner's 127. - Error/docs/tests — the
missing scripterror gains a(and no node_modules/.bin/<name>)note;run.mdx/exec.mdxdocument the fallback and the deliberatepnpm run/npm rundeviation; 11 integration tests, +3 PnP-matrix cells, and a conformance note pin the behavior.
The implementation is clean and reuses existing resolution/launch code rather than duplicating it. I traced the interactions that looked risky and they hold: the dep-freshness gate is once-per-process-latched and fires in run_script before the fallback, so the PnP probe's run_file_in_dir re-entry returns early rather than double-gating or reporting a spurious miss; a Policy::Error staleness abort short-circuits before the fallback is reachable; and script-always-wins is enforced by resolving the script first at both sites. The scope cuts (no -r/--filter fan-out, no lifecycle hooks) are documented and user-approved.
One non-actionable note for awareness: the fallback preamble prints $ <name> <args> whereas a script run prints $ <resolved command>. That difference is inherent (a bin has no command string) and the tests pin the current shape, so nothing to change.
Claude Opus | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — one new commit since the prior review restructures the PnP bin-run probe path to avoid process.exit(), fixing a Windows-only crash where an aborted exit made a bin HIT read as a miss.
process.exit()-free probe path inpnp-bin-run.cjs— on Windows,process.exit()with piped stdio races libuv's handle teardown and can abort on theUV_HANDLE_CLOSINGassert, surfacing a crash code instead of the intended 0/1. The probe now setsprocess.exitCodeand lets the event loop drain:if (!api && !probe)skips the 127 exit for probes, a miss callsprobeMiss()(exitCode = 1), a hit setsexitCode = 0, and the ESM-fallbackprocess.exit(1)is confined to the non-probe run block. The bin-resolution loop was wrapped inif (api)so the fall-through works.
I traced all four probe branches (no-api / miss / hit / run) and each exits with the correct code with no reachable process.exit() on the probe path. The Rust caller (try_run_bin_fallback, cli.rs:4940) still keys off run_file_in_dir(...) != 0, so keying on an exitCode-driven natural exit is behaviorally equivalent to the old explicit exits — the fix is transparent to the caller. The non-PnP probe case (!api && probe) correctly falls through to probeMiss() and reports a miss.
Claude Opus | 𝕏

Summary
nub run <name>now falls back tonode_modules/.bin/<name>when the name matches no script, so locally installed CLIs run without a pass-through script.nub run astro devworks in a project with no"astro": "astro"boilerplate — matchingbun runand aube's own documented contract ("If no script matches, aube falls back tonode_modules/.bin/<name>"), which nub's Rustrunpath had not implemented.nub run astro dev # no script needed; args forward as with a script nub run tsc --noEmitBehavior, in short:
nub execalready uses.nubx/dlxterritory).pre/posthooks, nonpm_lifecycle_*env.--if-presenttreats a resolvable bin as present./regex/selectors never fall back.missing scripterror, extended to note the.binlookup.Design decisions
-wroot lookup), trying the bin before--if-present, matching aube's order. Resolution and launch reuse thenub execmachinery (find_binwalk-up, shebang-awarelaunch_bin) — no new resolution logic.pnp-bin-run.cjsrunner gained a quiet--probe(exit 0/1, no output) so a PnP bin miss doesn't shadow themissing-scripterror with the runner's own 127 message.nub run ../foocan't traverse out of.binvia the walk-up (found in review; regression test included).-r/--filterkeep script-only resolution; per-member bin fallback is a deliberate follow-up.Session-settled decisions carried from planning: fallback scope limited to single-package +
-w(user-approved, over including-r/--filternow); no lifecycle hooks for bins (user-approved, over honoringpre<name>/post<name>); PnP parity included via the existing runner (user-approved, over node_modules-only); double miss keeps exit 1 (user-approved, over exec's 127 shape).Test plan
#!/bin/sh) bins, walk-up to the workspace root,--silentpreamble suppression,--if-presentsemantics, no lifecycle hooks, double-miss error shape, regex selectors never falling back,-wroot bin + root-script precedence, path-traversal guard.cargo test -p nub-core -p nub-cli: full suite green.run-bin(PnP fallback),run-bin-miss(probe-silent double miss keeps themissing-scripterror),ws-run-bin(from a member).run-missing-scriptcell still pins exit 1; expectation note updated to record the new requirement (bin lookup must also miss)..cmd-shaped fixtures on the windows CI leg;find_bin's extension handling is already unit-tested.Post-Deploy Monitoring & Validation
No additional operational monitoring required — this is a local CLI behavior change with no service, telemetry, or data-path impact; the conformance suite and PnP matrix pin the behavior on every CI run.
Documentation
runner/run.mdxgains a "Local binaries" section (precedence, no-registry boundary, PnP,--if-present, the deliberatepnpm run/npm rundeviation), andrunner/exec.mdxnotes whenexecremains the explicit form.