Skip to content

pantry install + bun run build is incompatible: deps land in pantry/ but bun resolves only from node_modules/ #201

Description

@glennmichael123

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.tsCould not resolve: "bun-plugin-dtsx" → build fails
  • bun --bun tsc --noEmitScript not found "tsc" (no node_modules/typescript/bin/tsc) → typecheck fails
  • bunx --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.json that declares Bun-runtime npm deps (e.g. bun-plugin-dtsx, bun-plugin-stx, bunfig, @stacksjs/clapp, ts-syntax-highlighter, typescript)
  • a bunfig.toml with linker = "hoisted"
  • a build.ts that calls Bun.build({ plugins: [dts(), stx()] })

Steps:

  1. rm -rf node_modules/
  2. pantry install — completes successfully, populates pantry/<name>/ for every dep, no node_modules/ is created
  3. pantry run build (or bun run build, or bun --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 --noEmitScript 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>/ where modules_dir defaults to pantry.
  • Bun's resolver does not consult pantry/ — it only looks at node_modules/ (and ancestor node_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's linker option controls how Bun installs (hoisted vs isolated layout under node_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.json with npm deps, and
  • runs anything via bun build, bun --bun <bin>, bunx, bun test, or bun 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:

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions