Problem
After pantry install, npm dependencies declared in package.json are downloaded and extracted into pantry/<name>/. However, bun run X, bun --bun X, and any bun build invocation use Bun's module resolver, which only walks node_modules/ (plus whatever bunfig.toml's linker setting points to — which is also node_modules/). pantry/<name>/ is invisible to Bun.
The result is that swapping bun install for pantry install in a Bun/npm project leaves the on-disk state in a configuration where Bun cannot find the packages it just "installed."
The build pipeline runs on Bun
This isn't just about apps that happen to import 'lodash' at runtime — the entire build/test/lint/typecheck pipeline used by these projects is a Bun-runtime pipeline. The relevant scripts in a typical bunpress-style package.json:
{
"scripts": {
"build:bunpress": "cd packages/bunpress && bun run build",
"build": "bun --bun build.ts",
"typecheck": "bun --bun tsc --noEmit",
"lint": "bunx --bun pickier .",
"test": "bun test"
}
}Every one of these invocations is Bun loading a JS/TS file as a Bun program. Inside build.ts you typically see things like:
import { dts } from 'bun-plugin-dtsx' // ← Bun build plugin
import { stx } from 'bun-plugin-stx' // ← Bun build plugin
import { CLI } from '@stacksjs/clapp'
await Bun.build({
entrypoints: ['./src/index.ts'],
plugins: [dts(), stx()],
})The plugins themselves (bun-plugin-dtsx, bun-plugin-stx, bun-plugin-tailwindcss, etc.) are defined as Bun plugins — they exist solely to extend the Bun runtime/build system, are loaded by Bun's module resolver, and are registered via Bun.build({ plugins }) which calls back into Bun internals. They have no meaning outside Bun. Same goes for the broader toolchain: bunx --bun pickier, bun test, bun --bun tsc, bun packages/bunpress/bin/cli.ts dev — all Bun-runtime invocations resolving npm packages and binaries through Bun's module resolution.
So when pantry install puts bun-plugin-dtsx in pantry/bun-plugin-dtsx/ instead of node_modules/bun-plugin-dtsx/, the failure cascades through the entire downstream pipeline:
bun --bun build.ts→Could not resolve: "bun-plugin-dtsx"→ build failsbun --bun tsc --noEmit→Script not found "tsc"(nonode_modules/typescript/bin/tsc) → typecheck failsbunx --bun pickier .→ has to fall back to fetching pickier from registry, or fails outright depending on cache state- any
bun run <script>whose body imports an npm pkg → fails the moment Bun hits the import
There is no Bun-side knob that can fix this. bunfig.toml's linker only controls Bun's own installer layout (hoisted vs isolated under node_modules/); it does not point Bun's resolver at pantry/. Bun's plugin API does not accept a custom resolution root either.
Reproduction
A standard Bun project (e.g. stacksjs/bunpress) with:
- a
package.jsonthat declares Bun-runtime npm deps (e.g.bun-plugin-dtsx,bun-plugin-stx,bunfig,@stacksjs/clapp,ts-syntax-highlighter,typescript) - a
bunfig.tomlwithlinker = "hoisted" - a
build.tsthat callsBun.build({ plugins: [dts(), stx()] })
Steps:
rm -rf node_modules/pantry install— completes successfully, populatespantry/<name>/for every dep, nonode_modules/is createdpantry run build(orbun run build, orbun --bun build.ts)
Observed:
error: Could not resolve: "bun-plugin-dtsx". Maybe you need to "bun install"?
at .../build.ts
error: Could not resolve: "@stacksjs/clapp". ...
error: Could not resolve: "ts-syntax-highlighter". ...
error: Could not resolve: "bunfig". ...The same pattern hits typecheck (bun --bun tsc --noEmit → Script not found "tsc" because node_modules/typescript/bin/tsc doesn't exist) and any bunx/bun --bun invocation that resolves an npm binary.
Why it happens
- Pantry's workspace install places each dep at
<workspace_root>/<modules_dir>/<name>/wheremodules_dirdefaults topantry. - Bun's resolver does not consult
pantry/— it only looks atnode_modules/(and ancestornode_modules/directories per Node-style resolution). - Bun's plugin loader uses the same resolver, so Bun-build plugins (
bun-plugin-*) are subject to the same lookup. bunfig.toml'slinkeroption controls how Bun installs (hoisted vs isolated layout undernode_modules/); it does not redirect Bun's resolver to a different directory.- So packages that are 100% present on disk under
pantry/are unresolvable from Bun's perspective. There is no Bun-side configuration that closes the gap.
Impact
This affects every project that:
- has a
package.jsonwith npm deps, and - runs anything via
bun build,bun --bun <bin>,bunx,bun test, orbun run <script>where the script (or anything it imports) reaches for an npm package
…which is essentially every Bun/TypeScript project today, and especially every project whose build is itself a Bun program loading Bun plugins. Migrating CI from bun install to pantry install produces a broken build with no diagnostic from pantry — the install step succeeds silently, and the failure surfaces later in whichever step first imports a JS dep.
Visible in CI for repos that already attempted the migration:
- stacksjs/bunpress — typecheck, build, and test jobs all fail post-
pantry install(build.tsimportsbun-plugin-dtsxandbun-plugin-stx) - previously reported as the symptom of #200, but the underlying cause is the layout mismatch, not a missing install step
Notes
- Filing this as the architectural problem behind #200. #200 frames it as "pantry install should also run bun install," but that's one possible response — the deeper question is what
pantry install's on-disk contract is for projects whose runtime (Bun) only knows how to resolve fromnode_modules/, and whose build pipeline runs entirely on top of that runtime via Bun plugins. - Not proposing a fix in this issue; the right answer (mirror into
node_modules/, install intonode_modules/directly when apackage.jsonis present, symlink scheme, etc.) is a design call.