get-tbd 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -28
- package/dist/bin.mjs +410 -170
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +202 -94
- package/dist/cli.mjs.map +1 -1
- package/dist/docs/README.md +47 -28
- package/dist/docs/SKILL.md +61 -18
- package/dist/docs/guidelines/bun-monorepo-patterns.md +2096 -0
- package/dist/docs/guidelines/cli-agent-skill-patterns.md +79 -5
- package/dist/docs/guidelines/error-handling-rules.md +66 -0
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +2868 -0
- package/dist/docs/guidelines/release-notes-guidelines.md +140 -0
- package/dist/docs/guidelines/{sync-troubleshooting.md → tbd-sync-troubleshooting.md} +1 -1
- package/dist/docs/guidelines/typescript-sorting-patterns.md +234 -0
- package/dist/docs/guidelines/typescript-yaml-handling-rules.md +195 -0
- package/dist/docs/install/claude-header.md +13 -6
- package/dist/docs/shortcuts/standard/agent-handoff.md +1 -0
- package/dist/docs/shortcuts/standard/checkout-third-party-repo.md +50 -0
- package/dist/docs/shortcuts/standard/{cleanup-all.md → code-cleanup-all.md} +3 -2
- package/dist/docs/shortcuts/standard/{cleanup-update-docstrings.md → code-cleanup-docstrings.md} +1 -0
- package/dist/docs/shortcuts/standard/{cleanup-remove-trivial-tests.md → code-cleanup-tests.md} +1 -0
- package/dist/docs/shortcuts/standard/{commit-code.md → code-review-and-commit.md} +1 -0
- package/dist/docs/shortcuts/standard/coding-spike.md +54 -0
- package/dist/docs/shortcuts/standard/create-or-update-pr-simple.md +1 -0
- package/dist/docs/shortcuts/standard/create-or-update-pr-with-validation-plan.md +1 -0
- package/dist/docs/shortcuts/standard/implement-beads.md +1 -0
- package/dist/docs/shortcuts/standard/merge-upstream.md +1 -0
- package/dist/docs/shortcuts/standard/new-architecture-doc.md +1 -0
- package/dist/docs/shortcuts/standard/new-guideline.md +8 -0
- package/dist/docs/shortcuts/standard/new-plan-spec.md +1 -0
- package/dist/docs/shortcuts/standard/new-research-brief.md +1 -0
- package/dist/docs/shortcuts/standard/new-shortcut.md +27 -1
- package/dist/docs/shortcuts/standard/new-validation-plan.md +1 -0
- package/dist/docs/shortcuts/standard/plan-implementation-with-beads.md +1 -0
- package/dist/docs/shortcuts/standard/precommit-process.md +1 -0
- package/dist/docs/shortcuts/standard/review-code-python.md +1 -0
- package/dist/docs/shortcuts/standard/review-code-typescript.md +1 -0
- package/dist/docs/shortcuts/standard/review-code.md +1 -0
- package/dist/docs/shortcuts/standard/review-github-pr.md +89 -17
- package/dist/docs/shortcuts/standard/revise-all-architecture-docs.md +1 -0
- package/dist/docs/shortcuts/standard/revise-architecture-doc.md +1 -0
- package/dist/docs/shortcuts/standard/setup-github-cli.md +1 -0
- package/dist/docs/shortcuts/standard/sync-failure-recovery.md +6 -53
- package/dist/docs/shortcuts/standard/update-specs-status.md +1 -0
- package/dist/docs/shortcuts/standard/welcome-user.md +2 -1
- package/dist/docs/shortcuts/system/skill-brief.md +1 -1
- package/dist/docs/shortcuts/system/skill.md +48 -12
- package/dist/docs/skill-brief.md +1 -1
- package/dist/docs/tbd-design.md +13 -1
- package/dist/index.d.mts +20 -6
- package/dist/index.mjs +2 -2
- package/dist/{src-BfhjLZXE.mjs → src-Ct16P2Ox.mjs} +154 -22
- package/dist/src-Ct16P2Ox.mjs.map +1 -0
- package/dist/tbd +410 -170
- package/package.json +1 -1
- package/dist/docs/guidelines/typescript-monorepo-patterns.md +0 -72
- package/dist/src-BfhjLZXE.mjs.map +0 -1
|
@@ -0,0 +1,2096 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Bun Monorepo Patterns
|
|
3
|
+
description: Modern patterns for Bun-based TypeScript monorepo architecture
|
|
4
|
+
author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
5
|
+
---
|
|
6
|
+
# Bun Monorepo Patterns
|
|
7
|
+
|
|
8
|
+
**Last Updated**: 2026-02-02
|
|
9
|
+
|
|
10
|
+
**Related**:
|
|
11
|
+
|
|
12
|
+
- [Bun Workspaces Documentation](https://bun.sh/docs/install/workspaces)
|
|
13
|
+
- [Bunup Documentation](https://bunup.dev/)
|
|
14
|
+
- [Changesets Documentation](https://github.com/changesets/changesets)
|
|
15
|
+
- [Biome Documentation](https://biomejs.dev/)
|
|
16
|
+
- [Companion: pnpm Monorepo Patterns](./pnpm-monorepo-patterns.md)
|
|
17
|
+
|
|
18
|
+
* * *
|
|
19
|
+
|
|
20
|
+
## Updating This Document
|
|
21
|
+
|
|
22
|
+
### Last Researched Versions
|
|
23
|
+
|
|
24
|
+
| Tool / Package | Version | Check For Updates |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| **Bun** | 1.3.8 | [bun.sh/blog](https://bun.sh/blog) — Runtime, bundler, package manager, test runner. Acquired by Anthropic (Dec 2025). |
|
|
27
|
+
| **TypeScript** | ^5.9.3 | [github.com/microsoft/TypeScript/releases](https://github.com/microsoft/TypeScript/releases) — 5.9.3 stable. TS 6.0 is "bridge" release; TS 7.0 (Go rewrite) in VS 2026 Insiders preview. |
|
|
28
|
+
| **Bunup** | ^0.16.0 | [npmjs.com/package/bunup](https://www.npmjs.com/package/bunup) — Build tool for TS libs. Rapid iteration (0.16.20 latest). |
|
|
29
|
+
| **Biome** | ^2.3.0 | [biomejs.dev](https://biomejs.dev/) — Formatter + linter. v2.0 added plugins and type-aware linting; 2.3.x is latest stable. |
|
|
30
|
+
| **@changesets/cli** | ^2.29.0 | [github.com/changesets/changesets/releases](https://github.com/changesets/changesets/releases) — 2.29.8 latest. No native Bun support yet. |
|
|
31
|
+
| **publint** | ^0.3.17 | [npmjs.com/package/publint](https://www.npmjs.com/package/publint) — 0.3.17 latest |
|
|
32
|
+
| **actions/checkout** | v6 | [github.com/actions/checkout/releases](https://github.com/actions/checkout/releases) |
|
|
33
|
+
| **oven-sh/setup-bun** | v2 | [github.com/oven-sh/setup-bun](https://github.com/oven-sh/setup-bun) — Verified on GitHub Marketplace |
|
|
34
|
+
| **lefthook** | ^2.0.15 | [github.com/evilmartians/lefthook/releases](https://github.com/evilmartians/lefthook/releases) — 2.0.15 latest |
|
|
35
|
+
| **npm-check-updates** | ^19.0.0 | [npmjs.com/package/npm-check-updates](https://www.npmjs.com/package/npm-check-updates) |
|
|
36
|
+
|
|
37
|
+
### Reminders When Updating
|
|
38
|
+
|
|
39
|
+
1. **Check each version** in the table above using the linked release pages
|
|
40
|
+
|
|
41
|
+
2. **Update the table** with new versions and any relevant notes
|
|
42
|
+
|
|
43
|
+
3. **Search and update code examples** — version numbers appear in:
|
|
44
|
+
|
|
45
|
+
- GitHub Actions workflows (CI and Release sections)
|
|
46
|
+
|
|
47
|
+
- `bunup.config.ts` examples
|
|
48
|
+
|
|
49
|
+
- `tsconfig.base.json` examples
|
|
50
|
+
|
|
51
|
+
- `package.json` examples (`devDependencies`)
|
|
52
|
+
|
|
53
|
+
- Appendices (complete examples)
|
|
54
|
+
|
|
55
|
+
4. **Verify compatibility** — check that tools still work together
|
|
56
|
+
|
|
57
|
+
5. **Update the “Last Updated” date** at the top of the document
|
|
58
|
+
|
|
59
|
+
6. **Review “Open Research Questions”** section for any resolved items
|
|
60
|
+
|
|
61
|
+
* * *
|
|
62
|
+
|
|
63
|
+
## Executive Summary
|
|
64
|
+
|
|
65
|
+
This research brief provides a comprehensive guide for setting up a modern TypeScript
|
|
66
|
+
monorepo using the **Bun ecosystem** end-to-end — Bun as runtime, package manager,
|
|
67
|
+
bundler (via Bunup), and test runner.
|
|
68
|
+
It serves as a direct comparison to the companion document on pnpm-based monorepos,
|
|
69
|
+
covering the same architectural scope but using Bun-native tooling wherever possible.
|
|
70
|
+
|
|
71
|
+
The recommended stack uses **Bun workspaces** for dependency management, **Bunup** for
|
|
72
|
+
building ESM/CJS dual outputs with TypeScript declarations, **Changesets** (with Bun
|
|
73
|
+
workarounds) for versioning and release automation, **Biome** for formatting and
|
|
74
|
+
linting, **publint** for package validation, and **lefthook** for git hooks.
|
|
75
|
+
The architecture also covers Bun’s unique capability for **compiling standalone
|
|
76
|
+
executables** — a native binary distribution path unavailable in the pnpm ecosystem.
|
|
77
|
+
|
|
78
|
+
**Research Questions**:
|
|
79
|
+
|
|
80
|
+
1. Can the Bun ecosystem fully replace pnpm + Node.js + tsdown for a TypeScript
|
|
81
|
+
monorepo?
|
|
82
|
+
|
|
83
|
+
2. How does complexity compare between a full-Bun and full-pnpm monorepo setup?
|
|
84
|
+
|
|
85
|
+
3. What are the trade-offs in ecosystem maturity, tooling gaps, and CI/CD support?
|
|
86
|
+
|
|
87
|
+
4. What unique capabilities does Bun offer that pnpm-based setups cannot?
|
|
88
|
+
|
|
89
|
+
* * *
|
|
90
|
+
|
|
91
|
+
## Research Methodology
|
|
92
|
+
|
|
93
|
+
### Approach
|
|
94
|
+
|
|
95
|
+
Research was conducted through official Bun documentation, web searches for current best
|
|
96
|
+
practices (2025–2026), analysis of real-world Bun monorepo implementations, evaluation
|
|
97
|
+
of Bunup and Biome documentation, and direct comparison with the companion pnpm monorepo
|
|
98
|
+
research stock.
|
|
99
|
+
|
|
100
|
+
### Sources
|
|
101
|
+
|
|
102
|
+
- Official documentation (Bun, Bunup, Biome, TypeScript, Changesets)
|
|
103
|
+
|
|
104
|
+
- Developer blog posts and migration guides
|
|
105
|
+
|
|
106
|
+
- GitHub discussions and issue threads
|
|
107
|
+
|
|
108
|
+
- Benchmark data and comparison articles
|
|
109
|
+
|
|
110
|
+
- Real-world Bun monorepo implementations
|
|
111
|
+
|
|
112
|
+
* * *
|
|
113
|
+
|
|
114
|
+
## Research Findings
|
|
115
|
+
|
|
116
|
+
### 1. Package Manager & Workspace Structure
|
|
117
|
+
|
|
118
|
+
#### Bun Workspaces
|
|
119
|
+
|
|
120
|
+
**Status**: Recommended (with caveats)
|
|
121
|
+
|
|
122
|
+
**Details**:
|
|
123
|
+
|
|
124
|
+
- Bun provides built-in workspace support via the `workspaces` field in `package.json`
|
|
125
|
+
|
|
126
|
+
- Dramatically faster installs than pnpm, npm, or yarn (often 2–10x)
|
|
127
|
+
|
|
128
|
+
- Uses `bun.lock` (text-based JSONC lockfile, default since Bun 1.2) for diffable,
|
|
129
|
+
deterministic resolution.
|
|
130
|
+
The older binary `bun.lockb` is deprecated.
|
|
131
|
+
|
|
132
|
+
- Supports `workspace:*` protocol for inter-package references
|
|
133
|
+
|
|
134
|
+
- Does not use a content-addressable store like pnpm — installs are flat in
|
|
135
|
+
`node_modules`
|
|
136
|
+
|
|
137
|
+
- Missing some pnpm features: no `pnpm deploy`, less strict `node_modules` (phantom
|
|
138
|
+
dependencies possible)
|
|
139
|
+
|
|
140
|
+
- **Notable**: Bun was acquired by Anthropic in December 2025. Bun now powers Claude
|
|
141
|
+
Code, Claude Agent SDK, and other Anthropic AI tooling, signaling strong ongoing
|
|
142
|
+
investment and maintenance.
|
|
143
|
+
|
|
144
|
+
**Assessment**: Bun workspaces are functional and fast, but less strict than pnpm.
|
|
145
|
+
The text-based `bun.lock` format (since Bun 1.2) resolves the earlier diffability
|
|
146
|
+
concern. The lack of content-addressable storage means higher disk usage in large
|
|
147
|
+
monorepos.
|
|
148
|
+
For projects prioritizing speed over strictness, Bun workspaces are excellent.
|
|
149
|
+
For projects requiring hermetic dependency isolation, pnpm remains superior.
|
|
150
|
+
|
|
151
|
+
**Key Configuration** (root `package.json`):
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"private": true,
|
|
156
|
+
"workspaces": [
|
|
157
|
+
"packages/*",
|
|
158
|
+
"apps/*"
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Adding dependencies to specific workspaces**:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Use --cwd to target a specific workspace
|
|
167
|
+
bun add express --cwd packages/server
|
|
168
|
+
|
|
169
|
+
# Or from the workspace directory
|
|
170
|
+
cd packages/server && bun add express
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**References**:
|
|
174
|
+
|
|
175
|
+
- [Bun Workspaces](https://bun.sh/docs/install/workspaces)
|
|
176
|
+
|
|
177
|
+
- [Guide to Monorepo Setup: NPM, Yarn, Pnpm & Bun Workspaces](https://jsdev.space/mastering-monorepos/)
|
|
178
|
+
|
|
179
|
+
* * *
|
|
180
|
+
|
|
181
|
+
#### Monorepo Structure Strategy
|
|
182
|
+
|
|
183
|
+
**Status**: Recommended
|
|
184
|
+
|
|
185
|
+
**Details**:
|
|
186
|
+
|
|
187
|
+
The same “start mono, stay sane” approach from the pnpm research applies here.
|
|
188
|
+
Place packages in `packages/` from day one, even with a single package.
|
|
189
|
+
|
|
190
|
+
**Recommended Directory Structure**:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
project-root/
|
|
194
|
+
.changeset/
|
|
195
|
+
config.json
|
|
196
|
+
README.md
|
|
197
|
+
.github/
|
|
198
|
+
workflows/
|
|
199
|
+
ci.yml
|
|
200
|
+
release.yml
|
|
201
|
+
packages/
|
|
202
|
+
package-name/
|
|
203
|
+
src/
|
|
204
|
+
core/ # Future: package-name-core
|
|
205
|
+
cli/ # Future: package-name-cli
|
|
206
|
+
adapters/ # Future: package-name-adapters
|
|
207
|
+
bin.ts
|
|
208
|
+
index.ts
|
|
209
|
+
package.json
|
|
210
|
+
tsconfig.json
|
|
211
|
+
bunup.config.ts
|
|
212
|
+
biome.json
|
|
213
|
+
bun.lock
|
|
214
|
+
lefthook.yml
|
|
215
|
+
package.json
|
|
216
|
+
tsconfig.base.json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Key differences from pnpm structure**:
|
|
220
|
+
|
|
221
|
+
| File | pnpm Monorepo | Bun Monorepo |
|
|
222
|
+
| --- | --- | --- |
|
|
223
|
+
| Lockfile | `pnpm-lock.yaml` | `bun.lock` (text JSONC, diffable) |
|
|
224
|
+
| Workspace config | `pnpm-workspace.yaml` | `workspaces` in `package.json` |
|
|
225
|
+
| Package manager config | `.npmrc` | `bunfig.toml` (optional) |
|
|
226
|
+
| Lint/format config | `.prettierrc` + `eslint.config.js` | `biome.json` (single file) |
|
|
227
|
+
| Build config | `tsdown.config.ts` | `bunup.config.ts` |
|
|
228
|
+
|
|
229
|
+
**Assessment**: The directory structure is nearly identical.
|
|
230
|
+
Bun consolidates configuration into fewer files (no separate workspace config, single
|
|
231
|
+
`biome.json` instead of Prettier + ESLint configs).
|
|
232
|
+
|
|
233
|
+
* * *
|
|
234
|
+
|
|
235
|
+
### 2. TypeScript Configuration
|
|
236
|
+
|
|
237
|
+
#### Base Configuration
|
|
238
|
+
|
|
239
|
+
**Status**: Recommended
|
|
240
|
+
|
|
241
|
+
**Details**:
|
|
242
|
+
|
|
243
|
+
TypeScript configuration for Bun monorepos is nearly identical to pnpm monorepos.
|
|
244
|
+
The main difference is that Bun natively executes TypeScript, so the configuration
|
|
245
|
+
primarily serves IDE support, type checking, and declaration generation.
|
|
246
|
+
|
|
247
|
+
**`tsconfig.base.json`**:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"compilerOptions": {
|
|
252
|
+
"target": "ES2024",
|
|
253
|
+
"lib": ["ES2024"],
|
|
254
|
+
"module": "ESNext",
|
|
255
|
+
"moduleResolution": "Bundler",
|
|
256
|
+
"resolveJsonModule": true,
|
|
257
|
+
"strict": true,
|
|
258
|
+
"skipLibCheck": true,
|
|
259
|
+
"noUncheckedIndexedAccess": true,
|
|
260
|
+
"forceConsistentCasingInFileNames": true,
|
|
261
|
+
"verbatimModuleSyntax": true,
|
|
262
|
+
"isolatedDeclarations": true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Package-level `tsconfig.json`**:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"extends": "../../tsconfig.base.json",
|
|
272
|
+
"compilerOptions": {
|
|
273
|
+
"types": ["bun-types"],
|
|
274
|
+
"noEmit": true
|
|
275
|
+
},
|
|
276
|
+
"include": ["src"]
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Key difference**: `isolatedDeclarations: true` is strongly recommended for Bun
|
|
281
|
+
projects because Bunup’s DTS generation is dramatically faster when this is enabled (it
|
|
282
|
+
avoids invoking the full TypeScript compiler).
|
|
283
|
+
|
|
284
|
+
**Assessment**: Nearly identical to the pnpm setup.
|
|
285
|
+
The addition of `isolatedDeclarations` and `bun-types` are the only differences.
|
|
286
|
+
Using `moduleResolution: "Bundler"` is appropriate since Bunup handles the final output.
|
|
287
|
+
|
|
288
|
+
**References**:
|
|
289
|
+
|
|
290
|
+
- [Bun TypeScript Documentation](https://bun.sh/docs/typescript)
|
|
291
|
+
|
|
292
|
+
- [TypeScript: Choosing Compiler Options](https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html)
|
|
293
|
+
|
|
294
|
+
* * *
|
|
295
|
+
|
|
296
|
+
#### Bun-Specific TypeScript Features
|
|
297
|
+
|
|
298
|
+
**Status**: Informational
|
|
299
|
+
|
|
300
|
+
**Details**:
|
|
301
|
+
|
|
302
|
+
Bun supports a `"bun"` export condition in `package.json` that allows consumers running
|
|
303
|
+
Bun to directly import TypeScript source files, bypassing compiled output entirely:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"exports": {
|
|
308
|
+
".": {
|
|
309
|
+
"bun": "./src/index.ts",
|
|
310
|
+
"import": {
|
|
311
|
+
"types": "./dist/index.d.ts",
|
|
312
|
+
"default": "./dist/index.js"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
This means during development within the monorepo, Bun can consume TypeScript directly
|
|
320
|
+
without a build step — a significant DX advantage over Node.js-based setups.
|
|
321
|
+
|
|
322
|
+
**Assessment**: This is a unique Bun advantage.
|
|
323
|
+
During local development, packages can be consumed without building.
|
|
324
|
+
Published packages still need compiled output for Node.js consumers.
|
|
325
|
+
|
|
326
|
+
* * *
|
|
327
|
+
|
|
328
|
+
### 3. Build Tooling
|
|
329
|
+
|
|
330
|
+
#### Bunup
|
|
331
|
+
|
|
332
|
+
**Status**: Strongly Recommended
|
|
333
|
+
|
|
334
|
+
**Details**:
|
|
335
|
+
|
|
336
|
+
Bunup is the modern build tool for TypeScript libraries, powered by Bun’s native
|
|
337
|
+
bundler. It is the Bun-ecosystem analog to tsdown in the pnpm ecosystem.
|
|
338
|
+
|
|
339
|
+
Key advantages:
|
|
340
|
+
|
|
341
|
+
- **Extremely fast**: ~37ms builds vs multi-second builds with tsdown/tsup
|
|
342
|
+
|
|
343
|
+
- **Dual format output**: Generates both ESM (`.js`) and CJS (`.cjs`)
|
|
344
|
+
|
|
345
|
+
- **TypeScript declarations**: Built-in `.d.ts` and `.d.cts` generation (much faster
|
|
346
|
+
with `isolatedDeclarations`)
|
|
347
|
+
|
|
348
|
+
- **Multi-entry support**: Build multiple entry points in one config
|
|
349
|
+
|
|
350
|
+
- **Workspace support**: `defineWorkspace()` for monorepo builds with incremental
|
|
351
|
+
rebuilds
|
|
352
|
+
|
|
353
|
+
- **Auto-exports**: Automatically generates and updates `package.json` `exports` field
|
|
354
|
+
|
|
355
|
+
- **Compile support**: Can produce standalone executables via `bun --compile`
|
|
356
|
+
|
|
357
|
+
- **Rapid iteration**: Bunup is under active development (0.16.x as of Jan 2026), with
|
|
358
|
+
frequent releases. Pin to a specific minor version for stability.
|
|
359
|
+
|
|
360
|
+
**Configuration (`bunup.config.ts`)**:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { defineConfig } from 'bunup';
|
|
364
|
+
|
|
365
|
+
export default defineConfig({
|
|
366
|
+
entry: {
|
|
367
|
+
index: 'src/index.ts',
|
|
368
|
+
cli: 'src/cli/index.ts',
|
|
369
|
+
bin: 'src/bin.ts',
|
|
370
|
+
},
|
|
371
|
+
format: ['esm', 'cjs'],
|
|
372
|
+
dts: true,
|
|
373
|
+
clean: true,
|
|
374
|
+
sourcemap: 'linked',
|
|
375
|
+
banner: '"use strict";',
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Workspace configuration (`bunup.config.ts` at root)**:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { defineWorkspace } from 'bunup';
|
|
383
|
+
|
|
384
|
+
export default defineWorkspace([
|
|
385
|
+
{
|
|
386
|
+
name: 'core',
|
|
387
|
+
root: 'packages/core',
|
|
388
|
+
config: {
|
|
389
|
+
entry: ['src/index.ts'],
|
|
390
|
+
format: ['esm', 'cjs'],
|
|
391
|
+
dts: true,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'cli',
|
|
396
|
+
root: 'packages/cli',
|
|
397
|
+
config: {
|
|
398
|
+
entry: ['src/index.ts', 'src/bin.ts'],
|
|
399
|
+
format: ['esm'],
|
|
400
|
+
dts: true,
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
]);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Comparison with tsdown**:
|
|
407
|
+
|
|
408
|
+
| Criteria | Bunup | tsdown |
|
|
409
|
+
| --- | --- | --- |
|
|
410
|
+
| Build speed | ~37ms | ~200ms–1s |
|
|
411
|
+
| Runtime dependency | Bun | Node.js |
|
|
412
|
+
| DTS generation | Built-in (fast with isolatedDeclarations) | Built-in |
|
|
413
|
+
| Auto-exports | Yes (generates `exports` field) | No |
|
|
414
|
+
| Workspace mode | Built-in `defineWorkspace()` | No (use pnpm -r) |
|
|
415
|
+
| Plugin ecosystem | Growing (Bun plugins) | Rolldown/Rollup/Vite |
|
|
416
|
+
| Maturity | Newer (2025) | More established |
|
|
417
|
+
| Standalone executables | Yes (`compile: true`) | No |
|
|
418
|
+
|
|
419
|
+
**Assessment**: Bunup is the clear choice for Bun-native projects.
|
|
420
|
+
The auto-exports feature eliminates a common source of configuration errors.
|
|
421
|
+
The workspace mode provides monorepo-aware builds that tsdown does not offer natively.
|
|
422
|
+
|
|
423
|
+
**References**:
|
|
424
|
+
|
|
425
|
+
- [Bunup Documentation](https://bunup.dev/)
|
|
426
|
+
|
|
427
|
+
- [Building a TypeScript Library in 2026 with Bunup](https://dev.to/arshadyaseen/building-a-typescript-library-in-2026-with-bunup-3bmg)
|
|
428
|
+
|
|
429
|
+
* * *
|
|
430
|
+
|
|
431
|
+
### 4. Package Exports & Dual Module Support
|
|
432
|
+
|
|
433
|
+
#### Subpath Exports
|
|
434
|
+
|
|
435
|
+
**Status**: Essential
|
|
436
|
+
|
|
437
|
+
**Details**:
|
|
438
|
+
|
|
439
|
+
Package exports work identically to the pnpm setup.
|
|
440
|
+
The key difference is that Bunup can **auto-generate** the exports field, reducing
|
|
441
|
+
manual configuration errors.
|
|
442
|
+
|
|
443
|
+
**With Bunup auto-exports** (`bunup.config.ts`):
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { defineConfig } from 'bunup';
|
|
447
|
+
|
|
448
|
+
export default defineConfig({
|
|
449
|
+
entry: ['src/index.ts', 'src/cli.ts'],
|
|
450
|
+
format: ['esm', 'cjs'],
|
|
451
|
+
dts: true,
|
|
452
|
+
exports: true, // Auto-generates package.json exports
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
This auto-generates:
|
|
457
|
+
|
|
458
|
+
```json
|
|
459
|
+
{
|
|
460
|
+
"exports": {
|
|
461
|
+
".": {
|
|
462
|
+
"import": {
|
|
463
|
+
"types": "./dist/index.d.ts",
|
|
464
|
+
"default": "./dist/index.js"
|
|
465
|
+
},
|
|
466
|
+
"require": {
|
|
467
|
+
"types": "./dist/index.d.cts",
|
|
468
|
+
"default": "./dist/index.cjs"
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
"./cli": {
|
|
472
|
+
"import": {
|
|
473
|
+
"types": "./dist/cli.d.ts",
|
|
474
|
+
"default": "./dist/cli.js"
|
|
475
|
+
},
|
|
476
|
+
"require": {
|
|
477
|
+
"types": "./dist/cli.d.cts",
|
|
478
|
+
"default": "./dist/cli.cjs"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Manual configuration** (if not using auto-exports):
|
|
486
|
+
|
|
487
|
+
Identical to the pnpm research — use the same `exports` structure with `"types"` before
|
|
488
|
+
`"default"` in each condition block.
|
|
489
|
+
|
|
490
|
+
**Bun export condition**: Optionally add a `"bun"` condition pointing to TypeScript
|
|
491
|
+
source for Bun consumers:
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{
|
|
495
|
+
"exports": {
|
|
496
|
+
".": {
|
|
497
|
+
"bun": "./src/index.ts",
|
|
498
|
+
"import": {
|
|
499
|
+
"types": "./dist/index.d.ts",
|
|
500
|
+
"default": "./dist/index.js"
|
|
501
|
+
},
|
|
502
|
+
"require": {
|
|
503
|
+
"types": "./dist/index.d.cts",
|
|
504
|
+
"default": "./dist/index.cjs"
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Assessment**: Bunup’s auto-exports feature is a significant DX improvement over manual
|
|
512
|
+
exports configuration.
|
|
513
|
+
It eliminates a common class of publishing errors.
|
|
514
|
+
|
|
515
|
+
**References**:
|
|
516
|
+
|
|
517
|
+
- [Bunup Exports Configuration](https://bunup.dev/)
|
|
518
|
+
|
|
519
|
+
- [Guide to package.json exports field](https://hirok.io/posts/package-json-exports)
|
|
520
|
+
|
|
521
|
+
* * *
|
|
522
|
+
|
|
523
|
+
### 5. Optional Peer Dependencies
|
|
524
|
+
|
|
525
|
+
**Status**: Recommended
|
|
526
|
+
|
|
527
|
+
**Details**:
|
|
528
|
+
|
|
529
|
+
This pattern is identical to the pnpm ecosystem approach.
|
|
530
|
+
Bun handles peer dependencies the same way as npm/pnpm.
|
|
531
|
+
|
|
532
|
+
```json
|
|
533
|
+
{
|
|
534
|
+
"peerDependencies": {
|
|
535
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
536
|
+
"ai": "^5.0.0"
|
|
537
|
+
},
|
|
538
|
+
"peerDependenciesMeta": {
|
|
539
|
+
"@modelcontextprotocol/sdk": { "optional": true },
|
|
540
|
+
"ai": { "optional": true }
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Assessment**: No differences from the pnpm approach.
|
|
546
|
+
Works identically in Bun.
|
|
547
|
+
|
|
548
|
+
* * *
|
|
549
|
+
|
|
550
|
+
### 6. Package Validation
|
|
551
|
+
|
|
552
|
+
#### publint
|
|
553
|
+
|
|
554
|
+
**Status**: Essential
|
|
555
|
+
|
|
556
|
+
**Details**:
|
|
557
|
+
|
|
558
|
+
publint works identically in the Bun ecosystem.
|
|
559
|
+
Run it via Bun:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
bunx publint
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Integration**:
|
|
566
|
+
|
|
567
|
+
```json
|
|
568
|
+
{
|
|
569
|
+
"scripts": {
|
|
570
|
+
"publint": "bunx publint",
|
|
571
|
+
"prepack": "bun run build"
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Assessment**: No changes needed from the pnpm approach.
|
|
577
|
+
publint is runtime-agnostic.
|
|
578
|
+
|
|
579
|
+
* * *
|
|
580
|
+
|
|
581
|
+
### 7. Versioning & Release Automation
|
|
582
|
+
|
|
583
|
+
#### Changesets (with Bun Workarounds)
|
|
584
|
+
|
|
585
|
+
**Status**: Recommended (with workarounds)
|
|
586
|
+
|
|
587
|
+
**Details**:
|
|
588
|
+
|
|
589
|
+
Changesets is the de facto standard for monorepo versioning, but it has known issues
|
|
590
|
+
with Bun workspaces.
|
|
591
|
+
The key problem is that `changeset version` does not resolve `workspace:*` references to
|
|
592
|
+
actual version numbers, which breaks published packages.
|
|
593
|
+
|
|
594
|
+
**Setup**:
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
bun add -d @changesets/cli @changesets/changelog-github
|
|
598
|
+
bunx changeset init
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**`.changeset/config.json`**: Identical to the pnpm setup:
|
|
602
|
+
|
|
603
|
+
```json
|
|
604
|
+
{
|
|
605
|
+
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
|
606
|
+
"changelog": "@changesets/changelog-github",
|
|
607
|
+
"commit": false,
|
|
608
|
+
"fixed": [],
|
|
609
|
+
"linked": [],
|
|
610
|
+
"access": "public",
|
|
611
|
+
"baseBranch": "main",
|
|
612
|
+
"updateInternalDependencies": "patch"
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Critical workaround scripts**:
|
|
617
|
+
|
|
618
|
+
```json
|
|
619
|
+
{
|
|
620
|
+
"scripts": {
|
|
621
|
+
"changeset": "changeset",
|
|
622
|
+
"version-packages": "changeset version && bun update",
|
|
623
|
+
"release": "bun run build && bunx publint && bun run publish-packages",
|
|
624
|
+
"publish-packages": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag"
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Why `bun update` after `changeset version`**: When Changesets updates version numbers
|
|
630
|
+
in `package.json` files, the `workspace:*` references in the lockfile become stale.
|
|
631
|
+
Running `bun update` regenerates the lockfile with resolved versions.
|
|
632
|
+
|
|
633
|
+
**Why `bun publish` per package**: The standard `changeset publish` uses npm under the
|
|
634
|
+
hood. Using `bun publish` directly for each package ensures proper Bun compatibility and
|
|
635
|
+
workspace reference resolution.
|
|
636
|
+
|
|
637
|
+
**Comparison with pnpm Changesets workflow**:
|
|
638
|
+
|
|
639
|
+
| Aspect | pnpm | Bun |
|
|
640
|
+
| --- | --- | --- |
|
|
641
|
+
| Setup | Works out of the box | Requires workarounds |
|
|
642
|
+
| Version command | `changeset version` | `changeset version && bun update` |
|
|
643
|
+
| Publish command | `changeset publish` | Custom per-package loop with `bun publish` |
|
|
644
|
+
| GitHub Action | `changesets/action` works directly | Needs custom publish step |
|
|
645
|
+
| Workspace resolution | Automatic | Requires `bun update` fixup |
|
|
646
|
+
|
|
647
|
+
**Assessment**: Changesets works with Bun but requires workarounds.
|
|
648
|
+
This is the most significant friction point in the Bun ecosystem compared to pnpm.
|
|
649
|
+
Monitor for native Bun support in Changesets or a Bun-native alternative.
|
|
650
|
+
|
|
651
|
+
**References**:
|
|
652
|
+
|
|
653
|
+
- [Setting up Changesets with Bun Workspaces](https://ianm.com/posts/2025-08-18-setting-up-changesets-with-bun-workspaces)
|
|
654
|
+
|
|
655
|
+
- [Changesets GitHub repository](https://github.com/changesets/changesets)
|
|
656
|
+
|
|
657
|
+
* * *
|
|
658
|
+
|
|
659
|
+
#### Dynamic Git-Based Versioning
|
|
660
|
+
|
|
661
|
+
**Status**: Recommended for dev builds
|
|
662
|
+
|
|
663
|
+
**Details**:
|
|
664
|
+
|
|
665
|
+
The git-based versioning pattern from the pnpm research works identically with Bun.
|
|
666
|
+
The only difference is the dev script uses `bun` instead of `tsx`:
|
|
667
|
+
|
|
668
|
+
**Dev Script** (`package.json`):
|
|
669
|
+
|
|
670
|
+
```json
|
|
671
|
+
{
|
|
672
|
+
"scripts": {
|
|
673
|
+
"dev": "PROJECT_DEV_VERSION=$(bun scripts/git-version.mjs) bun src/cli/bin.ts"
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Key advantage**: Bun executes TypeScript directly, so there is no need for `tsx`. The
|
|
679
|
+
`scripts/git-version.mjs` script works unchanged since it uses only Node.js built-in
|
|
680
|
+
modules which Bun supports.
|
|
681
|
+
|
|
682
|
+
**Assessment**: Identical pattern, simpler execution.
|
|
683
|
+
No `tsx` dependency needed.
|
|
684
|
+
|
|
685
|
+
* * *
|
|
686
|
+
|
|
687
|
+
### 8. Testing
|
|
688
|
+
|
|
689
|
+
#### Bun Test Runner
|
|
690
|
+
|
|
691
|
+
**Status**: Recommended (with caveats)
|
|
692
|
+
|
|
693
|
+
**Details**:
|
|
694
|
+
|
|
695
|
+
Bun ships with a built-in test runner that is API-compatible with Jest/Vitest.
|
|
696
|
+
It is extremely fast — roughly 2x faster than Node’s built-in test runner and
|
|
697
|
+
significantly faster than Jest or Vitest.
|
|
698
|
+
|
|
699
|
+
**Key features**:
|
|
700
|
+
|
|
701
|
+
- Zero-config: works out of the box with TypeScript
|
|
702
|
+
|
|
703
|
+
- Jest-compatible API (`describe`, `it`, `expect`, `mock`, etc.)
|
|
704
|
+
|
|
705
|
+
- Watch mode with HMR (re-runs only affected tests)
|
|
706
|
+
|
|
707
|
+
- Snapshot testing
|
|
708
|
+
|
|
709
|
+
- Code coverage (built-in, `--coverage`)
|
|
710
|
+
|
|
711
|
+
- Lifecycle hooks (`beforeAll`, `afterAll`, `beforeEach`, `afterEach`)
|
|
712
|
+
|
|
713
|
+
**Running tests**:
|
|
714
|
+
|
|
715
|
+
```bash
|
|
716
|
+
bun test # Run all tests
|
|
717
|
+
bun test --watch # Watch mode
|
|
718
|
+
bun test --coverage # With coverage
|
|
719
|
+
bun test --timeout 10000 # Custom timeout
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**Test file patterns**: `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx`, or files
|
|
723
|
+
in `__tests__/` directories.
|
|
724
|
+
|
|
725
|
+
**Example test**:
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
import { describe, it, expect, mock } from 'bun:test';
|
|
729
|
+
import { processData } from '../src/index.ts';
|
|
730
|
+
|
|
731
|
+
describe('processData', () => {
|
|
732
|
+
it('handles valid input', () => {
|
|
733
|
+
expect(processData({ key: 'value' })).toEqual({ key: 'VALUE' });
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('throws on invalid input', () => {
|
|
737
|
+
expect(() => processData(null)).toThrow('Invalid input');
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Known limitations**:
|
|
743
|
+
|
|
744
|
+
- Tests are not isolated by default (side effects can leak between suites)
|
|
745
|
+
|
|
746
|
+
- Less mature IDE integration than Vitest
|
|
747
|
+
|
|
748
|
+
- No browser mode, benchmarking, type testing, or sharding (features Vitest has)
|
|
749
|
+
|
|
750
|
+
**Fake timers** (added in Bun v1.3.4, Dec 2025):
|
|
751
|
+
|
|
752
|
+
Bun’s test runner now supports Jest-compatible fake timers via `jest.useFakeTimers()`,
|
|
753
|
+
`jest.advanceTimersByTime(ms)`, and `jest.useRealTimers()`. This was previously a major
|
|
754
|
+
gap. The system time mock (`setSystemTime` from `bun:test`) has been available longer.
|
|
755
|
+
Bun v1.3.6 added further compatibility fixes for `@testing-library/react` fake timer
|
|
756
|
+
detection.
|
|
757
|
+
|
|
758
|
+
**Comparison with Vitest / Node test runner**:
|
|
759
|
+
|
|
760
|
+
| Criteria | Bun test | Vitest | Node test runner |
|
|
761
|
+
| --- | --- | --- | --- |
|
|
762
|
+
| Speed | Fastest | Fast | Fast |
|
|
763
|
+
| Setup | Zero-config | Requires install + config | Zero-config (Node 20+) |
|
|
764
|
+
| TypeScript | Native | Via Vite transform | Via `--experimental-strip-types` or tsx |
|
|
765
|
+
| API | Jest-compatible | Jest-compatible | Node-native API |
|
|
766
|
+
| Watch mode | HMR-based | HMR-based | File-system based |
|
|
767
|
+
| Isolation | No isolation | Isolated by default | Isolated |
|
|
768
|
+
| Fake timers | Jest-compatible (v1.3.4+) | Full support | Full support |
|
|
769
|
+
| Coverage | Built-in | Built-in (c8/v8) | Built-in (Node 20+) |
|
|
770
|
+
| IDE support | Basic | Excellent (VS Code) | Moderate |
|
|
771
|
+
| Ecosystem | Growing | Mature | Node-native |
|
|
772
|
+
|
|
773
|
+
**Assessment**: With fake timers added in Bun v1.3.4, the main remaining gap is test
|
|
774
|
+
isolation and advanced features (browser mode, sharding).
|
|
775
|
+
Use `bun test` for most Bun-native projects.
|
|
776
|
+
For projects requiring test isolation or cross-runtime compatibility, Vitest remains the
|
|
777
|
+
safer choice.
|
|
778
|
+
|
|
779
|
+
**References**:
|
|
780
|
+
|
|
781
|
+
- [Bun Test Runner](https://bun.sh/docs/cli/test)
|
|
782
|
+
|
|
783
|
+
- [Comparing Test Frameworks: Jest vs Vitest vs Bun](https://dev.to/kcsujeet/your-tests-are-slow-you-need-to-migrate-to-bun-9hh)
|
|
784
|
+
|
|
785
|
+
- [Node Test Runner vs Bun Test Runner](https://dev.to/boscodomingo/node-test-runner-vs-bun-test-runner-with-typescript-and-esm-44ih)
|
|
786
|
+
|
|
787
|
+
* * *
|
|
788
|
+
|
|
789
|
+
### 9. Code Formatting & Linting
|
|
790
|
+
|
|
791
|
+
#### Biome
|
|
792
|
+
|
|
793
|
+
**Status**: Recommended
|
|
794
|
+
|
|
795
|
+
**Details**:
|
|
796
|
+
|
|
797
|
+
Biome is an all-in-one formatter and linter written in Rust.
|
|
798
|
+
It replaces both Prettier and ESLint with a single tool, aligning well with the Bun
|
|
799
|
+
ecosystem’s philosophy of consolidation and speed.
|
|
800
|
+
|
|
801
|
+
**Key advantages**:
|
|
802
|
+
|
|
803
|
+
- **10–25x faster** than ESLint + Prettier combined
|
|
804
|
+
|
|
805
|
+
- **Single configuration file** (`biome.json`) instead of `.prettierrc` +
|
|
806
|
+
`eslint.config.js`
|
|
807
|
+
|
|
808
|
+
- **Single binary** instead of 127+ npm packages
|
|
809
|
+
|
|
810
|
+
- **97% Prettier-compatible** formatting
|
|
811
|
+
|
|
812
|
+
- **300+ lint rules** including type-aware linting (v2.0+, without requiring `tsc`)
|
|
813
|
+
|
|
814
|
+
- **Built-in migration** from ESLint and Prettier configs
|
|
815
|
+
|
|
816
|
+
- **v2.3.x** (latest as of Jan 2026) adds nursery rules for floating promises
|
|
817
|
+
(`noFloatingPromises`), duplicate HTML attributes, JSX prop binding, and more
|
|
818
|
+
|
|
819
|
+
**Installation**:
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
bun add -d @biomejs/biome
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
**`biome.json`**:
|
|
826
|
+
|
|
827
|
+
```json
|
|
828
|
+
{
|
|
829
|
+
"$schema": "https://biomejs.dev/schemas/2.3.0/schema.json",
|
|
830
|
+
"organizeImports": {
|
|
831
|
+
"enabled": true
|
|
832
|
+
},
|
|
833
|
+
"formatter": {
|
|
834
|
+
"enabled": true,
|
|
835
|
+
"indentStyle": "space",
|
|
836
|
+
"indentWidth": 2,
|
|
837
|
+
"lineWidth": 100
|
|
838
|
+
},
|
|
839
|
+
"linter": {
|
|
840
|
+
"enabled": true,
|
|
841
|
+
"rules": {
|
|
842
|
+
"recommended": true,
|
|
843
|
+
"correctness": {
|
|
844
|
+
"noUnusedVariables": "error",
|
|
845
|
+
"noUnusedImports": "error"
|
|
846
|
+
},
|
|
847
|
+
"suspicious": {
|
|
848
|
+
"noExplicitAny": "warn"
|
|
849
|
+
},
|
|
850
|
+
"style": {
|
|
851
|
+
"useConst": "error",
|
|
852
|
+
"noNonNullAssertion": "warn"
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
"files": {
|
|
857
|
+
"ignore": ["dist", "node_modules", ".changeset"]
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
**Scripts**:
|
|
863
|
+
|
|
864
|
+
```json
|
|
865
|
+
{
|
|
866
|
+
"scripts": {
|
|
867
|
+
"format": "biome format --write .",
|
|
868
|
+
"format:check": "biome format .",
|
|
869
|
+
"lint": "biome lint --write .",
|
|
870
|
+
"lint:check": "biome lint .",
|
|
871
|
+
"check": "biome check --write .",
|
|
872
|
+
"check:ci": "biome check ."
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
**Biome `check` vs separate commands**: `biome check` runs both formatting and linting
|
|
878
|
+
in a single pass, which is faster than running them separately.
|
|
879
|
+
Use `check` for local development and `check:ci` (without `--write`) for CI.
|
|
880
|
+
|
|
881
|
+
**Comparison with Prettier + ESLint**:
|
|
882
|
+
|
|
883
|
+
| Criteria | Biome | Prettier + ESLint |
|
|
884
|
+
| --- | --- | --- |
|
|
885
|
+
| Speed | 10–25x faster | Baseline |
|
|
886
|
+
| Config files | 1 (`biome.json`) | 2–4 files |
|
|
887
|
+
| npm packages | 1 | 5–10+ (with plugins) |
|
|
888
|
+
| Type-aware linting | v2.0+ (plugin) | ESLint + typescript-eslint |
|
|
889
|
+
| Language support | JS/TS/JSON/CSS | Broader (HTML, Markdown, SCSS, etc.) |
|
|
890
|
+
| Plugin ecosystem | Growing (v2.0) | Mature (decade of plugins) |
|
|
891
|
+
| IDE support | Good (VS Code, JetBrains) | Excellent |
|
|
892
|
+
| Prettier compatibility | 97% | 100% (is Prettier) |
|
|
893
|
+
|
|
894
|
+
**Known limitations**:
|
|
895
|
+
|
|
896
|
+
- Does not format HTML, Markdown, or SCSS (use Prettier alongside if needed)
|
|
897
|
+
|
|
898
|
+
- Younger plugin ecosystem than ESLint
|
|
899
|
+
|
|
900
|
+
- Missing some specialized ESLint plugins (security, accessibility)
|
|
901
|
+
|
|
902
|
+
**Assessment**: Biome is the natural choice for a full-Bun ecosystem.
|
|
903
|
+
It provides the same quality guarantees as Prettier + ESLint with dramatically less
|
|
904
|
+
configuration and better performance.
|
|
905
|
+
For projects that need HTML/Markdown formatting or specialized ESLint plugins, a hybrid
|
|
906
|
+
approach (Biome + targeted Prettier/ESLint) is viable.
|
|
907
|
+
|
|
908
|
+
**References**:
|
|
909
|
+
|
|
910
|
+
- [Biome Documentation](https://biomejs.dev/)
|
|
911
|
+
|
|
912
|
+
- [Biome vs ESLint + Prettier: The 2025 Linting Revolution](https://medium.com/better-dev-nextjs-react/biome-vs-eslint-prettier-the-2025-linting-revolution-you-need-to-know-about-ec01c5d5b6c8)
|
|
913
|
+
|
|
914
|
+
- [Migrating from Prettier and ESLint to BiomeJS](https://blog.appsignal.com/2025/05/07/migrating-a-javascript-project-from-prettier-and-eslint-to-biomejs.html)
|
|
915
|
+
|
|
916
|
+
* * *
|
|
917
|
+
|
|
918
|
+
### 10. CI/CD Configuration
|
|
919
|
+
|
|
920
|
+
#### GitHub Actions: CI Workflow
|
|
921
|
+
|
|
922
|
+
**Status**: Recommended
|
|
923
|
+
|
|
924
|
+
**`.github/workflows/ci.yml`**:
|
|
925
|
+
|
|
926
|
+
```yaml
|
|
927
|
+
name: CI
|
|
928
|
+
|
|
929
|
+
on:
|
|
930
|
+
pull_request:
|
|
931
|
+
push:
|
|
932
|
+
branches: [main]
|
|
933
|
+
|
|
934
|
+
jobs:
|
|
935
|
+
test:
|
|
936
|
+
runs-on: ubuntu-latest
|
|
937
|
+
steps:
|
|
938
|
+
- uses: actions/checkout@v6
|
|
939
|
+
|
|
940
|
+
- uses: oven-sh/setup-bun@v2
|
|
941
|
+
with:
|
|
942
|
+
bun-version: latest
|
|
943
|
+
|
|
944
|
+
- run: bun install --frozen-lockfile
|
|
945
|
+
|
|
946
|
+
- run: bun run check:ci
|
|
947
|
+
- run: bun run build
|
|
948
|
+
- run: bunx publint
|
|
949
|
+
- run: bun test
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
**Key differences from pnpm CI**:
|
|
953
|
+
|
|
954
|
+
| Aspect | pnpm CI | Bun CI |
|
|
955
|
+
| --- | --- | --- |
|
|
956
|
+
| Setup action | `pnpm/action-setup@v4` + `actions/setup-node@v6` | `oven-sh/setup-bun@v2` (one action) |
|
|
957
|
+
| Install | `pnpm install --frozen-lockfile` | `bun install --frozen-lockfile` |
|
|
958
|
+
| Lockfile | `pnpm-lock.yaml` (YAML) | `bun.lock` (JSONC, text-based since Bun 1.2) |
|
|
959
|
+
| Lint + format | Separate `format:check` + `lint:check` | Single `biome check` |
|
|
960
|
+
| Node.js setup | Required | Not required (Bun includes runtime) |
|
|
961
|
+
|
|
962
|
+
**Assessment**: Bun CI is simpler — one setup action instead of two, and a single check
|
|
963
|
+
command instead of separate format and lint steps.
|
|
964
|
+
The text-based `bun.lock` (since Bun 1.2) is diffable in PRs, on par with
|
|
965
|
+
`pnpm-lock.yaml`.
|
|
966
|
+
|
|
967
|
+
**References**:
|
|
968
|
+
|
|
969
|
+
- [Bun CI/CD Guide](https://bun.sh/docs/guides/runtime/cicd)
|
|
970
|
+
|
|
971
|
+
- [oven-sh/setup-bun](https://github.com/oven-sh/setup-bun)
|
|
972
|
+
|
|
973
|
+
* * *
|
|
974
|
+
|
|
975
|
+
#### GitHub Actions: Release Workflow
|
|
976
|
+
|
|
977
|
+
**Status**: Recommended
|
|
978
|
+
|
|
979
|
+
**`.github/workflows/release.yml`**:
|
|
980
|
+
|
|
981
|
+
```yaml
|
|
982
|
+
name: Release
|
|
983
|
+
|
|
984
|
+
on:
|
|
985
|
+
push:
|
|
986
|
+
branches: [main]
|
|
987
|
+
|
|
988
|
+
permissions:
|
|
989
|
+
contents: write
|
|
990
|
+
pull-requests: write
|
|
991
|
+
|
|
992
|
+
jobs:
|
|
993
|
+
release:
|
|
994
|
+
runs-on: ubuntu-latest
|
|
995
|
+
steps:
|
|
996
|
+
- uses: actions/checkout@v6
|
|
997
|
+
with:
|
|
998
|
+
fetch-depth: 0
|
|
999
|
+
|
|
1000
|
+
- uses: oven-sh/setup-bun@v2
|
|
1001
|
+
with:
|
|
1002
|
+
bun-version: latest
|
|
1003
|
+
|
|
1004
|
+
- run: bun install --frozen-lockfile
|
|
1005
|
+
|
|
1006
|
+
- name: Create Release PR or Publish
|
|
1007
|
+
id: changesets
|
|
1008
|
+
uses: changesets/action@v1
|
|
1009
|
+
with:
|
|
1010
|
+
version: bun run version-packages
|
|
1011
|
+
publish: bun run release
|
|
1012
|
+
env:
|
|
1013
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
1014
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
**Important**: The `version` command must be `bun run version-packages` (which includes
|
|
1018
|
+
the `bun update` fixup), not just `changeset version`. The `publish` command uses the
|
|
1019
|
+
custom per-package publish script.
|
|
1020
|
+
|
|
1021
|
+
**Assessment**: Slightly more complex than the pnpm release workflow due to the
|
|
1022
|
+
Changesets workarounds, but functionally equivalent.
|
|
1023
|
+
|
|
1024
|
+
* * *
|
|
1025
|
+
|
|
1026
|
+
### 11. Git Hooks & Local Validation
|
|
1027
|
+
|
|
1028
|
+
#### Lefthook with Biome
|
|
1029
|
+
|
|
1030
|
+
**Status**: Recommended
|
|
1031
|
+
|
|
1032
|
+
**Details**:
|
|
1033
|
+
|
|
1034
|
+
Lefthook works identically in the Bun ecosystem.
|
|
1035
|
+
The main change is replacing Prettier + ESLint commands with Biome.
|
|
1036
|
+
|
|
1037
|
+
**Installation**:
|
|
1038
|
+
|
|
1039
|
+
```bash
|
|
1040
|
+
bun add -d lefthook
|
|
1041
|
+
bunx lefthook install
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
**`lefthook.yml`**:
|
|
1045
|
+
|
|
1046
|
+
```yaml
|
|
1047
|
+
pre-commit:
|
|
1048
|
+
parallel: true
|
|
1049
|
+
|
|
1050
|
+
commands:
|
|
1051
|
+
# Format + lint with Biome (~200ms)
|
|
1052
|
+
check:
|
|
1053
|
+
glob: '*.{js,ts,tsx,json}'
|
|
1054
|
+
run: bunx biome check --write {staged_files}
|
|
1055
|
+
stage_fixed: true
|
|
1056
|
+
priority: 1
|
|
1057
|
+
|
|
1058
|
+
# Type check (~2s)
|
|
1059
|
+
typecheck:
|
|
1060
|
+
glob: '*.{ts,tsx}'
|
|
1061
|
+
run: bunx tsc --noEmit --incremental
|
|
1062
|
+
priority: 2
|
|
1063
|
+
|
|
1064
|
+
pre-push:
|
|
1065
|
+
commands:
|
|
1066
|
+
verify-tests:
|
|
1067
|
+
run: |
|
|
1068
|
+
COMMIT_HASH=$(git rev-parse HEAD)
|
|
1069
|
+
CACHE_DIR="node_modules/.test-cache"
|
|
1070
|
+
CACHE_FILE="$CACHE_DIR/$COMMIT_HASH"
|
|
1071
|
+
|
|
1072
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
1073
|
+
bun test
|
|
1074
|
+
exit $?
|
|
1075
|
+
fi
|
|
1076
|
+
|
|
1077
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
1078
|
+
exit 0
|
|
1079
|
+
fi
|
|
1080
|
+
|
|
1081
|
+
bun test
|
|
1082
|
+
|
|
1083
|
+
if [ $? -eq 0 ]; then
|
|
1084
|
+
mkdir -p "$CACHE_DIR"
|
|
1085
|
+
touch "$CACHE_FILE"
|
|
1086
|
+
else
|
|
1087
|
+
exit 1
|
|
1088
|
+
fi
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
**Key advantage**: Using `biome check` in pre-commit combines formatting and linting
|
|
1092
|
+
into a single command, reducing hook complexity from 2–3 commands to 1.
|
|
1093
|
+
|
|
1094
|
+
**Assessment**: Simpler and faster hooks than the pnpm equivalent thanks to Biome’s
|
|
1095
|
+
unified check command.
|
|
1096
|
+
|
|
1097
|
+
* * *
|
|
1098
|
+
|
|
1099
|
+
### 12. Standalone Executable Compilation
|
|
1100
|
+
|
|
1101
|
+
#### Bun Compile
|
|
1102
|
+
|
|
1103
|
+
**Status**: Unique Capability
|
|
1104
|
+
|
|
1105
|
+
**Details**:
|
|
1106
|
+
|
|
1107
|
+
Bun can compile TypeScript/JavaScript into standalone executables that include the Bun
|
|
1108
|
+
runtime. This is a capability not available in the pnpm/Node.js ecosystem without
|
|
1109
|
+
third-party tools like `pkg` (deprecated) or `vercel/pkg`.
|
|
1110
|
+
|
|
1111
|
+
**Basic usage**:
|
|
1112
|
+
|
|
1113
|
+
```bash
|
|
1114
|
+
bun build --compile ./src/bin.ts --outfile myapp
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
**Cross-compilation**:
|
|
1118
|
+
|
|
1119
|
+
```bash
|
|
1120
|
+
# Build for Linux x64 from any platform
|
|
1121
|
+
bun build --compile --target=bun-linux-x64 ./src/bin.ts --outfile myapp
|
|
1122
|
+
|
|
1123
|
+
# Build for Windows
|
|
1124
|
+
bun build --compile --target=bun-windows-x64 ./src/bin.ts --outfile myapp.exe
|
|
1125
|
+
|
|
1126
|
+
# Build for macOS ARM
|
|
1127
|
+
bun build --compile --target=bun-darwin-arm64 ./src/bin.ts --outfile myapp
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
**Via Bunup config**:
|
|
1131
|
+
|
|
1132
|
+
```typescript
|
|
1133
|
+
import { defineConfig } from 'bunup';
|
|
1134
|
+
|
|
1135
|
+
export default defineConfig({
|
|
1136
|
+
entry: 'src/bin.ts',
|
|
1137
|
+
compile: true, // Compile for current platform
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// Or cross-compile:
|
|
1141
|
+
export default defineConfig({
|
|
1142
|
+
entry: 'src/bin.ts',
|
|
1143
|
+
compile: {
|
|
1144
|
+
target: 'bun-linux-x64',
|
|
1145
|
+
outfile: './bin/myapp',
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**Embedding assets**:
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
// Files can be embedded directly into the binary
|
|
1154
|
+
const config = await Bun.file(import.meta.dir + '/config.json').text();
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
**Available targets**:
|
|
1158
|
+
|
|
1159
|
+
| Target | Architecture |
|
|
1160
|
+
| --- | --- |
|
|
1161
|
+
| `bun-linux-x64` | Linux x86_64 |
|
|
1162
|
+
| `bun-linux-arm64` | Linux ARM64 |
|
|
1163
|
+
| `bun-darwin-x64` | macOS Intel |
|
|
1164
|
+
| `bun-darwin-arm64` | macOS Apple Silicon |
|
|
1165
|
+
| `bun-windows-x64` | Windows x86_64 |
|
|
1166
|
+
|
|
1167
|
+
**Optimization flags**:
|
|
1168
|
+
|
|
1169
|
+
```bash
|
|
1170
|
+
# Reduce startup time (not binary size) with bytecode pre-compilation
|
|
1171
|
+
bun build --compile --bytecode --minify ./src/bin.ts --outfile myapp
|
|
1172
|
+
|
|
1173
|
+
# Minify source to reduce embedded code size
|
|
1174
|
+
bun build --compile --minify --sourcemap ./src/bin.ts --outfile myapp
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Known limitations**:
|
|
1178
|
+
|
|
1179
|
+
- Binary size starts at ~50–100MB (includes Bun runtime).
|
|
1180
|
+
This is an open issue ([#5854](https://github.com/oven-sh/bun/issues/5854)).
|
|
1181
|
+
|
|
1182
|
+
- Embedded directory support is beta-quality
|
|
1183
|
+
|
|
1184
|
+
- The executable is Bun-runtime-specific (not a true native binary like Go/Rust)
|
|
1185
|
+
|
|
1186
|
+
**Comparison with other distribution methods**:
|
|
1187
|
+
|
|
1188
|
+
| Method | Size | Runtime needed | Cross-platform | Speed |
|
|
1189
|
+
| --- | --- | --- | --- | --- |
|
|
1190
|
+
| npm publish | Small (~KB) | Node.js or Bun | Yes | Startup depends on runtime |
|
|
1191
|
+
| `bun --compile` | ~50–100MB | None | Cross-compile | Fast startup |
|
|
1192
|
+
| Go binary | ~10–20MB | None | Cross-compile | Fast startup |
|
|
1193
|
+
| Rust binary | ~5–15MB | None | Cross-compile | Fast startup |
|
|
1194
|
+
|
|
1195
|
+
**Assessment**: Bun’s compile feature is valuable for distributing CLI tools to users
|
|
1196
|
+
who don’t have Node.js or Bun installed.
|
|
1197
|
+
The binary size is the main downside.
|
|
1198
|
+
This is a unique advantage that the pnpm ecosystem does not offer natively.
|
|
1199
|
+
|
|
1200
|
+
**References**:
|
|
1201
|
+
|
|
1202
|
+
- [Bun Single-file Executables](https://bun.sh/docs/bundler/executables)
|
|
1203
|
+
|
|
1204
|
+
- [Bunup Compile Documentation](https://bunup.dev/docs/advanced/compile.html)
|
|
1205
|
+
|
|
1206
|
+
- [Bun Cross-Compilation](https://developer.mamezou-tech.com/en/blogs/2024/05/20/bun-cross-compile/)
|
|
1207
|
+
|
|
1208
|
+
* * *
|
|
1209
|
+
|
|
1210
|
+
### 13. Dependency Upgrade Management
|
|
1211
|
+
|
|
1212
|
+
**Status**: Recommended
|
|
1213
|
+
|
|
1214
|
+
**Details**:
|
|
1215
|
+
|
|
1216
|
+
npm-check-updates (`ncu`) works with Bun.
|
|
1217
|
+
The main difference is using `bun install` instead of `pnpm install` after updates.
|
|
1218
|
+
|
|
1219
|
+
**Root `package.json` scripts**:
|
|
1220
|
+
|
|
1221
|
+
```json
|
|
1222
|
+
{
|
|
1223
|
+
"scripts": {
|
|
1224
|
+
"upgrade:check": "bunx npm-check-updates --format group",
|
|
1225
|
+
"upgrade": "bunx npm-check-updates --target minor -u && bun install && bun test",
|
|
1226
|
+
"upgrade:major": "bunx npm-check-updates --target latest --interactive --format group"
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
**Assessment**: Identical workflow.
|
|
1232
|
+
`bunx` replaces `npx`.
|
|
1233
|
+
|
|
1234
|
+
* * *
|
|
1235
|
+
|
|
1236
|
+
### 14. CLI Development Workflow
|
|
1237
|
+
|
|
1238
|
+
#### Running CLI from Source
|
|
1239
|
+
|
|
1240
|
+
**Status**: Strongly Recommended
|
|
1241
|
+
|
|
1242
|
+
**Details**:
|
|
1243
|
+
|
|
1244
|
+
Bun natively executes TypeScript, eliminating the need for `tsx` entirely.
|
|
1245
|
+
|
|
1246
|
+
**The dual-script pattern** (simplified vs pnpm):
|
|
1247
|
+
|
|
1248
|
+
```json
|
|
1249
|
+
{
|
|
1250
|
+
"scripts": {
|
|
1251
|
+
"cli-name": "bun packages/package-name/src/cli/bin.ts",
|
|
1252
|
+
"cli-name:bin": "bun packages/package-name/dist/bin.js"
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
**Comparison with pnpm approach**:
|
|
1258
|
+
|
|
1259
|
+
| Aspect | pnpm (tsx) | Bun |
|
|
1260
|
+
| --- | --- | --- |
|
|
1261
|
+
| Dev command | `tsx packages/pkg/src/cli/bin.ts` | `bun packages/pkg/src/cli/bin.ts` |
|
|
1262
|
+
| Extra dependency | `tsx` (~4.21.0) | None (built-in) |
|
|
1263
|
+
| TypeScript support | Via esbuild transform | Native |
|
|
1264
|
+
| Startup time | ~50ms | ~5ms |
|
|
1265
|
+
|
|
1266
|
+
**Assessment**: Bun eliminates the `tsx` dependency entirely.
|
|
1267
|
+
One fewer devDependency, faster startup, simpler scripts.
|
|
1268
|
+
|
|
1269
|
+
* * *
|
|
1270
|
+
|
|
1271
|
+
### 15. Private Package Distribution
|
|
1272
|
+
|
|
1273
|
+
**Status**: Same as pnpm
|
|
1274
|
+
|
|
1275
|
+
**Details**:
|
|
1276
|
+
|
|
1277
|
+
Distribution options are identical:
|
|
1278
|
+
|
|
1279
|
+
1. **GitHub Packages** (recommended for teams)
|
|
1280
|
+
|
|
1281
|
+
2. **Direct GitHub install**: `bun add github:org/repo` — Bun has limited support for
|
|
1282
|
+
monorepo subdirectory installs (lagging behind pnpm)
|
|
1283
|
+
|
|
1284
|
+
3. **Local linking**: `bun link` works similarly to `pnpm link`
|
|
1285
|
+
|
|
1286
|
+
**Bun-specific option**: Standalone executables via `bun --compile` provide an
|
|
1287
|
+
additional distribution path that bypasses package registries entirely.
|
|
1288
|
+
|
|
1289
|
+
* * *
|
|
1290
|
+
|
|
1291
|
+
### 16. Library/CLI Hybrid Packages
|
|
1292
|
+
|
|
1293
|
+
**Status**: Same as pnpm
|
|
1294
|
+
|
|
1295
|
+
**Details**:
|
|
1296
|
+
|
|
1297
|
+
The Node-free core pattern from the pnpm research applies identically.
|
|
1298
|
+
Keep `node:` imports out of core library code and isolated to CLI-specific modules.
|
|
1299
|
+
|
|
1300
|
+
The only addition for Bun projects: you may also want to avoid Bun-specific APIs
|
|
1301
|
+
(`Bun.file()`, `Bun.serve()`, etc.)
|
|
1302
|
+
in core library code if the library needs to work in Node.js environments.
|
|
1303
|
+
|
|
1304
|
+
* * *
|
|
1305
|
+
|
|
1306
|
+
## Comparative Analysis
|
|
1307
|
+
|
|
1308
|
+
### Full-Stack Tooling Comparison
|
|
1309
|
+
|
|
1310
|
+
| Concern | pnpm Monorepo | Bun Monorepo |
|
|
1311
|
+
| --- | --- | --- |
|
|
1312
|
+
| **Package manager** | pnpm | Bun |
|
|
1313
|
+
| **Runtime** | Node.js | Bun |
|
|
1314
|
+
| **Build tool** | tsdown | Bunup |
|
|
1315
|
+
| **Test runner** | Node test / Vitest | bun test |
|
|
1316
|
+
| **Formatter** | Prettier | Biome |
|
|
1317
|
+
| **Linter** | ESLint | Biome |
|
|
1318
|
+
| **Git hooks** | Lefthook | Lefthook |
|
|
1319
|
+
| **Versioning** | Changesets | Changesets (with workarounds) |
|
|
1320
|
+
| **Validation** | publint | publint |
|
|
1321
|
+
| **Dev execution** | tsx | Bun (native TS) |
|
|
1322
|
+
| **Native binaries** | N/A | bun --compile |
|
|
1323
|
+
| **CI setup** | 2 actions (pnpm + node) | 1 action (setup-bun) |
|
|
1324
|
+
|
|
1325
|
+
### Complexity Comparison
|
|
1326
|
+
|
|
1327
|
+
| Aspect | pnpm | Bun | Winner |
|
|
1328
|
+
| --- | --- | --- | --- |
|
|
1329
|
+
| **Config file count** | 6–8 | 4–5 | Bun |
|
|
1330
|
+
| **devDependencies** | 10–15 | 5–8 | Bun |
|
|
1331
|
+
| **Setup actions (CI)** | 2 | 1 | Bun |
|
|
1332
|
+
| **Lint + format config** | 2–4 files | 1 file | Bun |
|
|
1333
|
+
| **Build config** | Equivalent | Equivalent | Tie |
|
|
1334
|
+
| **Changesets workflow** | Works natively | Requires workarounds | pnpm |
|
|
1335
|
+
| **Dependency isolation** | Strict (content-addressable) | Loose (flat node_modules) | pnpm |
|
|
1336
|
+
| **Lockfile diffability** | YAML (diffable) | JSONC (diffable since Bun 1.2) | Tie |
|
|
1337
|
+
| **Ecosystem maturity** | Very mature | Growing rapidly (Anthropic backing) | pnpm |
|
|
1338
|
+
| **Test runner maturity** | Vitest (mature) | bun test (fake timers added, isolation gap) | pnpm |
|
|
1339
|
+
|
|
1340
|
+
### Speed Comparison
|
|
1341
|
+
|
|
1342
|
+
| Operation | pnpm + Node.js | Bun | Improvement |
|
|
1343
|
+
| --- | --- | --- | --- |
|
|
1344
|
+
| Package install | Baseline | 2–10x faster | Bun |
|
|
1345
|
+
| Build (tsdown vs Bunup) | ~200ms–1s | ~37ms | 5–25x (Bun) |
|
|
1346
|
+
| Test execution | Baseline | ~2x faster | Bun |
|
|
1347
|
+
| Lint + format | ~3–5s | ~200ms | 10–25x (Biome) |
|
|
1348
|
+
| CI total | Baseline | Significantly faster | Bun |
|
|
1349
|
+
| Dev server startup | ~50ms (tsx) | ~5ms | 10x (Bun) |
|
|
1350
|
+
|
|
1351
|
+
* * *
|
|
1352
|
+
|
|
1353
|
+
## Best Practices
|
|
1354
|
+
|
|
1355
|
+
1. **Use Bun workspaces** with `"workspaces"` in root `package.json`. Use `--cwd` to
|
|
1356
|
+
target specific workspaces when adding dependencies.
|
|
1357
|
+
|
|
1358
|
+
2. **Enable `isolatedDeclarations`** in `tsconfig.base.json` for dramatically faster DTS
|
|
1359
|
+
generation with Bunup.
|
|
1360
|
+
|
|
1361
|
+
3. **Use Bunup’s auto-exports** (`exports: true`) to keep `package.json` exports
|
|
1362
|
+
synchronized with build output.
|
|
1363
|
+
|
|
1364
|
+
4. **Use Biome for formatting + linting** via a single `biome.json`. Use `biome check`
|
|
1365
|
+
for combined format + lint in one pass.
|
|
1366
|
+
|
|
1367
|
+
5. **Add `bun update` after `changeset version`** to fix workspace reference resolution
|
|
1368
|
+
in the lockfile.
|
|
1369
|
+
|
|
1370
|
+
6. **Use `bun publish` per package** instead of `changeset publish` to ensure proper
|
|
1371
|
+
workspace resolution.
|
|
1372
|
+
|
|
1373
|
+
7. **Run CLI from source with `bun`** directly — no need for `tsx` or any TypeScript
|
|
1374
|
+
execution wrapper.
|
|
1375
|
+
|
|
1376
|
+
8. **Consider `bun --compile`** for distributing CLI tools as standalone executables,
|
|
1377
|
+
especially for users who don’t have Node.js or Bun installed.
|
|
1378
|
+
|
|
1379
|
+
9. **Use `bun test`** for testing — fake timers are now supported (v1.3.4+). Switch to
|
|
1380
|
+
Vitest only if you need test isolation, browser mode, or sharding.
|
|
1381
|
+
|
|
1382
|
+
10. **Keep the root `package.json` private** with `"private": true` and only workspace
|
|
1383
|
+
tooling.
|
|
1384
|
+
|
|
1385
|
+
11. **Scope your package names** with `@org/package-name` for GitHub Packages
|
|
1386
|
+
compatibility.
|
|
1387
|
+
|
|
1388
|
+
12. **Validate before publish** with `publint` in CI and before every release.
|
|
1389
|
+
|
|
1390
|
+
13. **Use lefthook** for git hooks with `biome check` in pre-commit (single command
|
|
1391
|
+
replaces separate format + lint hooks).
|
|
1392
|
+
|
|
1393
|
+
14. **Add the `"bun"` export condition** to let Bun consumers import TypeScript source
|
|
1394
|
+
directly, bypassing compiled output.
|
|
1395
|
+
|
|
1396
|
+
15. **Use dynamic git-based versioning** for dev builds — the pattern works identically
|
|
1397
|
+
with Bun, and `bun` replaces `tsx` for script execution.
|
|
1398
|
+
|
|
1399
|
+
* * *
|
|
1400
|
+
|
|
1401
|
+
## Open Research Questions
|
|
1402
|
+
|
|
1403
|
+
1. **Changesets Bun support**: As of Jan 2026, Changesets still has no native Bun
|
|
1404
|
+
workspace support. The `workspace:*` resolution issue remains open
|
|
1405
|
+
([#1468](https://github.com/changesets/changesets/issues/1468),
|
|
1406
|
+
[oven-sh/bun#16074](https://github.com/oven-sh/bun/issues/16074)). The workaround
|
|
1407
|
+
(`bun update` after `changeset version`, per-package `bun publish`) remains
|
|
1408
|
+
necessary. Monitor for a Bun-native alternative.
|
|
1409
|
+
|
|
1410
|
+
2. ~~**Bun test runner: fake timers**~~: **RESOLVED** in Bun v1.3.4 (Dec 2025).
|
|
1411
|
+
Jest-compatible fake timers are now supported.
|
|
1412
|
+
Remaining gaps: test isolation, browser mode, sharding.
|
|
1413
|
+
|
|
1414
|
+
3. ~~**Bun lockfile format**~~: **RESOLVED** in Bun 1.2. The text-based `bun.lock`
|
|
1415
|
+
(JSONC format) is now the default.
|
|
1416
|
+
It is diffable in code review and supported by GitHub rendering.
|
|
1417
|
+
The binary `bun.lockb` is deprecated.
|
|
1418
|
+
|
|
1419
|
+
4. **Biome plugin ecosystem**: Biome v2.0+ introduced plugins and type-aware linting
|
|
1420
|
+
without `tsc`. As of v2.3.x, the rule set continues growing (300+ rules,
|
|
1421
|
+
`noFloatingPromises` in nursery).
|
|
1422
|
+
Monitor for parity with security and accessibility ESLint plugins.
|
|
1423
|
+
Custom ESLint rules remain a reason to keep ESLint (see craft-agents-oss case study
|
|
1424
|
+
in Appendix G).
|
|
1425
|
+
|
|
1426
|
+
5. **Bun workspace strictness**: Bun still uses flat `node_modules` (no
|
|
1427
|
+
content-addressable store).
|
|
1428
|
+
Phantom dependencies remain possible.
|
|
1429
|
+
Monitor for improvements.
|
|
1430
|
+
|
|
1431
|
+
6. **`bun --compile` binary size**: Standalone executables still start at ~50–100MB
|
|
1432
|
+
(includes Bun runtime).
|
|
1433
|
+
No significant size reductions announced.
|
|
1434
|
+
The open issue ([#5854](https://github.com/oven-sh/bun/issues/5854)) remains tracked.
|
|
1435
|
+
The `--minify` and `--bytecode` flags help with startup time but not binary size.
|
|
1436
|
+
|
|
1437
|
+
7. **TypeScript 6.0/7.0**: TypeScript 6.0 is a “bridge” release; TypeScript 7.0 (Go
|
|
1438
|
+
rewrite) promises 10x faster builds.
|
|
1439
|
+
TS 7.0 is now available in Visual Studio 2026 Insiders preview with ~8x faster
|
|
1440
|
+
project load times. Install via `npm install -D @typescript/native-preview`. May
|
|
1441
|
+
change the DTS generation landscape for Bunup and tsdown once stable.
|
|
1442
|
+
|
|
1443
|
+
8. **Bunup maturity**: Bunup is iterating rapidly (0.16.x as of Jan 2026, up from 0.4.x
|
|
1444
|
+
a few months earlier).
|
|
1445
|
+
The API surface (`defineConfig`, `defineWorkspace`, `exports`, `compile`) appears
|
|
1446
|
+
stable, but pin versions carefully.
|
|
1447
|
+
|
|
1448
|
+
9. **Bun + Anthropic**: Bun was acquired by Anthropic (Dec 2025) and now powers Claude
|
|
1449
|
+
Code and Claude Agent SDK. This signals strong continued investment but also a shift
|
|
1450
|
+
toward AI-tooling use cases.
|
|
1451
|
+
Monitor whether the broader open-source community continues to benefit equally.
|
|
1452
|
+
|
|
1453
|
+
* * *
|
|
1454
|
+
|
|
1455
|
+
## Recommendations
|
|
1456
|
+
|
|
1457
|
+
### Summary
|
|
1458
|
+
|
|
1459
|
+
For projects that prioritize **speed and simplicity**, the full-Bun ecosystem provides a
|
|
1460
|
+
compelling alternative to the pnpm stack.
|
|
1461
|
+
Fewer configuration files, fewer dependencies, faster builds, and native TypeScript
|
|
1462
|
+
execution reduce overall complexity.
|
|
1463
|
+
|
|
1464
|
+
For projects that prioritize **ecosystem maturity and strictness**, the pnpm stack
|
|
1465
|
+
remains the safer choice, particularly due to pnpm’s strict dependency isolation,
|
|
1466
|
+
Changesets’ native support, ESLint’s mature plugin ecosystem, and Vitest’s comprehensive
|
|
1467
|
+
testing features.
|
|
1468
|
+
|
|
1469
|
+
### When to Choose the Bun Ecosystem
|
|
1470
|
+
|
|
1471
|
+
- New projects without legacy Node.js dependencies
|
|
1472
|
+
|
|
1473
|
+
- Projects that value speed and simplicity over ecosystem maturity
|
|
1474
|
+
|
|
1475
|
+
- CLI tools that could benefit from standalone executable distribution
|
|
1476
|
+
|
|
1477
|
+
- Teams comfortable with occasional workarounds for younger tooling
|
|
1478
|
+
|
|
1479
|
+
- Projects where the Bun runtime will be the primary execution environment
|
|
1480
|
+
|
|
1481
|
+
### When to Stay with pnpm
|
|
1482
|
+
|
|
1483
|
+
- Projects with strict dependency isolation requirements
|
|
1484
|
+
|
|
1485
|
+
- Projects requiring mature ESLint plugins (security, accessibility, custom rules)
|
|
1486
|
+
|
|
1487
|
+
- Projects needing advanced testing features (test isolation, browser mode, sharding)
|
|
1488
|
+
|
|
1489
|
+
- Projects that must support Node.js as the primary runtime
|
|
1490
|
+
|
|
1491
|
+
### Recommended Approach (Full-Bun)
|
|
1492
|
+
|
|
1493
|
+
1. **Initialize workspace** with Bun and packages in `packages/`
|
|
1494
|
+
|
|
1495
|
+
2. **Configure Bunup** for dual ESM/CJS output with auto-exports
|
|
1496
|
+
|
|
1497
|
+
3. **Enable `isolatedDeclarations`** in TypeScript config
|
|
1498
|
+
|
|
1499
|
+
4. **Set up Biome** for formatting and linting
|
|
1500
|
+
|
|
1501
|
+
5. **Add Changesets** with the Bun workaround scripts
|
|
1502
|
+
|
|
1503
|
+
6. **Configure lefthook** with `biome check` in pre-commit
|
|
1504
|
+
|
|
1505
|
+
7. **Set up CI** with `oven-sh/setup-bun@v2`
|
|
1506
|
+
|
|
1507
|
+
8. **Configure release workflow** with custom publish scripts
|
|
1508
|
+
|
|
1509
|
+
9. **Validate with publint** before every release
|
|
1510
|
+
|
|
1511
|
+
10. **Optionally configure `bun --compile`** for standalone executable distribution
|
|
1512
|
+
|
|
1513
|
+
* * *
|
|
1514
|
+
|
|
1515
|
+
## References
|
|
1516
|
+
|
|
1517
|
+
### Official Documentation
|
|
1518
|
+
|
|
1519
|
+
- [Bun Documentation](https://bun.sh/docs)
|
|
1520
|
+
|
|
1521
|
+
- [Bun Workspaces](https://bun.sh/docs/install/workspaces)
|
|
1522
|
+
|
|
1523
|
+
- [Bun TypeScript](https://bun.sh/docs/typescript)
|
|
1524
|
+
|
|
1525
|
+
- [Bun Test Runner](https://bun.sh/docs/cli/test)
|
|
1526
|
+
|
|
1527
|
+
- [Bun Bundler](https://bun.sh/docs/bundler)
|
|
1528
|
+
|
|
1529
|
+
- [Bun Single-file Executables](https://bun.sh/docs/bundler/executables)
|
|
1530
|
+
|
|
1531
|
+
- [Bunup Documentation](https://bunup.dev/)
|
|
1532
|
+
|
|
1533
|
+
- [Biome Documentation](https://biomejs.dev/)
|
|
1534
|
+
|
|
1535
|
+
- [Changesets GitHub](https://github.com/changesets/changesets)
|
|
1536
|
+
|
|
1537
|
+
- [publint Documentation](https://publint.dev/docs/)
|
|
1538
|
+
|
|
1539
|
+
### Guides & Articles
|
|
1540
|
+
|
|
1541
|
+
- [Building a TypeScript Library in 2026 with Bunup](https://dev.to/arshadyaseen/building-a-typescript-library-in-2026-with-bunup-3bmg)
|
|
1542
|
+
|
|
1543
|
+
- [Setting up Changesets with Bun Workspaces](https://ianm.com/posts/2025-08-18-setting-up-changesets-with-bun-workspaces)
|
|
1544
|
+
|
|
1545
|
+
- [Monorepo with Bun](https://dev.to/vikkio88/monorepo-with-bun-474n)
|
|
1546
|
+
|
|
1547
|
+
- [Guide to Monorepo Setup: NPM, Yarn, Pnpm & Bun Workspaces](https://jsdev.space/mastering-monorepos/)
|
|
1548
|
+
|
|
1549
|
+
- [Setting up a Bun Workspace](https://medium.com/@oluijks/setting-up-a-bun-workspace-23543df61e52)
|
|
1550
|
+
|
|
1551
|
+
- [Biome vs ESLint + Prettier](https://medium.com/better-dev-nextjs-react/biome-vs-eslint-prettier-the-2025-linting-revolution-you-need-to-know-about-ec01c5d5b6c8)
|
|
1552
|
+
|
|
1553
|
+
- [Migrating from Prettier and ESLint to BiomeJS](https://blog.appsignal.com/2025/05/07/migrating-a-javascript-project-from-prettier-and-eslint-to-biomejs.html)
|
|
1554
|
+
|
|
1555
|
+
- [CommonJS is not going away (Bun Blog)](https://bun.sh/blog/commonjs-is-not-going-away)
|
|
1556
|
+
|
|
1557
|
+
- [Bun’s New Text-Based Lockfile](https://bun.com/blog/bun-lock-text-lockfile)
|
|
1558
|
+
|
|
1559
|
+
- [Fake Timers in Bun Test](https://js2brain.com/blog/fake-timers-in-bun-test/)
|
|
1560
|
+
|
|
1561
|
+
- [Bun v1.3.4 Release (Fake Timers)](https://bun.com/blog/bun-v1.3.4)
|
|
1562
|
+
|
|
1563
|
+
- [Bun Cross-Compilation](https://developer.mamezou-tech.com/en/blogs/2024/05/20/bun-cross-compile/)
|
|
1564
|
+
|
|
1565
|
+
- [Bun Joins Anthropic](https://www.infoq.com/news/2026/01/bun-v3-1-release/)
|
|
1566
|
+
|
|
1567
|
+
### GitHub Actions
|
|
1568
|
+
|
|
1569
|
+
- [oven-sh/setup-bun](https://github.com/oven-sh/setup-bun)
|
|
1570
|
+
|
|
1571
|
+
- [changesets/action](https://github.com/changesets/action)
|
|
1572
|
+
|
|
1573
|
+
* * *
|
|
1574
|
+
|
|
1575
|
+
## Appendices
|
|
1576
|
+
|
|
1577
|
+
### Appendix A: Complete Package package.json Example
|
|
1578
|
+
|
|
1579
|
+
```json
|
|
1580
|
+
{
|
|
1581
|
+
"name": "@scope/package-name",
|
|
1582
|
+
"version": "0.1.0",
|
|
1583
|
+
"description": "Package description",
|
|
1584
|
+
"license": "MIT",
|
|
1585
|
+
"type": "module",
|
|
1586
|
+
"sideEffects": false,
|
|
1587
|
+
"main": "./dist/index.cjs",
|
|
1588
|
+
"module": "./dist/index.js",
|
|
1589
|
+
"types": "./dist/index.d.ts",
|
|
1590
|
+
"exports": {
|
|
1591
|
+
".": {
|
|
1592
|
+
"bun": "./src/index.ts",
|
|
1593
|
+
"import": {
|
|
1594
|
+
"types": "./dist/index.d.ts",
|
|
1595
|
+
"default": "./dist/index.js"
|
|
1596
|
+
},
|
|
1597
|
+
"require": {
|
|
1598
|
+
"types": "./dist/index.d.cts",
|
|
1599
|
+
"default": "./dist/index.cjs"
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
"./cli": {
|
|
1603
|
+
"bun": "./src/cli/index.ts",
|
|
1604
|
+
"import": {
|
|
1605
|
+
"types": "./dist/cli.d.ts",
|
|
1606
|
+
"default": "./dist/cli.js"
|
|
1607
|
+
},
|
|
1608
|
+
"require": {
|
|
1609
|
+
"types": "./dist/cli.d.cts",
|
|
1610
|
+
"default": "./dist/cli.cjs"
|
|
1611
|
+
}
|
|
1612
|
+
},
|
|
1613
|
+
"./package.json": "./package.json"
|
|
1614
|
+
},
|
|
1615
|
+
"bin": {
|
|
1616
|
+
"package-name": "./dist/bin.js"
|
|
1617
|
+
},
|
|
1618
|
+
"files": ["dist"],
|
|
1619
|
+
"scripts": {
|
|
1620
|
+
"build": "bunup",
|
|
1621
|
+
"dev": "bunup --watch",
|
|
1622
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
1623
|
+
"test": "bun test",
|
|
1624
|
+
"publint": "bunx publint",
|
|
1625
|
+
"prepack": "bun run build"
|
|
1626
|
+
},
|
|
1627
|
+
"dependencies": {},
|
|
1628
|
+
"peerDependencies": {
|
|
1629
|
+
"optional-sdk": "^1.0.0"
|
|
1630
|
+
},
|
|
1631
|
+
"peerDependenciesMeta": {
|
|
1632
|
+
"optional-sdk": { "optional": true }
|
|
1633
|
+
},
|
|
1634
|
+
"devDependencies": {
|
|
1635
|
+
"bun-types": "latest",
|
|
1636
|
+
"bunup": "^0.16.0",
|
|
1637
|
+
"typescript": "^5.9.0"
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
### Appendix B: Root package.json Example
|
|
1643
|
+
|
|
1644
|
+
```json
|
|
1645
|
+
{
|
|
1646
|
+
"name": "project-workspace",
|
|
1647
|
+
"private": true,
|
|
1648
|
+
"workspaces": [
|
|
1649
|
+
"packages/*"
|
|
1650
|
+
],
|
|
1651
|
+
"scripts": {
|
|
1652
|
+
"build": "bunup",
|
|
1653
|
+
"typecheck": "tsc -b",
|
|
1654
|
+
"test": "bun test",
|
|
1655
|
+
"publint": "bunx publint",
|
|
1656
|
+
"format": "biome format --write .",
|
|
1657
|
+
"format:check": "biome format .",
|
|
1658
|
+
"lint": "biome lint --write .",
|
|
1659
|
+
"lint:check": "biome lint .",
|
|
1660
|
+
"check": "biome check --write .",
|
|
1661
|
+
"check:ci": "biome check .",
|
|
1662
|
+
"prepare": "lefthook install",
|
|
1663
|
+
"changeset": "changeset",
|
|
1664
|
+
"version-packages": "changeset version && bun update",
|
|
1665
|
+
"release": "bun run build && bunx publint && bun run publish-packages",
|
|
1666
|
+
"publish-packages": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag",
|
|
1667
|
+
"upgrade:check": "bunx npm-check-updates --format group",
|
|
1668
|
+
"upgrade": "bunx npm-check-updates --target minor -u && bun install && bun test",
|
|
1669
|
+
"upgrade:major": "bunx npm-check-updates --target latest --interactive --format group"
|
|
1670
|
+
},
|
|
1671
|
+
"devDependencies": {
|
|
1672
|
+
"@biomejs/biome": "^2.3.0",
|
|
1673
|
+
"@changesets/cli": "^2.29.0",
|
|
1674
|
+
"@changesets/changelog-github": "^0.5.0",
|
|
1675
|
+
"lefthook": "^2.0.0",
|
|
1676
|
+
"publint": "^0.3.0",
|
|
1677
|
+
"typescript": "^5.9.0"
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
### Appendix C: Complete biome.json Example
|
|
1683
|
+
|
|
1684
|
+
```json
|
|
1685
|
+
{
|
|
1686
|
+
"$schema": "https://biomejs.dev/schemas/2.3.0/schema.json",
|
|
1687
|
+
"organizeImports": {
|
|
1688
|
+
"enabled": true
|
|
1689
|
+
},
|
|
1690
|
+
"formatter": {
|
|
1691
|
+
"enabled": true,
|
|
1692
|
+
"indentStyle": "space",
|
|
1693
|
+
"indentWidth": 2,
|
|
1694
|
+
"lineWidth": 100,
|
|
1695
|
+
"lineEnding": "lf"
|
|
1696
|
+
},
|
|
1697
|
+
"javascript": {
|
|
1698
|
+
"formatter": {
|
|
1699
|
+
"quoteStyle": "single",
|
|
1700
|
+
"trailingCommas": "all",
|
|
1701
|
+
"semicolons": "always",
|
|
1702
|
+
"arrowParentheses": "always"
|
|
1703
|
+
}
|
|
1704
|
+
},
|
|
1705
|
+
"linter": {
|
|
1706
|
+
"enabled": true,
|
|
1707
|
+
"rules": {
|
|
1708
|
+
"recommended": true,
|
|
1709
|
+
"correctness": {
|
|
1710
|
+
"noUnusedVariables": "error",
|
|
1711
|
+
"noUnusedImports": "error",
|
|
1712
|
+
"useExhaustiveDependencies": "warn"
|
|
1713
|
+
},
|
|
1714
|
+
"suspicious": {
|
|
1715
|
+
"noExplicitAny": "warn",
|
|
1716
|
+
"noConfusingVoidType": "error"
|
|
1717
|
+
},
|
|
1718
|
+
"style": {
|
|
1719
|
+
"useConst": "error",
|
|
1720
|
+
"noNonNullAssertion": "warn",
|
|
1721
|
+
"useImportType": "error"
|
|
1722
|
+
},
|
|
1723
|
+
"complexity": {
|
|
1724
|
+
"noForEach": "warn"
|
|
1725
|
+
},
|
|
1726
|
+
"nursery": {
|
|
1727
|
+
"noFloatingPromises": "error"
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
},
|
|
1731
|
+
"overrides": [
|
|
1732
|
+
{
|
|
1733
|
+
"include": ["**/*.test.ts", "**/*.spec.ts", "**/tests/**/*.ts"],
|
|
1734
|
+
"linter": {
|
|
1735
|
+
"rules": {
|
|
1736
|
+
"suspicious": {
|
|
1737
|
+
"noExplicitAny": "off"
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
"include": ["**/scripts/**/*.ts"],
|
|
1744
|
+
"linter": {
|
|
1745
|
+
"rules": {
|
|
1746
|
+
"suspicious": {
|
|
1747
|
+
"noExplicitAny": "off"
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
],
|
|
1753
|
+
"files": {
|
|
1754
|
+
"ignore": [
|
|
1755
|
+
"dist",
|
|
1756
|
+
"node_modules",
|
|
1757
|
+
".changeset",
|
|
1758
|
+
"bun.lock",
|
|
1759
|
+
"coverage"
|
|
1760
|
+
]
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
### Appendix D: Complete bunup.config.ts Example
|
|
1766
|
+
|
|
1767
|
+
```typescript
|
|
1768
|
+
// bunup.config.ts (workspace root)
|
|
1769
|
+
import { defineWorkspace } from 'bunup';
|
|
1770
|
+
|
|
1771
|
+
export default defineWorkspace([
|
|
1772
|
+
{
|
|
1773
|
+
name: 'core',
|
|
1774
|
+
root: 'packages/core',
|
|
1775
|
+
config: {
|
|
1776
|
+
entry: ['src/index.ts'],
|
|
1777
|
+
format: ['esm', 'cjs'],
|
|
1778
|
+
dts: true,
|
|
1779
|
+
clean: true,
|
|
1780
|
+
sourcemap: 'linked',
|
|
1781
|
+
exports: true,
|
|
1782
|
+
},
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
name: 'cli',
|
|
1786
|
+
root: 'packages/cli',
|
|
1787
|
+
config: [
|
|
1788
|
+
{
|
|
1789
|
+
name: 'library',
|
|
1790
|
+
entry: ['src/index.ts'],
|
|
1791
|
+
format: ['esm', 'cjs'],
|
|
1792
|
+
dts: true,
|
|
1793
|
+
clean: true,
|
|
1794
|
+
exports: true,
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
name: 'binary',
|
|
1798
|
+
entry: ['src/bin.ts'],
|
|
1799
|
+
format: ['esm'],
|
|
1800
|
+
banner: '"#!/usr/bin/env bun";',
|
|
1801
|
+
},
|
|
1802
|
+
],
|
|
1803
|
+
},
|
|
1804
|
+
]);
|
|
1805
|
+
```
|
|
1806
|
+
|
|
1807
|
+
**Single-package config** (`packages/core/bunup.config.ts`):
|
|
1808
|
+
|
|
1809
|
+
```typescript
|
|
1810
|
+
import { defineConfig } from 'bunup';
|
|
1811
|
+
|
|
1812
|
+
export default defineConfig({
|
|
1813
|
+
entry: {
|
|
1814
|
+
index: 'src/index.ts',
|
|
1815
|
+
cli: 'src/cli/index.ts',
|
|
1816
|
+
bin: 'src/bin.ts',
|
|
1817
|
+
},
|
|
1818
|
+
format: ['esm', 'cjs'],
|
|
1819
|
+
dts: true,
|
|
1820
|
+
clean: true,
|
|
1821
|
+
sourcemap: 'linked',
|
|
1822
|
+
exports: true,
|
|
1823
|
+
define: {
|
|
1824
|
+
__VERSION__: JSON.stringify(process.env.PROJECT_VERSION ?? 'development'),
|
|
1825
|
+
},
|
|
1826
|
+
});
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
### Appendix E: Complete lefthook.yml Example
|
|
1830
|
+
|
|
1831
|
+
```yaml
|
|
1832
|
+
# lefthook.yml
|
|
1833
|
+
# Git hooks for code quality (Bun + Biome edition)
|
|
1834
|
+
# Pre-commit: Fast checks with auto-fix (target: <1 second)
|
|
1835
|
+
# Pre-push: Full test validation with caching
|
|
1836
|
+
|
|
1837
|
+
pre-commit:
|
|
1838
|
+
parallel: true
|
|
1839
|
+
|
|
1840
|
+
commands:
|
|
1841
|
+
# Format + lint with Biome (~200ms for staged files)
|
|
1842
|
+
check:
|
|
1843
|
+
glob: '*.{js,ts,tsx,json,css}'
|
|
1844
|
+
run: bunx biome check --write {staged_files}
|
|
1845
|
+
stage_fixed: true
|
|
1846
|
+
priority: 1
|
|
1847
|
+
|
|
1848
|
+
# Type check with incremental mode (~2s)
|
|
1849
|
+
typecheck:
|
|
1850
|
+
glob: '*.{ts,tsx}'
|
|
1851
|
+
run: bunx tsc --noEmit --incremental
|
|
1852
|
+
priority: 2
|
|
1853
|
+
|
|
1854
|
+
pre-push:
|
|
1855
|
+
commands:
|
|
1856
|
+
verify-tests:
|
|
1857
|
+
run: |
|
|
1858
|
+
COMMIT_HASH=$(git rev-parse HEAD)
|
|
1859
|
+
CACHE_DIR="node_modules/.test-cache"
|
|
1860
|
+
CACHE_FILE="$CACHE_DIR/$COMMIT_HASH"
|
|
1861
|
+
|
|
1862
|
+
# Check for uncommitted changes
|
|
1863
|
+
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
1864
|
+
bun test
|
|
1865
|
+
exit $?
|
|
1866
|
+
fi
|
|
1867
|
+
|
|
1868
|
+
# Check cache
|
|
1869
|
+
if [ -f "$CACHE_FILE" ]; then
|
|
1870
|
+
SHORT_HASH=$(echo "$COMMIT_HASH" | cut -c1-8)
|
|
1871
|
+
echo "Tests already passed for commit $SHORT_HASH"
|
|
1872
|
+
exit 0
|
|
1873
|
+
fi
|
|
1874
|
+
|
|
1875
|
+
# Run tests
|
|
1876
|
+
bun test
|
|
1877
|
+
|
|
1878
|
+
# Cache on success
|
|
1879
|
+
if [ $? -eq 0 ]; then
|
|
1880
|
+
mkdir -p "$CACHE_DIR"
|
|
1881
|
+
touch "$CACHE_FILE"
|
|
1882
|
+
exit 0
|
|
1883
|
+
else
|
|
1884
|
+
echo "Tests failed - push blocked"
|
|
1885
|
+
echo "Bypass with: git push --no-verify"
|
|
1886
|
+
exit 1
|
|
1887
|
+
fi
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
### Appendix F: Standalone Executable Build Example
|
|
1891
|
+
|
|
1892
|
+
```typescript
|
|
1893
|
+
// bunup.config.ts for CLI with standalone executable
|
|
1894
|
+
import { defineConfig } from 'bunup';
|
|
1895
|
+
|
|
1896
|
+
export default defineConfig([
|
|
1897
|
+
// Standard library build
|
|
1898
|
+
{
|
|
1899
|
+
name: 'library',
|
|
1900
|
+
entry: ['src/index.ts'],
|
|
1901
|
+
format: ['esm', 'cjs'],
|
|
1902
|
+
dts: true,
|
|
1903
|
+
exports: true,
|
|
1904
|
+
},
|
|
1905
|
+
// Standalone executable
|
|
1906
|
+
{
|
|
1907
|
+
name: 'standalone',
|
|
1908
|
+
entry: 'src/bin.ts',
|
|
1909
|
+
compile: {
|
|
1910
|
+
target: 'bun-linux-x64',
|
|
1911
|
+
outfile: './bin/myapp-linux',
|
|
1912
|
+
},
|
|
1913
|
+
},
|
|
1914
|
+
]);
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
**Multi-platform build script** (`package.json`):
|
|
1918
|
+
|
|
1919
|
+
```json
|
|
1920
|
+
{
|
|
1921
|
+
"scripts": {
|
|
1922
|
+
"build:binary": "bun run build:binary:linux && bun run build:binary:mac && bun run build:binary:windows",
|
|
1923
|
+
"build:binary:linux": "bun build --compile --target=bun-linux-x64 src/bin.ts --outfile bin/myapp-linux",
|
|
1924
|
+
"build:binary:mac": "bun build --compile --target=bun-darwin-arm64 src/bin.ts --outfile bin/myapp-darwin",
|
|
1925
|
+
"build:binary:windows": "bun build --compile --target=bun-windows-x64 src/bin.ts --outfile bin/myapp.exe"
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
### Appendix G: Case Study — craft-agents-oss (Real-World Bun Monorepo)
|
|
1931
|
+
|
|
1932
|
+
**Repository**:
|
|
1933
|
+
[lukilabs/craft-agents-oss](https://github.com/lukilabs/craft-agents-oss) (Apache 2.0,
|
|
1934
|
+
Electron desktop app for AI agent interactions)
|
|
1935
|
+
|
|
1936
|
+
This appendix documents how a real-world, production Bun monorepo is structured.
|
|
1937
|
+
It serves as a concrete reference implementation for the patterns described in this
|
|
1938
|
+
research.
|
|
1939
|
+
|
|
1940
|
+
#### Structure Overview
|
|
1941
|
+
|
|
1942
|
+
```
|
|
1943
|
+
craft-agents-oss/
|
|
1944
|
+
├── apps/
|
|
1945
|
+
│ ├── electron/ # Electron desktop app (primary product)
|
|
1946
|
+
│ └── viewer/ # Web viewer for sessions
|
|
1947
|
+
├── packages/
|
|
1948
|
+
│ ├── core/ # Types and core utilities (no deps)
|
|
1949
|
+
│ ├── shared/ # Business logic (agent, auth, config, MCP)
|
|
1950
|
+
│ └── ui/ # React UI components
|
|
1951
|
+
├── scripts/ # Bun build/dev scripts (TypeScript)
|
|
1952
|
+
├── biome.json # (not present — uses ESLint)
|
|
1953
|
+
├── bunfig.toml # Minimal: preload only
|
|
1954
|
+
├── bun.lock # Text lockfile (JSONC, default since Bun 1.2)
|
|
1955
|
+
├── package.json # Root workspace config
|
|
1956
|
+
└── tsconfig.json # Root TypeScript config
|
|
1957
|
+
```
|
|
1958
|
+
|
|
1959
|
+
**Workspace configuration** (root `package.json`):
|
|
1960
|
+
|
|
1961
|
+
```json
|
|
1962
|
+
{
|
|
1963
|
+
"name": "craft-agent",
|
|
1964
|
+
"version": "0.2.34",
|
|
1965
|
+
"type": "module",
|
|
1966
|
+
"private": true,
|
|
1967
|
+
"workspaces": [
|
|
1968
|
+
"packages/*",
|
|
1969
|
+
"apps/*",
|
|
1970
|
+
"!apps/online-docs"
|
|
1971
|
+
]
|
|
1972
|
+
}
|
|
1973
|
+
```
|
|
1974
|
+
|
|
1975
|
+
#### Key Patterns Worth Adopting
|
|
1976
|
+
|
|
1977
|
+
**1. Source-Level Exports (No Build Step for Internal Consumption)**
|
|
1978
|
+
|
|
1979
|
+
All internal packages export TypeScript source directly — no `dist/` directory:
|
|
1980
|
+
|
|
1981
|
+
```json
|
|
1982
|
+
// packages/shared/package.json — 27 subpath exports
|
|
1983
|
+
{
|
|
1984
|
+
"exports": {
|
|
1985
|
+
".": "./src/index.ts",
|
|
1986
|
+
"./agent": "./src/agent/index.ts",
|
|
1987
|
+
"./auth": "./src/auth/index.ts",
|
|
1988
|
+
"./config": "./src/config/index.ts",
|
|
1989
|
+
"./mcp": "./src/mcp/index.ts",
|
|
1990
|
+
"./sessions": "./src/sessions/index.ts",
|
|
1991
|
+
// ... 21 more subpaths
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
```
|
|
1995
|
+
|
|
1996
|
+
This works because Bun natively imports TypeScript.
|
|
1997
|
+
Apps consume packages directly from source, eliminating build-watch complexity for local
|
|
1998
|
+
development.
|
|
1999
|
+
Published packages would need compiled output for Node.js consumers, but for
|
|
2000
|
+
internal-only packages this is ideal.
|
|
2001
|
+
|
|
2002
|
+
**2. TypeScript Build Scripts (No Shell Scripts)**
|
|
2003
|
+
|
|
2004
|
+
All build orchestration is in TypeScript, executed directly by Bun:
|
|
2005
|
+
|
|
2006
|
+
```json
|
|
2007
|
+
{
|
|
2008
|
+
"scripts": {
|
|
2009
|
+
"electron:build:main": "bun run scripts/electron-build-main.ts",
|
|
2010
|
+
"electron:build:preload": "bun run scripts/electron-build-preload.ts",
|
|
2011
|
+
"electron:build:renderer": "bun run scripts/electron-build-renderer.ts",
|
|
2012
|
+
"electron:dev": "bun run scripts/electron-dev.ts"
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
```
|
|
2016
|
+
|
|
2017
|
+
This eliminates platform-specific shell script issues and provides type safety for build
|
|
2018
|
+
logic.
|
|
2019
|
+
|
|
2020
|
+
**3. Hybrid Build Architecture**
|
|
2021
|
+
|
|
2022
|
+
Uses esbuild for Node.js targets (Electron main/preload), Vite for browser targets
|
|
2023
|
+
(renderer, viewer), and TypeScript for type checking only (`noEmit: true`). Bun serves
|
|
2024
|
+
as the orchestrator but does not replace specialized build tools.
|
|
2025
|
+
|
|
2026
|
+
**4. Global Preload via bunfig.toml**
|
|
2027
|
+
|
|
2028
|
+
```toml
|
|
2029
|
+
# bunfig.toml
|
|
2030
|
+
preload = ["./packages/shared/src/network-interceptor.ts"]
|
|
2031
|
+
```
|
|
2032
|
+
|
|
2033
|
+
Preloads a network interceptor for API error capture and MCP schema injection across all
|
|
2034
|
+
Bun processes. This is a Bun-unique pattern for cross-cutting concerns.
|
|
2035
|
+
|
|
2036
|
+
**5. Single-Source Version Management**
|
|
2037
|
+
|
|
2038
|
+
Version lives in one TypeScript constant (`packages/shared/src/version/app-version.ts`),
|
|
2039
|
+
synced to all `package.json` files via `bun run scripts/sync-version.ts`. Avoids
|
|
2040
|
+
Changesets entirely for a private monorepo that distributes as a single Electron app.
|
|
2041
|
+
|
|
2042
|
+
**6. Custom ESLint Rules for Architectural Constraints**
|
|
2043
|
+
|
|
2044
|
+
Instead of Biome, this project uses ESLint with 4 custom rules:
|
|
2045
|
+
|
|
2046
|
+
- `no-direct-navigation-state` — enforces navigation state abstraction
|
|
2047
|
+
- `no-localstorage` — prevents browser localStorage usage (Bun incompatible)
|
|
2048
|
+
- `no-direct-platform-check` — enforces platform detection abstraction
|
|
2049
|
+
- `no-hardcoded-path-separator` — enforces cross-platform paths
|
|
2050
|
+
|
|
2051
|
+
This demonstrates that ESLint’s custom rule ecosystem remains valuable for
|
|
2052
|
+
domain-specific architectural constraints that Biome cannot yet replicate.
|
|
2053
|
+
|
|
2054
|
+
**7. Inter-Package Dependencies (Workspace Protocol)**
|
|
2055
|
+
|
|
2056
|
+
```
|
|
2057
|
+
apps/electron → @craft-agent/core, shared, ui (workspace:*)
|
|
2058
|
+
apps/viewer → @craft-agent/core, ui (workspace:*)
|
|
2059
|
+
packages/shared → @craft-agent/core (workspace:*)
|
|
2060
|
+
packages/ui → @craft-agent/core (workspace:*)
|
|
2061
|
+
packages/core → (no internal deps)
|
|
2062
|
+
```
|
|
2063
|
+
|
|
2064
|
+
Clean dependency graph with `core` as the leaf package.
|
|
2065
|
+
|
|
2066
|
+
#### What This Repo Does NOT Have
|
|
2067
|
+
|
|
2068
|
+
- **No Bunup**: Uses esbuild + Vite directly (hybrid approach)
|
|
2069
|
+
- **No Biome**: Uses ESLint (needed custom rules)
|
|
2070
|
+
- **No Changesets**: Single-version private app, manual sync script
|
|
2071
|
+
- **No CI/CD workflows**: No `.github/workflows/` directory
|
|
2072
|
+
- **No publint**: Not publishing to npm
|
|
2073
|
+
- **No git hooks**: No lefthook, husky, or pre-commit
|
|
2074
|
+
- **No tests**: No test files found in the codebase
|
|
2075
|
+
- **No `bun --compile`**: Ships Bun binary in vendor/ directory via electron-builder
|
|
2076
|
+
|
|
2077
|
+
#### Lessons for Published Library Monorepos
|
|
2078
|
+
|
|
2079
|
+
This project optimizes for a different use case (private Electron app) than a published
|
|
2080
|
+
npm library monorepo.
|
|
2081
|
+
Key takeaways:
|
|
2082
|
+
|
|
2083
|
+
| craft-agents-oss Pattern | Adaptation for Published Library |
|
|
2084
|
+
| --- | --- |
|
|
2085
|
+
| Source-level exports | Add `"bun"` condition + compiled dist for Node.js consumers |
|
|
2086
|
+
| esbuild + Vite build | Use Bunup for library/CLI packages |
|
|
2087
|
+
| Manual version sync | Use Changesets with Bun workarounds |
|
|
2088
|
+
| No CI | Add GitHub Actions with `oven-sh/setup-bun@v2` |
|
|
2089
|
+
| ESLint with custom rules | Use Biome unless custom rules needed |
|
|
2090
|
+
| No tests | Add `bun test` suite |
|
|
2091
|
+
| No publint | Add publint validation |
|
|
2092
|
+
| No git hooks | Add lefthook with `biome check` |
|
|
2093
|
+
|
|
2094
|
+
The source-level exports pattern and TypeScript build scripts are directly applicable.
|
|
2095
|
+
The hybrid esbuild/Vite approach is more relevant for Electron/full-stack apps than for
|
|
2096
|
+
pure library packages where Bunup would be simpler.
|