feat(runtime): --runtime axis (node --run / bun --bun run / deno task) - #96
Merged
Conversation
`--pm bun` conflated "bun installs and invokes scripts here" with "use bun's runtime", so forcing a script's process tree onto bun had nowhere to live. `bun run build` starts under bun but a dependency bin with a `#!/usr/bin/env node` shebang still resolves to system Node; `bun --bun run` is what moves it, and nothing could ask for that. --runtime <node|bun|deno>, RUNNER_RUNTIME, [runtime].js, same three-layer precedence as every other override. bun forces its runtime, deno dispatches `deno task` (which reads package.json scripts), node forces nothing and falls through to the resolved node PM. Applies whichever PM wrote the lockfile, selects the local-file runtime too, propagates to nested runners, and is reported by --explain and doctor --json. Bundles the CLI override strings into CliOverrides, mirroring DiagnosticFlags, so the constructors stay under clippy's argument threshold. Closes #94.
`deno run <file>` is deny-all and ignores the file's shebang, so a file that works under node and bun dies under deno on the first env/fs/net access. Pre-dates --runtime; --pm deno <file> took the same path.
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
🚫 Excluded labels (none allowed) (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
--runtime axis, separate from --pm
The first cut read the runtime override in two places and silently dropped it everywhere else, so `run --runtime bun ./cli.js` on a node-shebanged file ran on node, the exact case the feature exists for. `--runtime node` also hard-errored in a Deno-resolved project, contradicting its own docs. Rework: - Each runtime uses its own script runner: node --run, bun --bun run, deno task. No package manager is consulted, so --runtime node stops resolving a PM and stops bailing. - The override is read at one chokepoint and honoured on every path, or the user is warned it did not apply: shebang and exec-bit local files, dep bins, exec fallback, bun-test fallback, and source selection (a turborepo now biases to package.json). Non-JS sources warn instead of dropping it. - Per-runtime arg forwarding: node needs an injected `--`, deno must not get one, bun neither; no trailing bare `--` on empty args. - node --run skips pre/post lifecycle scripts; runner warns when the task has one. - RUNNER_RUNTIME is exported to children only for CLI/env origins, not config, so a repo's [runtime].js no longer leaks into a nested project. - deno local files get six granular permissions, not -A; -A widened remote imports past deno's allowlist on a false parity claim. - why and doctor --json preview the runtime-correct command; the deno dispatch label matches its real argv.
--runtime axis, separate from --pmThe bun integration tests assert on stdout and discard the child's stderr, so a CI-only failure reports only empty stdout with no cause. Route them through a helper that checks the exit status and prints both streams.
…d run Running the suite through `runner` under GitHub Actions inherits RUNNER_GROUP_ACTIVE=1; a spawned runner then suppresses its own grouping (GHA groups don't nest) and the grouping assertions fail. The tests spawned the binary with the ambient environment intact. Route every spawn through a helper that strips RUNNER_*.
CI installs bun/deno as mise shims. A shim resolves its version by walking up from cwd for a config; runner spawns the runtime with cwd set to a throwaway project outside the repo, where none exists, so bun died with "No version is set for shim" and empty stdout. node passed only because it ships on the image as a real binary. Prepend the real mise bin dirs (`mise bin-paths`, resolved at the crate root where the config lives) to the child PATH so a shimmed runtime resolves independent of cwd; empty and inert when mise is absent.
- why --json: drop `runtime` from `required` (it is emitted only when a runtime override is set), so a payload without it still validates against schema v2; no version bump. - Constrain the runtime label to node|bun|deno in both the why schema and `[runtime].js`, so a bad value fails validation in-editor, not at dispatch. - Source selection: check a forced runtime before a forced PM. With both set, `--pm cargo --runtime bun` biased by the PM (which owns no JS source) and dropped the runtime; the runtime is the more specific intent and now wins. - arrow_only test helper scrubs RUNNER_* like the other spawns, so the four fallback tests asserting on exact stderr are not exposed to ambient config. - Cover the forced/unforced bunx exec branch and the runtime source-selection branch (including the pm+runtime combination) with unit tests. - README: fix the garbled "applies whatever wrote the lockfile" sentence. - schema.rs: raw-string literal for the runtime FIELD_TEMPLATE value, matching every other entry.
--runtime node dispatches `node --run`, which only exists in Node 22+. On an older node the dispatch failed with a cryptic `bad option: --run`. Probe the node on PATH and, when it is older than 22, bail before spawning with a diagnostic naming the version and the alternatives (bun/deno, or drop --runtime). node absent or unparseable is not blocked; the spawn surfaces its own error. Node 22+ and local-file dispatch (`node <file>`) are unaffected.
The if-let/else in the previous commit trips clippy's option-map-or lint (-D), reddening CI. No behaviour change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #94.
--pm bunmeant two things at once: bun installs here, and bun runs scriptshere. It never meant "put the script's process tree on bun", so there was no
way to ask for
bun --bun run. This adds a runtime axis that is that ask.The axis
--runtime <node|bun|deno>,RUNNER_RUNTIME,[runtime].js. Each runtime usesits own script runner and its own package-exec primitive. No package manager is
consulted on the runtime path.
--runtimenodenode --run <task>node <file>npxbunbun --bun run <task>bun <file>bunx --bundenodeno task <task>deno run <file>deno xnode --run(Node 22+) is the piece that makes it symmetric; there was no wayto run a package.json script on plain Node's own runner before.
An explicit runtime outranks a local file's
#!line — overriding theshebang is the whole point, so
run --runtime bun ./cli.jsruns anode-shebanged file on bun. It reaches dependency bins, the exec fallback, and
the bun-test fallback too. In a turborepo a runtime override biases selection
toward
package.json(the source that can honour it). A source that cannothonour a runtime (make, just, cargo, …) warns, with an
--explainline,rather than dropping the request silently.
Behaviour notes that ship in the docs
node --rundoes not run pre/post lifecycle scripts (npm run,bun runand
deno taskdo). Runner warns when the task has one.requires an injected
--before user args, deno must not receive one, bunneeds neither.
RUNNER_RUNTIMEis exported to nestedrunner/runchildren only for CLIand env origins, never for a config-file
[runtime].js— a repo's runtimechoice does not leak into a different project reached through a nested call.
(
--allow-read/write/net/env/run/sys), not-A.-Aalso grants--allow-import, which would let a local file fetch and execute code from anyhost past Deno's default allowlist; node and bun refuse remote imports, so the
parity argument for a blanket grant does not hold.
--explain,runner why, andrunner doctor --jsonall report theruntime-correct command.
History
The first push of this branch applied the runtime on only two dispatch paths
and dropped it on the rest, so the advertised node-shebang case did not work and
--runtime nodehard-errored in Deno projects. Commit8cf3ffareworks it ontoa single chokepoint and fixes the dependent defects. The diff-from-master is the
final design; the intermediate commits are kept rather than squashed.
Verification
dprint check,cargo lint,cargo test --all-featuresall clean; 1106 tests.tests/runtime_override.rsdrives real node/bun/deno binaries for eachbehaviour above and skips when a runtime is not installed.
Residual, disclosed
node --runneeds Node 22+; on older Node the spawn fails naming node, withno pre-flight version check.
not surfaced in
doctor --json, which does not dispatch a task.