Bun package management, lockfile policy, workspaces, CI integration, and Bun-native tooling
Install to Claude Code
npx -y skills add AppVerk/av-marketplace --skill bun-package-manager --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Bun Package Manager?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/appverk-bun-package-manager)More formats (shields.io, HTML) on the badges page.
---
name: bun-package-manager
description: Bun package management, lockfile policy, workspaces, CI integration, and Bun-native tooling
---
# Bun Package Manager
## Overview
Bun best practices for frontend projects:
- Bun commands (add, remove, update, run, x)
- Lockfile management (`bun.lock` text vs `bun.lockb` binary)
- `bunfig.toml` configuration
- Workspace support (monorepos)
- CI/CD integration
- Troubleshooting common issues
- Bun-native tooling primer (`bun test`, `bun build`, Bun runtime)
---
## Hard Rules
<HARD-RULES>
These rules are NON-NEGOTIABLE. Violating any of them is a bug.
- ALWAYS use `bun` for all package operations — NEVER `npm`, `yarn`, or `pnpm` in Bun projects
- ALWAYS use `bun run <script>` (or implicit `bun <script>`) — NEVER `npm run`/`yarn`/`pnpm run`
- ALWAYS commit exactly one Bun lockfile (`bun.lock` preferred, `bun.lockb` for legacy projects) — the lockfile MUST be in version control and the two formats MUST NOT coexist
- PREFER `bun.lock` (text) over `bun.lockb` (binary) — Bun 1.2+ default, diff-friendly in PRs; migrate via `bun install --save-text-lockfile` when feasible
- ALWAYS use `bunx` instead of `npx` for one-off package execution
- ALWAYS use `--frozen-lockfile` in CI — NEVER allow lockfile modifications in CI
- NEVER delete the lockfile to "fix" issues — resolve the underlying problem
- NEVER mix package managers — if `pnpm-lock.yaml` or `package-lock.json` exists, use that manager
- ALWAYS check for existing lockfile before running `bun install` in a new project
</HARD-RULES>
---
## Detecting Bun Projects
Before using Bun commands, verify the project uses Bun:
```bash
# Check for Bun lock file (text preferred, binary legacy)
ls bun.lock bun.lockb 2>/dev/null
# Check for Bun configuration
ls bunfig.toml 2>/dev/null
# Verify Bun is available
bun --version
```
**Lockfile detection priority:**
| Found | Manager |
|---|---|
| `bun.lock` or `bun.lockb` | Bun |
| `pnpm-lock.yaml` | pnpm |
| `package-lock.json` | npm |
| `yarn.lock` | yarn |
**If two or more lockfiles are present, flag this as an anti-pattern and ask the user which manager to keep.** Never mix package managers.
---
## Essential Commands
### Installing Dependencies
```bash
# Install all dependencies from lock file
bun install
# Install with frozen lock file (CI)
bun install --frozen-lockfile
# Install only production dependencies
bun install --production
# Install and save lockfile as text (migration from bun.lockb)
bun install --save-text-lockfile
```
### Adding Dependencies
```bash
# Add a runtime dependency
bun add react
# Add a dev dependency
bun add -d vitest @testing-library/react
# Add a specific version
bun add react@18.3.1
# Add to a specific workspace package (cd into the package)
cd packages/utils && bun add lodash
```
### Removing Dependencies
```bash
# Remove a dependency
bun remove lodash
# Remove from a specific workspace package (cd into the package)
cd packages/utils && bun remove lodash
```
### Updating Dependencies
```bash
# Update all dependencies within semver range
bun update
# Update a specific package
bun update react
# Update to latest version (ignore semver range)
bun update react --latest
# Check what would change without writing
bun update --dry-run
# List outdated dependencies
bun outdated
```
### Running Scripts
```bash
# Run a script from package.json
bun run dev
bun run build
bun run test
bun run lint
# Implicit shortcut (works for any package.json script)
bun dev # same as bun run dev
bun start # same as bun run start
```
> **Note:** `bun test` runs Bun's **built-in test runner** (NOT the `test` script in `package.json`). To run the `test` script use `bun run test`. See the "Bun-native Tooling" section below.
### One-Off Execution (bunx)
```bash
# ✅ GOOD: Use bunx instead of npx
bunx create-vite my-app --template react-ts
bunx shadcn@latest add button
bunx tsc --noEmit
# ❌ BAD: Using npx in a Bun project
npx create-vite my-app # WRONG — use bunx
```
---
## Package.json Scripts Template
### Standard Frontend Scripts
```json
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run --coverage",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"typecheck": "tsc --noEmit",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
```
### Alternative with Biome
```json
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"test": "vitest run --coverage",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"lint": "biome check --fix .",
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format ."
}
}
```
Invoke with `bun run <script>` (or the implicit `bun <script>` shortcut for unambiguous names).
---
## Lockfile Management
Bun supports two lockfile formats:
| Format | File | Status | Use case |
|---|---|---|---|
| Text | `bun.lock` | **Preferred** (Bun 1.2+ default) | New projects, all PR-reviewable codebases |
| Binary | `bun.lockb` | Legacy (Bun 1.0–1.1 default) | Existing projects pre-migration |
> **Note on defaults:** Bun 1.2+ writes text-form `bun.lock` by default at the CLI level, but the `bunfig.toml` flag `[install].saveTextLockfile` defaults to `false`. New projects created with `bun init` on Bun 1.2+ get `bun.lock`; if you don't see it, run `bun install --save-text-lockfile` once or set `saveTextLockfile = true` in `bunfig.toml` to make the behaviour explicit.
### Why prefer `bun.lock` (text)?
- Reviewable in PRs — diffs are human-readable
- Conflict-resolvable — standard text merge tools work
- Tooling-friendly — scrapers, linters, and dependency auditors can read it
### Migrating from `bun.lockb` → `bun.lock`
```bash
# Write the text form on the next install
bun install --save-text-lockfile
# Remove the binary lockfile after verifying the text version
git add bun.lock
git rm bun.lockb
git commit -m "chore: migrate bun lockfile to text form"
```
After migration, all collaborators and CI must use Bun 1.2 or later (which recognises `bun.lock`).
### Coexistence rules
- **Never** have both `bun.lock` and `bun.lockb` committed simultaneously. Pick one (prefer text) and delete the other.
- **Never** have any Bun lockfile and `pnpm-lock.yaml` / `package-lock.json` / `yarn.lock` committed simultaneously.
---
## `bunfig.toml` Configuration
`bunfig.toml` is Bun's TOML-format configuration file (analog of `.npmrc`).
### Recommended settings for frontend projects
```toml
# bunfig.toml
[install]
saveTextLockfile = true # Use bun.lock (text), not bun.lockb (binary)
exact = false # Use semver ranges in package.json
[install.cache]
dir = "~/.bun/install/cache"
```
### Private registries / scoped tokens (analog of `.npmrc` auth)
```toml
[install.scopes]
"@my-private-scope" = { token = "$BUN_AUTH_TOKEN", url = "https://npm.my-company.com" }
```
### What each setting does
| Setting | Value | Purpose |
|---|---|---|
| `install.saveTextLockfile` | `true` | Use text-form `bun.lock` for PR-friendly diffs |
| `install.exact` | `false` | Allow semver range updates (`^x.y.z`) |
---
## Workspace Support (Monorepos)
### Workspace Configuration
Bun uses the standard `workspaces` field in the **root** `package.json` (npm-style; no separate workspace file like `pnpm-workspace.yaml`).
```json
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
```
### Directory Structure
```
my-monorepo/
package.json # Root — workspaces + shared dev deps
bun.lock
apps/
web/ # @myapp/web
package.json
admin/ # @myapp/admin
package.json
packages/
ui/ # @myapp/ui
package.json
utils/ # @myapp/utils
package.json
```
### Workspace Commands
```bash
# Run a script in a specific workspace
bun run --filter @myapp/web build
# Run a script across all workspaces
bun run --filter '*' build
# Add a dependency to a specific workspace (cd into the package)
cd packages/utils && bun add lodash
# Add a dev dependency to the workspace root
bun add -d typescript
```
### Cross-Package Dependencies
```json
// apps/web/package.json
{
"dependencies": {
"@myapp/ui": "workspace:*",
"@myapp/utils": "workspace:*"
}
}
```
**`workspace:*`** — Always resolves to the local workspace version. When publishing, replace with the actual version.
---
## CI/CD Integration
### GitHub Actions with Bun
```yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.0 # or read from .bun-version / package.json packageManager
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type check
run: bun run typecheck
- name: Lint
run: bun run lint:check
- name: Test
run: bun run test
- name: Build
run: bun run build
```
> Note: `bun-version: latest` exists but is discouraged — it produces non-reproducible CI runs because an unannounced Bun release can be picked up silently, and `--frozen-lockfile` gives false reproducibility confidence when the runtime itself is unpinned. Always pin (see Key CI Principle #2 below).
### Key CI Principles
1. **`--frozen-lockfile`** — Lockfile must not change in CI. If it would, the build fails.
2. **Pin Bun version** — Either via `bun-version:` input or a `.bun-version` file at repo root.
3. **Pipeline order:** typecheck → lint → test → build (fail fast on cheapest checks first).
---
## Troubleshooting
### Lockfile drift between `bun.lock` and `bun.lockb`
**Problem:** Project has both files committed (mixed state during migration).
**Fix:** Pick one (prefer `bun.lock`), delete the other.
```bash
bun install --save-text-lockfile
git add bun.lock
git rm bun.lockb
```
### Peer Dependency Conflicts
**Problem:** `bun install` warns about peer dependency conflicts.
```bash
# Inspect what's conflicting
bun install 2>&1 | grep -i "peer"
# Option 1: Update the conflicting package
bun update conflicting-package
# Option 2: Pin a compatible version in package.json
```
For Bun, dependency overrides are configured via the **top-level** `overrides` field in `package.json` (npm-style). Bun also recognises the Yarn-style top-level `resolutions` field as a fallback for projects migrating from Yarn:
```json
{
"overrides": {
"react": "^18.3.0"
}
}
```
### Stale Lock File
```bash
# Regenerate lockfile from package.json
bun install
# Verify changes
git diff bun.lock
# Commit
git add bun.lock
```
**Never delete the lockfile** to "fix" issues — it contains resolved versions for reproducible builds.
### Cache Issues
```bash
# Clear the install cache
bun pm cache rm
# Nuclear option — clear everything and reinstall
rm -rf node_modules
bun install
```
### Module Resolution Issues
```bash
# List installed packages (top level)
bun pm ls
# List the entire dependency tree
bun pm ls --all
# Inspect why a package is installed
bun pm why react
```
---
## Bun-native Tooling (Informational)
Bun ships with a built-in test runner, bundler, and JavaScript runtime. These are **separate from** the package-manager concerns above.
**Hard rule:** Do NOT migrate existing projects from Vitest/Vite to Bun-native tooling as part of unrelated work. Only adopt Bun-native tools if the project already uses them (detected via `bunfig.toml` `[test]` section, `bun test` in `package.json` scripts, `bun build` in `package.json` scripts, or explicit user request).
### `bun test`
Detect: the project uses `bun test` if either of these is true:
- `bunfig.toml` has a `[test]` section, OR
- `package.json` `scripts.test` runs `bun test` (not `vitest`)
When the project uses `bun test`:
```bash
bun test # Run all tests
bun test --watch # Watch mode
bun test --coverage # With coverage
bun test path/to/file.test.ts # Specific file
```
Test files: `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx` (configurable in `bunfig.toml`).
When the project uses Vitest, **do NOT switch to `bun test`**. Use `bun run test` (which executes the Vitest script).
### `bun build`
Detect: the project uses `bun build` if `package.json` `scripts.build` runs `bun build` (not `vite build`).
When the project uses `bun build`:
```bash
bun build ./src/index.ts --outdir ./dist
bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --outdir ./dist --target browser
```
When the project uses Vite/Webpack/Rollup, **do NOT switch to `bun build`**. Use `bun run build`.
### Bun as runtime
Bun can run TypeScript and JSX natively without Node.js. Adopt only if:
- `package.json` `scripts` already use `bun ./script.ts` directly, OR
- The user explicitly asks for runtime adoption.
Common runtime flags:
```bash
bun --hot ./server.ts # Hot reload server
bun --bun next dev # Run Next.js via Bun runtime (not Node)
bun --bun vite # Run Vite via Bun runtime
```
Adopting Bun runtime affects dependency compatibility — some packages assume Node APIs not present in Bun. Verify by running the dev server before committing the switch.
---
## Common Mistakes
### ❌ Using npm/yarn/pnpm in a Bun Project
```bash
# WRONG: Mixing package managers
npm install lodash # Creates package-lock.json — conflicts!
yarn add lodash # Creates yarn.lock — conflicts!
pnpm add lodash # Creates pnpm-lock.yaml — conflicts!
# CORRECT: Always use bun
bun add lodash
```
### ❌ Running `bun test` when the project uses Vitest
```bash
# WRONG: Skips the configured test runner
bun test # Runs Bun's built-in test runner
# CORRECT: Use bun run, which executes the package.json script
bun run test # Runs vitest (or whatever scripts.test specifies)
```
### ❌ Deleting Lock File
```bash
# WRONG: "Fix" by deleting lock file
rm bun.lock
bun install # Generates new lock with potentially different versions
# CORRECT: Fix the actual issue
bun install # Usually resolves conflicts
bun update affected-package
```
### ❌ Missing `--frozen-lockfile` in CI
```yaml
# WRONG: Allows lock file changes in CI
- run: bun install
# CORRECT: Fails if lock file would change
- run: bun install --frozen-lockfile
```
### ❌ Using `npx` Instead of `bunx`
```bash
# WRONG: npx in a Bun project
npx create-vite my-app
# CORRECT: bunx
bunx create-vite my-app
```
---
## Summary
1. ✅ `bun` for all package operations — never npm/yarn/pnpm in Bun projects
2. ✅ `bun run` for scripts, `bunx` instead of `npx`
3. ✅ Prefer `bun.lock` (text) over `bun.lockb` (binary); always commit lockfile
4. ✅ `--frozen-lockfile` in CI
5. ✅ `bunfig.toml` with `saveTextLockfile = true`
6. ✅ `oven-sh/setup-bun@v2` for GitHub Actions
7. ✅ `workspace:*` for monorepo cross-dependencies (no separate workspace file needed)
8. ✅ Do NOT migrate from Vitest/Vite to `bun test`/`bun build` as unrelated work
Scanned 9/1/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!