Open-Source Wikis

/

Bun

/

Apps

/

Package manager

oven-sh/bun

Package manager

Active contributors: Jarred Sumner, Dylan Conway, Alistair Smith

bun install, bun add, bun remove, bun update, bun pm, bun outdated, bun audit, bun publish, bun pack, bun why, bun link/unlink, bun patch and bun patch-commit all share one implementation in src/install/. The package manager is a Zig-only subsystem — it does not start a JS VM unless lifecycle scripts run.

Purpose

A drop-in replacement for npm/yarn/pnpm that:

  • Resolves dependency graphs from package.json and writes a binary or text lockfile (bun.lock).
  • Downloads tarballs concurrently from the npm registry (or git, GitHub, tarball URLs, workspace, file paths).
  • Materializes node_modules/ either flat (npm-compatible) or symlinked (pnpm-style isolated install).
  • Runs lifecycle scripts (preinstall, install, postinstall, prepublish, ...) gated by a trusted-dependencies allowlist.
  • Patches packages via bun patch and applies the patches on subsequent installs.

Directory layout

src/install/
├── PackageManager.zig          # the orchestrator, ~55 KB
├── PackageInstall.zig          # extract a single tarball into node_modules
├── PackageInstaller.zig        # ~73 KB — drives many PackageInstalls in parallel
├── PackageManagerTask.zig      # work queue items
├── PackageManagerCommand/      # bun pm subcommands (ls, hash, migrate, ...)
├── PackageManifestMap.zig      # in-memory manifest cache
├── NetworkTask.zig             # HTTP request abstraction over HTTPThread
├── npm.zig                     # npm registry client (~133 KB)
├── lockfile.zig                # binary + text lockfile reader/writer (~82 KB)
├── lockfile/                   # versioned lockfile decoders
├── migration.zig               # convert package-lock.json / yarn.lock → bun.lock
├── hoisted_install.zig         # npm-style flat node_modules
├── isolated_install.zig        # pnpm-style symlinked install (~98 KB)
├── isolated_install/           # additional isolated-install helpers
├── lifecycle_script_runner.zig # spawn pre/post scripts
├── extract_tarball.zig         # libarchive wrapper
├── TarballStream.zig           # streaming tarball decoder
├── repository.zig              # git repository handling
├── hosted_git_info.zig         # github:/gitlab:/bitbucket: shorthand parser
├── pnpm.zig                    # parse pnpm-lock.yaml for migration
├── yarn.zig                    # parse yarn.lock (v1 + v2)
├── patch.zig (in src/)         # patch-file format
├── patch_install.zig           # apply patches on install
├── postinstall_optimizer.zig   # skip useless prebuilds
├── default-trusted-dependencies.txt  # default allowlist
├── PnpmMatcher.zig             # version range matching
├── audit_command.zig (in cli/) # bun audit
├── outdated_command.zig (..)   # bun outdated
├── pm_view_command.zig (..)    # bun pm view (npm view)
├── update_interactive_command.zig (..)  # bun update --interactive
├── publish_command.zig (..)    # bun publish
└── pack_command.zig (..)       # bun pack

