Repository navigation
v2.3.0: Host-bound Node launchers for embedders, plus hoisted `.bin`, lockfile patch-hash, and terminal-cleanup fixes
Embedding hosts such as mise can now bind installed Node CLIs to a specific Node executable, and a batch of fixes covers hoisted-install bin linking, aube-lock.yaml patch hashes, ignoreScripts resolution, signal delivery in dlx --shell-mode, and terminal state after failed or interrupted installs.
Added
-
Bind installed CLIs to a host-managed Node runtime (#1576 by @jdx) — Installed Node CLIs previously found their interpreter through the caller's
PATH, so a tool manager could not give an application its own runtime without also changing which Node its project subprocesses see.EmbedderRuntime::bind_bins_togenerates launchers that invoke the supplied absolute Node executable directly while leaving the inheritedPATHunchanged; it works with both embeddedinstallandadd:let mut options = aube::embed::InstallOptions::new("/tools/my-cli"); options.runtime = Some( aube::embed::EmbedderRuntime::selector("/runtimes/node/25/bin") .bind_bins_to("/runtimes/node/25/bin/node"), ); aube::embed::install(options).await?;
The path is kept as written (symlinks are not canonicalized), so a moving major-version path like mise's
installs/node/25/bin/nodeworks; a missing runtime fails rather than falling back to ambient Node. Changing or removing the binding regenerates launchers on the next install, including frozen ones. Bound installs always materialize locally and disable the global virtual store so runtime-specific launchers stay out of shared trees. Simple Node shebang flags are preserved; quoted shebang arguments and environment assignments are rejected. Standalone aube's runtime selection is unchanged — this is the launcher support a host needs to implement its own policy (see jdx/mise#13477).
Fixed
- Hoisted installs link transitive dependency bins (#1548 by @jdx) — With
--node-linker=hoisted, only importers' direct dependencies were linked intonode_modules/.bin, so packages whose lifecycle scripts call a binary from their own dependencies failed with errors likesh: node-pre-gyp: command not found(bcrypt,prebuild-install,napi-postinstall; see discussion #1543). Hoisted installs now follow npm's rule: each package's bins are linked into the.bin/of thenode_modules/it sits in, and a dependency's lifecycle script sees its own nested.binfirst so a version conflict no longer silently runs the hoisted winner. An importer's ownbinand its direct dependencies always take precedence over transitive packages on a name collision. Isolated installs are unaffected. aube rebuildrelinks the full bin surface (#1550 by @jdx) —rebuildonly relinked per-dependency shims, so a command added or retargeted inpackage.json#binafter install stayed missing or stale until the nextaube install.rebuildnow uses the same bin-linking entry point asinstall(importer bins, workspace member bins, then dependency bins). If a workspace member manifest is unreadable, it warns and reconciles the root importer instead of aborting.aube rebuild --filter <member>is not covered by this change.ignoreScriptsis honored from env,.npmrc, andaube-workspace.yaml(#1552 by @jdx, fixes #1551) — Only the--ignore-scriptsflag reached the installer;AUBE_IGNORE_SCRIPTS,npm_config_ignore_scripts,ignore-scripts=truein.npmrc, andignoreScripts: truein the workspace yaml were parsed and then discarded, soaube config get ignore-scriptsprintedtruewhile the next install ran scripts anyway. All sources now apply acrossinstall,ci,add,remove,update(including itspnpm:devPreinstallhook),dlx,deploy, and the auto-installaube runperforms on a stale tree — which previously had no way to skip scripts at all. An explicit flag still wins over a lower-precedencefalse.pack,publish, andversionkeep their own--ignore-scriptsflags.aube-lock.yamlkeeps patch hashes on re-resolve (#1577 by @jdx) — In a project withpatchedDependencies, runningaube adddropped the(patch_hash=…)suffix from patched entries inaube-lock.yaml, producing a spurious lockfile diff on every add and anode_modules/.aube/directory name that varied by how the lockfile was produced (reported in jdx/mise#13456).aube-lock.yamlnow records patch identities the same waypnpm-lock.yamldoes — a top-levelpatchedDependencies:map and(patch_hash=…)in entry names — consistently after install, add, and import, and a plainaube installnow detects edited, added, or removed patches. Upgrade note: existing hashlessaube-lock.yamlfiles still pass--frozen-lockfileunchanged; the next non-frozen install oraddrecords the hashes as a one-time diff. Projects without patches are unaffected.- Lifecycle scripts get a working
npm_execpathwhen aube is embedded (#1565 by @jdx) — Embedded aube setnpm_execpathto the host's binary, so a package running${npm_execpath} run verify-build(e.g.install-artifact-from-github, used byre2) invoked the host's own CLI and failed, causing a needless source build or a failed install (jdx/mise#13451). Embedded aube now exports a shim that re-enters aube's CLI through the host via a private__aube-cliargv token (embed::CLI_TRAMPOLINE_ARG); hosts dispatch it the same way as the existing node-gyp trampoline, anddocs/embedding/rust.mdnow documents both. If the shim cannot be written,npm_execpathis left unset rather than naming the host.AUBE_CLI_EXEis new in the lifecycle environment, and an inherited outernpm_execpathis now cleared before stamping. Standalone aube is unchanged. aube dlx --shell-modedelivers termination signals to the tool (#1562 by @jdx) — Signaling anaube dlx --shell-mode(oraubx -c) process stopped at the intermediatesh -c, leaving the tool running and aube hung waiting on it. A shell-mode line that is a single plain command now runs without a shell (using the same strict allowlist asaube run), so the tool is aube's direct child, receives the forwarded signal, and its exit code propagates. Pipelines, redirects, expansions, quoting, and shell builtins still run undershwith unchanged behavior, and the--shell-modehelp text now describes this contract.- Terminal state is restored when an install fails, is interrupted, or panics (#1559, #1560 by @jdx) — A failing install (trust-policy rejection, unresolvable dependency, network error) exited without retiring the progress renderer, leaving the cursor hidden and the OSC 9;4 taskbar indicator lit in iTerm2, Ghostty, and VS Code (discussions #1557 and #1558). Every error path now restores both before the diagnostic prints, and while the progress bar is active aube also handles
SIGINT/SIGTERM/SIGHUP/SIGQUITand panics to restore the terminal before re-raising — Ctrl-C still exits 130. Signals already ignored or handled (e.g. undernohup, or by an embedding host) are left alone. Windows gets the panic restore but not the signal handling.
Full Changelog: v2.2.17...v2.3.0
💚 Sponsor aube
aube is built and maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.
If aube saves your team install time or CI minutes, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep aube fast, free, and independent.