Announcing aubeshim v1.0.0
Contents
Introduction
I’m happy to announce aubeshim↗ v1.0.0, the first stable release of a small Rust tool I’ve been using on my own machines for a while. This is the first time I’ve written about it here, so I’ll start with what it is, why I built it, and what it does.
What Is aubeshim?
aube↗ is a fast JavaScript package manager from
jdx (the author of mise↗). It offers quick installs, a
strict node_modules layout, and run-time checks that auto-install missing
dependencies when you run a script.
aubeshim is a separate project (not affiliated with jdx or aube) that installs
PATH shims named bun, bunx, npm, npx, pnpm, pnpx, pnx, and yarn.
When you run one of those commands in a directory where aubeshim is active, it
routes the invocation to aube if the command shape is compatible. Otherwise it
falls back to the real package manager binary.
The point is to get aube’s benefits without editing every project’s
package.json scripts or retraining your muscle memory. npm install,
pnpm add, bun run build, and npx eslint can all go through aube when the
shim recognizes the command.
Why Use It?
If you work across many JavaScript repositories, you probably have a mix of
package-lock.json, pnpm-lock.yaml, bun.lock, and yarn.lock files. Each
package manager keeps its own copy of dependencies under node_modules, and
those trees add up fast. On a machine with dozens of checkouts, it’s easy to end
up with hundreds of gigabytes of duplicate packages.
aubeshim doesn’t magically unify every lockfile format, but it does let you standardize on aube for day-to-day installs and script runs in the directories you choose, while still falling back to the real npm, pnpm, bun, or yarn binary when a command needs exact package-manager behavior. Registry queries, publish commands, and tool-specific flags that aube doesn’t model all pass through transparently.
I also wanted something that cooperates with mise. aubeshim discovers real
package-manager binaries through mise which first, auto-detects whether mise
or aube owns each global npm CLI, and activates cleanly after mise activate.
You keep your existing tool versions; aubeshim just intercepts compatible
invocations on the way to them.
What It Can Do
Here’s a quick tour of the routing behavior:
- Local installs and scripts run through aube. That covers
npm install,npm ci,npm run build,pnpm install,bun add, and similar commands. - Package edits are normalized to aube’s vocabulary.
npm install lodashbecomesaube add lodash, andnpm uninstall lodashbecomesaube remove lodash. - For npm compatibility, shimmed npm commands set
AUBE_NODE_LINKER=hoistedunless you already set a node-linker env var, so the resultingnode_modulestree looks like what npm users expect. - One-off runners such as
npx,bunx,pnpx, andpnpm dlxroute toaube dlxwhen the flags are compatible. No-install modes map toaube exec --no-install. - Since aube already speaks a pnpm-compatible command surface, pnpm mostly passes through directly.
- Common Yarn package-manager and script commands route to aube; Yarn-only management commands fall back to real Yarn.
bun install,bun add, andbun rungo through aube, while other Bun runtime commands and unknown subcommands fall back to the real binary.- Global npm CLI commands use
global_packages = "auto"by default. aubeshim checks whether mise or aube owns the package and routes add/install/remove/outdated accordingly. Registry metadata commands likenpm view ... --jsonstill hit real npm so tools like mise can parse the response.
You control where shimming happens with a TOML config file. Globs match the
current working directory or any ancestor, so a rule on ~/devel/projects/**
covers nested packages inside a monorepo:
enabled = true
default = true
global_packages = "auto"
ignore = [
"~/devel/work/broken-expo",
]
shim = [
"~/devel/projects/**",
]
Commands run from ignored directories pass straight through to the real package
manager. Everything else uses aube when default = true and no more specific
shim glob applies.
Getting Started
Install with Cargo and create the shims:
cargo install aubeshim
aubeshim install --force
Activate aubeshim after mise (mise installs its own package-manager shims, so aubeshim needs to come last):
eval "$(mise activate zsh --shims)"
eval "$(aubeshim activate zsh)"
If you use full mise activate without --shims, add --persistent to the
aubeshim line:
eval "$(mise activate zsh)"
eval "$(aubeshim activate zsh --persistent)"
From a source checkout, ./install.sh builds the binary and replaces shims in
~/.local/share/aubeshim/shims.
Run npm --version (or bun --version, etc.) inside a shimmed directory and
you should see the real package manager version plus a parenthesized hint
showing the aubeshim and aube versions.
Compatibility Notes
aubeshim is opinionated about when to shim and when to fall back, but migrating
existing projects to aube can still surface dependency layout differences.
aube’s strict layout rejects imports of packages that aren’t declared in your
own package.json, even if npm-style hoisting made them work before. Adding
direct dependencies is usually the right fix; a hoisted linker profile is
available as a fallback for messier trees.
Lockfile compatibility is a separate question. A hoisted aube install might
produce a package-lock.json that npm coworkers need to validate with npm ci.
If your team isn’t ready to switch lockfiles, keep npm responsible for writing
the lockfile, or import to a native aube-lock.yaml when you’re ready to commit
fully.
I’ve been tracking upstream interop findings in aube-issues↗ as I run into them.