The src/cli/*_command.zig files are thin wrappers that parse argv and call into PackageManager.zig.

Key abstractions

Type File Purpose
PackageManager src/install/PackageManager.zig The root struct. Owns the resolved graph, the lockfile, the network task queue, the install queue, and progress reporting.
Lockfile src/install/lockfile.zig The deterministic dependency graph. Two on-disk formats: bun.lockb (binary, legacy) and bun.lock (text v1, default).
Resolution src/install/resolution.zig A tagged union for "where does this package come from": npm registry, git URL, github shorthand, workspace, file path, tarball, ...
Dependency src/install/dependency.zig A constraint from a package.json (^1.2.3, git+https://..., workspace:*, npm:foo@1).
PackageInstall src/install/PackageInstall.zig One tarball → one folder in node_modules. Handles extraction, file moves, and symlink layout.
PackageInstaller src/install/PackageInstaller.zig The fan-out controller that drives many PackageInstalls on the thread pool.
NetworkTask src/install/NetworkTask.zig An async HTTP request (manifest fetch, tarball download). Submitted to HTTPThread.zig.
Npm.PackageManifest src/install/npm.zig Parsed npm view-like manifest. Cached on disk under ~/.bun/install/cache/.
LifecycleScriptSubprocess src/install/lifecycle_script_runner.zig Spawns bun run <script> in the package directory.

How bun install works

sequenceDiagram
    participant CLI as src/cli/install_command.zig
    participant PM as PackageManager.zig
    participant Lock as Lockfile
    participant Res as Resolver loop
    participant HTTP as HTTPThread
    participant FS as PackageInstaller

    CLI->>PM: PackageManager.init
    PM->>Lock: load bun.lock (or migrate from npm/yarn/pnpm)
    PM->>Res: enqueue root manifest
    loop until graph stable
        Res->>HTTP: GET manifest tarball
        HTTP-->>Res: bytes
        Res->>Res: parse, expand transitive deps
        Res->>Lock: insert
    end
    PM->>Lock: write bun.lock(b)
    PM->>FS: install (hoisted_install or isolated_install)
    FS->>HTTP: download tarballs in parallel
    FS->>FS: extract via libarchive
    FS->>FS: link bins, write .modules.yaml (isolated)
    PM->>PM: run lifecycle scripts (filtered by trusted-deps)
    PM-->>CLI: exit

The graph resolver runs lockstep with the network: each manifest fetch returns a list of dependencies; each new dependency may need its own manifest. The same HTTPThread (src/http/HTTPThread.zig) used by fetch() powers these requests, so connection pooling and HTTP/2 multiplexing apply.

Hoisted vs isolated installs

Bun supports two node_modules/ layouts. The choice is per-project, configured in bunfig.toml:

[install]
linker = "hoisted"   # default — npm-compatible flat tree
# linker = "isolated"  # pnpm-style symlinks
Aspect Hoisted (hoisted_install.zig) Isolated (isolated_install.zig)
Layout Flat node_modules/<pkg> with peer-deps hoisted Symlinks under node_modules/<pkg>node_modules/.bun/<pkg>@<version>/node_modules/<pkg>
Phantom deps Possible Not possible — only declared deps are reachable
Disk usage Higher (duplication) Lower (content-addressed store)
Compatibility npm/yarn parity pnpm-style
File ops mkdir + cp mkdir + symlink (junctions on Windows)

The isolated layout uses a .modules.yaml file in node_modules/.bun/ to track the exact graph for incremental updates.

Lockfile

bun.lock is the new default text format (a JSON-ish, sorted, stable representation). The legacy binary bun.lockb is still supported. The reader/writer is in src/install/lockfile.zig plus per-version decoders under src/install/lockfile/.

Migration from package-lock.json, yarn.lock (v1 + v2), and pnpm-lock.yaml runs automatically the first time bun install is invoked in a project. The parsers live in:

Source format Parser
package-lock.json src/install/migration.zig
yarn.lock (v1) src/install/yarn.zig
yarn.lock (Berry) src/install/yarn.zig
pnpm-lock.yaml src/install/pnpm.zig

Network: registry and tarballs

npm.zig issues GET /<package> against the configured registry (default https://registry.npmjs.org/). Auth tokens come from .npmrc (parsed by src/install/npmrc.zig style helpers) and bunfig.toml. Per-scope registries are resolved via npmrc.

Tarballs are downloaded by NetworkTask, decoded streamingly by TarballStream.zig (using vendored libarchive), and extracted into the global cache under ~/.bun/install/cache/<pkg>@<version>/. The cache is shared across projects.

integrity.zig enforces the integrity field from the lockfile (sha512 by default) against the downloaded bytes.

Lifecycle scripts

After install, lifecycle_script_runner.zig walks the install order and executes preinstall / install / postinstall / prepublish / preprepare / prepare for each package. Scripts are gated:

  • A package whose name is in default-trusted-dependencies.txt (or in trustedDependencies in the user's package.json) runs scripts unconditionally.
  • All others are skipped with a "Blocked: did not run install script for X" notice. bun pm trust <pkg> adds an entry to the user's package.json.

postinstall_optimizer.zig recognises common prebuild scripts (e.g. node-gyp rebuild) and skips them when a prebuilt binary already shipped in the tarball.

Patches

bun patch <pkg> extracts a fresh copy of the package into node_modules/.bun-patches/<pkg>/ and prints the path. After editing, bun patch-commit <path> produces a unified diff and saves it as patches/<pkg>@<version>.patch. On subsequent installs, patch_install.zig reads patches/ and applies them after extraction. The set of applied patches is recorded in the lockfile so downstream consumers re-apply them too.

Workspaces and filters

A workspace package is matched by workspace:* / workspace:^ / workspace:~ in the dependent's package.json. The resolver short-circuits to the local path. Workspace globs (e.g. "workspaces": ["packages/*"]) are expanded by walking the file system once at startup.

bun --filter <pattern> <cmd> runs <cmd> across matching workspaces. Implementation: src/cli/filter_arg.zig + src/cli/filter_run.zig + src/cli/multi_run.zig.

Other commands

Command Implementation
bun pm ls / pack / bin / cache / migrate / whoami src/cli/package_manager_command.zig + src/cli/pm_pkg_command.zig
bun pm trust / untrust src/cli/pm_trusted_command.zig
bun pm version src/cli/pm_version_command.zig
bun pm view (npm view) src/cli/pm_view_command.zig
bun pm why src/cli/pm_why_command.zig and src/cli/why_command.zig
bun outdated src/cli/outdated_command.zig
bun update --interactive src/cli/update_interactive_command.zig
bun audit src/cli/audit_command.zig
bun publish src/cli/publish_command.zig
bun pack src/cli/pack_command.zig
bun link / unlink src/cli/link_command.zig / src/cli/unlink_command.zig
bun patch / patch-commit src/cli/patch_command.zig / src/cli/patch_commit_command.zig

Integration points

  • HTTP — All registry traffic goes through src/http/HTTPThread.zig. See HTTP stack.
  • JSON parsersrc/json_parser.zig reads every package.json.
  • Resolversrc/install/resolution.zig for dependency resolution; src/resolver/resolver.zig separately handles module resolution at runtime.
  • bunxbunx calls into PackageManager to install packages on demand. See bunx.
  • Test runnerbun test does not invoke the package manager except via bun install --filter.

Entry points for modification

  • To change install layout, edit hoisted_install.zig or isolated_install.zig. Both produce PackageInstall jobs that the same PackageInstaller consumes.
  • To add a new lockfile format, look at src/install/lockfile/ for the versioned decoders. Tag a new version in lockfile.zig and write the upgrade path in migration.zig.
  • To support a new dependency source (e.g. a custom protocol), extend the Resolution union in resolution.zig and the matching parser in dependency.zig.
  • To gate a new pre/post script behaviour, edit lifecycle_script_runner.zig and default-trusted-dependencies.txt.

Key source files

File Purpose
src/install/PackageManager.zig Orchestrator.
src/install/lockfile.zig bun.lock(b) read/write.
src/install/npm.zig Registry client + manifest cache.
src/install/PackageInstaller.zig Parallel install driver.
src/install/hoisted_install.zig npm-compatible install layout.
src/install/isolated_install.zig pnpm-style install layout.
src/install/lifecycle_script_runner.zig pre/post scripts.
src/install/migration.zig Migrate npm/yarn lockfiles.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Package manager – Bun wiki | Factory