get-tbd 0.1.21 → 0.1.23
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 +17 -19
- package/dist/bin.mjs +181 -12
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +110 -644
- package/dist/cli.mjs.map +1 -1
- package/dist/config-CB1tcqTZ.mjs +3 -0
- package/dist/config-CmEAGaxz.mjs +637 -0
- package/dist/config-CmEAGaxz.mjs.map +1 -0
- package/dist/docs/README.md +17 -19
- package/dist/docs/guidelines/bun-monorepo-patterns.md +816 -80
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +586 -16
- package/dist/docs/guidelines/python-cli-patterns.md +2 -2
- package/dist/docs/guidelines/tbd-sync-troubleshooting.md +27 -0
- package/dist/docs/guidelines/typescript-cli-tool-rules.md +465 -196
- package/dist/docs/tbd-design.md +86 -46
- package/dist/docs/tbd-docs.md +0 -6
- package/dist/id-mapping-0-R0X8zb.mjs +3 -0
- package/dist/{id-mapping-CD5c_ZVA.mjs → id-mapping-JGow6Jk4.mjs} +57 -3
- package/dist/{id-mapping-CD5c_ZVA.mjs.map → id-mapping-JGow6Jk4.mjs.map} +1 -1
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +2 -2
- package/dist/{src-BjMRpmMh.mjs → src-7qUDeWJf.mjs} +3 -3
- package/dist/{src-BjMRpmMh.mjs.map → src-7qUDeWJf.mjs.map} +1 -1
- package/dist/tbd +181 -12
- package/dist/{yaml-utils-x_kr2IId.mjs → yaml-utils-U7l9hhkh.mjs} +7 -1
- package/dist/yaml-utils-U7l9hhkh.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/id-mapping-BqSnxlxk.mjs +0 -3
- package/dist/yaml-utils-x_kr2IId.mjs.map +0 -1
|
@@ -5,7 +5,7 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
|
5
5
|
---
|
|
6
6
|
# Bun Monorepo Patterns
|
|
7
7
|
|
|
8
|
-
**Last Updated**: 2026-02-
|
|
8
|
+
**Last Updated**: 2026-02-18
|
|
9
9
|
|
|
10
10
|
**Related**:
|
|
11
11
|
|
|
@@ -24,10 +24,13 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
|
24
24
|
| Tool / Package | Version | Check For Updates |
|
|
25
25
|
| --- | --- | --- |
|
|
26
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
|
|
28
|
-
| **Bunup** | ^0.16.
|
|
29
|
-
| **Biome** | ^2.3.
|
|
30
|
-
| **@changesets/cli** | ^2.29.
|
|
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.22 | [npmjs.com/package/bunup](https://www.npmjs.com/package/bunup) — Build tool for TS libs. Rapid iteration (0.16.22 latest). |
|
|
29
|
+
| **Biome** | ^2.3.14 | [biomejs.dev](https://biomejs.dev/) — Formatter + linter. v2.0 added plugins and type-aware linting; 2.3.14 is latest stable. |
|
|
30
|
+
| **@changesets/cli** | ^2.29.8 | [github.com/changesets/changesets/releases](https://github.com/changesets/changesets/releases) — 2.29.8 latest. No native Bun support yet. |
|
|
31
|
+
| **bun-types** | ^1.3.8 | [npmjs.com/package/bun-types](https://www.npmjs.com/package/bun-types) — Type definitions for Bun runtime APIs. |
|
|
32
|
+
| **tryscript** | ^0.1.6 | [npmjs.com/package/tryscript](https://www.npmjs.com/package/tryscript) — Golden/CLI testing via Markdown test files. |
|
|
33
|
+
| **flowmark** | latest | [github.com/jlevy/flowmark](https://github.com/jlevy/flowmark) — Markdown auto-formatter (via `uvx`). |
|
|
31
34
|
| **publint** | ^0.3.17 | [npmjs.com/package/publint](https://www.npmjs.com/package/publint) — 0.3.17 latest |
|
|
32
35
|
| **actions/checkout** | v6 | [github.com/actions/checkout/releases](https://github.com/actions/checkout/releases) |
|
|
33
36
|
| **oven-sh/setup-bun** | v2 | [github.com/oven-sh/setup-bun](https://github.com/oven-sh/setup-bun) — Verified on GitHub Marketplace |
|
|
@@ -69,9 +72,9 @@ It serves as a direct comparison to the companion document on pnpm-based monorep
|
|
|
69
72
|
covering the same architectural scope but using Bun-native tooling wherever possible.
|
|
70
73
|
|
|
71
74
|
The recommended stack uses **Bun workspaces** for dependency management, **Bunup** for
|
|
72
|
-
building ESM/CJS
|
|
73
|
-
workarounds) for versioning and release automation, **Biome** for formatting
|
|
74
|
-
linting, **publint** for package validation, and **lefthook** for git hooks.
|
|
75
|
+
building ESM (or dual ESM/CJS) outputs with TypeScript declarations, **Changesets**
|
|
76
|
+
(with Bun workarounds) for versioning and release automation, **Biome** for formatting
|
|
77
|
+
and linting, **publint** for package validation, and **lefthook** for git hooks.
|
|
75
78
|
The architecture also covers Bun’s unique capability for **compiling standalone
|
|
76
79
|
executables** — a native binary distribution path unavailable in the pnpm ecosystem.
|
|
77
80
|
|
|
@@ -281,6 +284,24 @@ primarily serves IDE support, type checking, and declaration generation.
|
|
|
281
284
|
projects because Bunup’s DTS generation is dramatically faster when this is enabled (it
|
|
282
285
|
avoids invoking the full TypeScript compiler).
|
|
283
286
|
|
|
287
|
+
**Override at package level**: For packages with complex types that cannot satisfy
|
|
288
|
+
isolated declaration emit, override to `false` at the package level while keeping `true`
|
|
289
|
+
in the base config:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"extends": "../../tsconfig.base.json",
|
|
294
|
+
"compilerOptions": {
|
|
295
|
+
"types": ["bun-types"],
|
|
296
|
+
"noEmit": true,
|
|
297
|
+
"isolatedDeclarations": false
|
|
298
|
+
},
|
|
299
|
+
"include": ["src", "tests", "*.config.ts"]
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This lets most packages benefit from fast DTS generation while allowing exceptions.
|
|
304
|
+
|
|
284
305
|
**Assessment**: Nearly identical to the pnpm setup.
|
|
285
306
|
The addition of `isolatedDeclarations` and `bun-types` are the only differences.
|
|
286
307
|
Using `moduleResolution: "Bundler"` is appropriate since Bunup handles the final output.
|
|
@@ -428,6 +449,175 @@ The workspace mode provides monorepo-aware builds that tsdown does not offer nat
|
|
|
428
449
|
|
|
429
450
|
* * *
|
|
430
451
|
|
|
452
|
+
#### ESM-Only Build Strategy
|
|
453
|
+
|
|
454
|
+
**Status**: Recommended (when appropriate)
|
|
455
|
+
|
|
456
|
+
**Details**:
|
|
457
|
+
|
|
458
|
+
Not every package needs dual ESM/CJS output.
|
|
459
|
+
For Bun-native CLI tools and packages targeting modern Node.js (>=22), ESM-only is
|
|
460
|
+
simpler and avoids CJS compatibility concerns entirely.
|
|
461
|
+
|
|
462
|
+
**When ESM-only is appropriate**:
|
|
463
|
+
|
|
464
|
+
- CLI tools that run via `#!/usr/bin/env bun` or `#!/usr/bin/env node`
|
|
465
|
+
- Libraries targeting only modern consumers (Node.js >=22, Bun, Deno)
|
|
466
|
+
- Internal monorepo packages not published to npm
|
|
467
|
+
|
|
468
|
+
**When you still need dual ESM/CJS**:
|
|
469
|
+
|
|
470
|
+
- Libraries consumed by older Node.js versions or legacy bundlers
|
|
471
|
+
- Packages that must work in CommonJS-only environments
|
|
472
|
+
|
|
473
|
+
**ESM-only configuration** (`bunup.config.ts`):
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { defineConfig } from 'bunup';
|
|
477
|
+
|
|
478
|
+
export default defineConfig({
|
|
479
|
+
entry: ['src/index.ts'],
|
|
480
|
+
format: ['esm'], // No CJS output
|
|
481
|
+
dts: true,
|
|
482
|
+
clean: true,
|
|
483
|
+
sourcemap: 'linked',
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**ESM-only exports** (`package.json`):
|
|
488
|
+
|
|
489
|
+
```json
|
|
490
|
+
{
|
|
491
|
+
"type": "module",
|
|
492
|
+
"main": "./dist/index.js",
|
|
493
|
+
"types": "./dist/index.d.ts",
|
|
494
|
+
"exports": {
|
|
495
|
+
".": {
|
|
496
|
+
"bun": "./src/index.ts",
|
|
497
|
+
"import": {
|
|
498
|
+
"types": "./dist/index.d.ts",
|
|
499
|
+
"default": "./dist/index.js"
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
"./package.json": "./package.json"
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Note: The legacy `"main"` and `"types"` fields still work for ESM-only packages and
|
|
508
|
+
provide fallback resolution for older tooling.
|
|
509
|
+
|
|
510
|
+
**Assessment**: ESM-only is the right default for new Bun-native projects.
|
|
511
|
+
Only add CJS if you have specific consumers that require it.
|
|
512
|
+
|
|
513
|
+
* * *
|
|
514
|
+
|
|
515
|
+
#### Multi-Config Bunup (Array Pattern)
|
|
516
|
+
|
|
517
|
+
**Status**: Recommended for CLI packages
|
|
518
|
+
|
|
519
|
+
**Details**:
|
|
520
|
+
|
|
521
|
+
For packages that produce both a library and a CLI binary, use `defineConfig([...])`
|
|
522
|
+
with separate configurations.
|
|
523
|
+
This provides fine-grained control over each output:
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
import { defineConfig } from 'bunup';
|
|
527
|
+
|
|
528
|
+
export default defineConfig([
|
|
529
|
+
// Library entry point — consumers import this
|
|
530
|
+
{
|
|
531
|
+
name: 'index',
|
|
532
|
+
entry: ['src/index.ts'],
|
|
533
|
+
format: ['esm'],
|
|
534
|
+
dts: true,
|
|
535
|
+
clean: true,
|
|
536
|
+
sourcemap: 'linked',
|
|
537
|
+
target: 'node',
|
|
538
|
+
},
|
|
539
|
+
// CLI binary — users run this directly
|
|
540
|
+
{
|
|
541
|
+
name: 'bin',
|
|
542
|
+
entry: ['src/bin.ts'],
|
|
543
|
+
format: ['esm'],
|
|
544
|
+
dts: false, // No declarations for CLI entry
|
|
545
|
+
clean: false, // Don't delete library output from first config
|
|
546
|
+
sourcemap: 'linked',
|
|
547
|
+
target: 'node',
|
|
548
|
+
banner: '#!/usr/bin/env bun',
|
|
549
|
+
},
|
|
550
|
+
]);
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Key details**:
|
|
554
|
+
|
|
555
|
+
- Set `clean: true` only on the first config — subsequent configs must use
|
|
556
|
+
`clean: false` to avoid deleting earlier outputs.
|
|
557
|
+
- CLI binaries typically don’t need `dts` since they aren’t imported by consumers.
|
|
558
|
+
- The `banner` field adds the shebang line for direct execution.
|
|
559
|
+
|
|
560
|
+
**Assessment**: The array pattern is cleaner than a single config with multiple entries
|
|
561
|
+
when the library and CLI have different build requirements (dts, format, banner, etc.).
|
|
562
|
+
|
|
563
|
+
* * *
|
|
564
|
+
|
|
565
|
+
#### Dependency Bundling for CLI Startup
|
|
566
|
+
|
|
567
|
+
**Status**: Recommended for CLI tools
|
|
568
|
+
|
|
569
|
+
**Details**:
|
|
570
|
+
|
|
571
|
+
Bunup’s `noExternal` option can inline specific dependencies into the CLI binary,
|
|
572
|
+
reducing startup time by eliminating require/import resolution at runtime:
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
{
|
|
576
|
+
name: 'bin',
|
|
577
|
+
entry: ['src/bin.ts'],
|
|
578
|
+
format: ['esm'],
|
|
579
|
+
banner: '#!/usr/bin/env bun',
|
|
580
|
+
noExternal: ['picocolors'], // Bundle into output for faster startup
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**When to bundle**:
|
|
585
|
+
|
|
586
|
+
- Small, frequently-imported packages (e.g., `picocolors`, `kleur`)
|
|
587
|
+
- Dependencies used on every CLI invocation (startup-path dependencies)
|
|
588
|
+
- Dependencies with deep `node_modules` resolution chains
|
|
589
|
+
|
|
590
|
+
**When NOT to bundle**:
|
|
591
|
+
|
|
592
|
+
- Large dependencies (increases output size significantly)
|
|
593
|
+
- Dependencies with native bindings
|
|
594
|
+
- Dependencies shared with the library entry point (consumers may already have them)
|
|
595
|
+
|
|
596
|
+
**Cross-platform considerations**: Some dependencies may trigger Bun bundler bugs on
|
|
597
|
+
specific platforms. Use platform guards when needed:
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
const isWindows = process.platform === 'win32';
|
|
601
|
+
|
|
602
|
+
export default defineConfig([
|
|
603
|
+
{
|
|
604
|
+
name: 'bin',
|
|
605
|
+
entry: ['src/bin.ts'],
|
|
606
|
+
format: ['esm'],
|
|
607
|
+
banner: '#!/usr/bin/env bun',
|
|
608
|
+
// Skip bundling on Windows due to Bun bundler bug with Windows paths
|
|
609
|
+
// See: https://github.com/oven-sh/bun/issues/15007
|
|
610
|
+
noExternal: isWindows ? [] : ['picocolors'],
|
|
611
|
+
},
|
|
612
|
+
]);
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Assessment**: Dependency bundling is a practical optimization for CLI tools.
|
|
616
|
+
The cross-platform guard pattern ensures builds work everywhere even when Bun’s bundler
|
|
617
|
+
has platform-specific issues.
|
|
618
|
+
|
|
619
|
+
* * *
|
|
620
|
+
|
|
431
621
|
### 4. Package Exports & Dual Module Support
|
|
432
622
|
|
|
433
623
|
#### Subpath Exports
|
|
@@ -613,6 +803,10 @@ bunx changeset init
|
|
|
613
803
|
}
|
|
614
804
|
```
|
|
615
805
|
|
|
806
|
+
**Note**: For solo or small projects, `"changelog": "@changesets/cli/changelog"` is a
|
|
807
|
+
simpler alternative that uses the built-in changelog generator without requiring the
|
|
808
|
+
`@changesets/changelog-github` dependency.
|
|
809
|
+
|
|
616
810
|
**Critical workaround scripts**:
|
|
617
811
|
|
|
618
812
|
```json
|
|
@@ -786,6 +980,71 @@ safer choice.
|
|
|
786
980
|
|
|
787
981
|
* * *
|
|
788
982
|
|
|
983
|
+
#### Golden/CLI Testing with tryscript
|
|
984
|
+
|
|
985
|
+
**Status**: Recommended for CLI tools
|
|
986
|
+
|
|
987
|
+
**Details**:
|
|
988
|
+
|
|
989
|
+
For CLI tools, `bun test` covers unit tests but doesn’t validate end-to-end CLI behavior
|
|
990
|
+
(argument parsing, output formatting, exit codes).
|
|
991
|
+
Use [tryscript](https://github.com/jlevy/tryscript) for golden/snapshot testing of CLI
|
|
992
|
+
output.
|
|
993
|
+
|
|
994
|
+
**Setup**:
|
|
995
|
+
|
|
996
|
+
```bash
|
|
997
|
+
bun add -d tryscript
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**Dual-test pattern** (`package.json`):
|
|
1001
|
+
|
|
1002
|
+
```json
|
|
1003
|
+
{
|
|
1004
|
+
"scripts": {
|
|
1005
|
+
"test": "bun test && tryscript run tests/cli.tryscript.md",
|
|
1006
|
+
"test:unit": "bun test",
|
|
1007
|
+
"test:golden": "tryscript run tests/*.tryscript.md"
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
This ensures `bun run test` runs both unit tests and golden tests.
|
|
1013
|
+
Developers can also run them separately during development.
|
|
1014
|
+
|
|
1015
|
+
**Example golden test** (`tests/cli.tryscript.md`):
|
|
1016
|
+
|
|
1017
|
+
````markdown
|
|
1018
|
+
# CLI Golden Tests
|
|
1019
|
+
|
|
1020
|
+
## Help output
|
|
1021
|
+
|
|
1022
|
+
```shell
|
|
1023
|
+
$ my-cli --help
|
|
1024
|
+
Usage: my-cli [options] [command]
|
|
1025
|
+
...
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
## Version output
|
|
1029
|
+
|
|
1030
|
+
```shell
|
|
1031
|
+
$ my-cli --version
|
|
1032
|
+
0.1.0
|
|
1033
|
+
```
|
|
1034
|
+
````
|
|
1035
|
+
|
|
1036
|
+
**Assessment**: Golden tests are especially valuable for CLI tools where output
|
|
1037
|
+
stability matters. They catch regressions in help text, formatting, and argument parsing
|
|
1038
|
+
that unit tests may miss.
|
|
1039
|
+
The `tryscript` tool uses Markdown files as test cases, making them easy to read and
|
|
1040
|
+
maintain.
|
|
1041
|
+
|
|
1042
|
+
**References**:
|
|
1043
|
+
|
|
1044
|
+
- [tryscript](https://github.com/jlevy/tryscript)
|
|
1045
|
+
|
|
1046
|
+
* * *
|
|
1047
|
+
|
|
789
1048
|
### 9. Code Formatting & Linting
|
|
790
1049
|
|
|
791
1050
|
#### Biome
|
|
@@ -826,15 +1085,29 @@ bun add -d @biomejs/biome
|
|
|
826
1085
|
|
|
827
1086
|
```json
|
|
828
1087
|
{
|
|
829
|
-
"$schema": "https://biomejs.dev/schemas/2.3.
|
|
830
|
-
"
|
|
831
|
-
"enabled": true
|
|
1088
|
+
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
1089
|
+
"vcs": {
|
|
1090
|
+
"enabled": true,
|
|
1091
|
+
"clientKind": "git",
|
|
1092
|
+
"useIgnoreFile": true
|
|
832
1093
|
},
|
|
1094
|
+
"files": {
|
|
1095
|
+
"ignoreUnknown": true,
|
|
1096
|
+
"includes": [
|
|
1097
|
+
"**",
|
|
1098
|
+
"!**/dist",
|
|
1099
|
+
"!**/node_modules",
|
|
1100
|
+
"!**/*.d.ts",
|
|
1101
|
+
"!**/bun.lock"
|
|
1102
|
+
]
|
|
1103
|
+
},
|
|
1104
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
833
1105
|
"formatter": {
|
|
834
1106
|
"enabled": true,
|
|
835
1107
|
"indentStyle": "space",
|
|
836
1108
|
"indentWidth": 2,
|
|
837
|
-
"lineWidth": 100
|
|
1109
|
+
"lineWidth": 100,
|
|
1110
|
+
"lineEnding": "lf"
|
|
838
1111
|
},
|
|
839
1112
|
"linter": {
|
|
840
1113
|
"enabled": true,
|
|
@@ -849,16 +1122,27 @@ bun add -d @biomejs/biome
|
|
|
849
1122
|
},
|
|
850
1123
|
"style": {
|
|
851
1124
|
"useConst": "error",
|
|
852
|
-
"noNonNullAssertion": "warn"
|
|
1125
|
+
"noNonNullAssertion": "warn",
|
|
1126
|
+
"useImportType": "error"
|
|
853
1127
|
}
|
|
854
1128
|
}
|
|
855
|
-
},
|
|
856
|
-
"files": {
|
|
857
|
-
"ignore": ["dist", "node_modules", ".changeset"]
|
|
858
1129
|
}
|
|
859
1130
|
}
|
|
860
1131
|
```
|
|
861
1132
|
|
|
1133
|
+
**Key configuration notes**:
|
|
1134
|
+
|
|
1135
|
+
- **VCS integration**:
|
|
1136
|
+
`"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }` tells Biome
|
|
1137
|
+
to respect `.gitignore` for file exclusion, reducing config duplication.
|
|
1138
|
+
- **Import organization**: In Biome 2.x, the `"organizeImports"` top-level key is
|
|
1139
|
+
deprecated. Use the new `"assist"` API:
|
|
1140
|
+
`"assist": { "actions": { "source": { "organizeImports": "on" } } }`.
|
|
1141
|
+
- **`ignoreUnknown: true`**: Prevents Biome from erroring on file types it doesn’t
|
|
1142
|
+
support (e.g., images, binaries).
|
|
1143
|
+
- **`useImportType: "error"`**: Enforces `import type` for type-only imports, aligning
|
|
1144
|
+
with `verbatimModuleSyntax` in TypeScript.
|
|
1145
|
+
|
|
862
1146
|
**Scripts**:
|
|
863
1147
|
|
|
864
1148
|
```json
|
|
@@ -866,17 +1150,17 @@ bun add -d @biomejs/biome
|
|
|
866
1150
|
"scripts": {
|
|
867
1151
|
"format": "biome format --write .",
|
|
868
1152
|
"format:check": "biome format .",
|
|
869
|
-
"lint": "biome
|
|
870
|
-
"lint:check": "biome
|
|
1153
|
+
"lint": "biome check --write . && bun run typecheck",
|
|
1154
|
+
"lint:check": "biome check . && bun run typecheck",
|
|
871
1155
|
"check": "biome check --write .",
|
|
872
|
-
"check:ci": "biome
|
|
1156
|
+
"check:ci": "biome ci ."
|
|
873
1157
|
}
|
|
874
1158
|
}
|
|
875
1159
|
```
|
|
876
1160
|
|
|
877
|
-
**Biome `check` vs
|
|
878
|
-
|
|
879
|
-
|
|
1161
|
+
**Biome `check` vs `ci`**: `biome check` runs formatting and linting in a single pass.
|
|
1162
|
+
Use `biome check --write .` for local development (auto-fixes issues) and `biome ci .`
|
|
1163
|
+
for CI (stricter: errors on any issue, cleaner output for CI logs).
|
|
880
1164
|
|
|
881
1165
|
**Comparison with Prettier + ESLint**:
|
|
882
1166
|
|
|
@@ -921,7 +1205,7 @@ approach (Biome + targeted Prettier/ESLint) is viable.
|
|
|
921
1205
|
|
|
922
1206
|
**Status**: Recommended
|
|
923
1207
|
|
|
924
|
-
**`.github/workflows/ci.yml
|
|
1208
|
+
**`.github/workflows/ci.yml`** (basic):
|
|
925
1209
|
|
|
926
1210
|
```yaml
|
|
927
1211
|
name: CI
|
|
@@ -949,6 +1233,61 @@ jobs:
|
|
|
949
1233
|
- run: bun test
|
|
950
1234
|
```
|
|
951
1235
|
|
|
1236
|
+
**Cross-platform CI with separate lint job** (recommended for CLI tools):
|
|
1237
|
+
|
|
1238
|
+
```yaml
|
|
1239
|
+
name: CI
|
|
1240
|
+
|
|
1241
|
+
on:
|
|
1242
|
+
pull_request:
|
|
1243
|
+
push:
|
|
1244
|
+
branches: [main]
|
|
1245
|
+
|
|
1246
|
+
permissions:
|
|
1247
|
+
contents: read
|
|
1248
|
+
|
|
1249
|
+
jobs:
|
|
1250
|
+
test:
|
|
1251
|
+
name: Test (${{ matrix.os }})
|
|
1252
|
+
runs-on: ${{ matrix.os }}
|
|
1253
|
+
strategy:
|
|
1254
|
+
fail-fast: false
|
|
1255
|
+
matrix:
|
|
1256
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
1257
|
+
steps:
|
|
1258
|
+
- uses: actions/checkout@v6
|
|
1259
|
+
|
|
1260
|
+
- uses: oven-sh/setup-bun@v2
|
|
1261
|
+
with:
|
|
1262
|
+
bun-version: latest
|
|
1263
|
+
|
|
1264
|
+
- run: bun install --frozen-lockfile
|
|
1265
|
+
- run: bun run build
|
|
1266
|
+
- run: bun run --filter '*' test
|
|
1267
|
+
|
|
1268
|
+
lint:
|
|
1269
|
+
name: Lint & Typecheck
|
|
1270
|
+
runs-on: ubuntu-latest
|
|
1271
|
+
steps:
|
|
1272
|
+
- uses: actions/checkout@v6
|
|
1273
|
+
|
|
1274
|
+
- uses: oven-sh/setup-bun@v2
|
|
1275
|
+
with:
|
|
1276
|
+
bun-version: latest
|
|
1277
|
+
|
|
1278
|
+
- run: bun install --frozen-lockfile
|
|
1279
|
+
- run: bun run check:ci
|
|
1280
|
+
- run: bun run typecheck
|
|
1281
|
+
- run: bun run build
|
|
1282
|
+
- run: bun run publint
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
**`biome ci` vs `biome check`**: Use `biome ci .` (via `"check:ci": "biome ci ."`) in CI
|
|
1286
|
+
workflows instead of `biome check .`. The `biome ci` command is stricter — it errors on
|
|
1287
|
+
formatting issues rather than just reporting them, and produces cleaner output for CI
|
|
1288
|
+
logs. Use `biome check --write .` for local development (auto-fixes issues) and
|
|
1289
|
+
`biome ci .` in CI (fails on any issue).
|
|
1290
|
+
|
|
952
1291
|
**Key differences from pnpm CI**:
|
|
953
1292
|
|
|
954
1293
|
| Aspect | pnpm CI | Bun CI |
|
|
@@ -1023,6 +1362,131 @@ Changesets workarounds, but functionally equivalent.
|
|
|
1023
1362
|
|
|
1024
1363
|
* * *
|
|
1025
1364
|
|
|
1365
|
+
#### Alternative: Tag-Triggered OIDC Release Workflow
|
|
1366
|
+
|
|
1367
|
+
**Status**: Recommended (alternative to Changesets Action)
|
|
1368
|
+
|
|
1369
|
+
**Details**:
|
|
1370
|
+
|
|
1371
|
+
Instead of using the `changesets/action` GitHub Action, an alternative approach uses git
|
|
1372
|
+
tags to trigger releases with npm provenance attestation via OIDC. This is simpler for
|
|
1373
|
+
projects that prefer manual version control and want provenance guarantees.
|
|
1374
|
+
|
|
1375
|
+
**`.github/workflows/release.yml`**:
|
|
1376
|
+
|
|
1377
|
+
```yaml
|
|
1378
|
+
name: Release
|
|
1379
|
+
|
|
1380
|
+
on:
|
|
1381
|
+
push:
|
|
1382
|
+
tags:
|
|
1383
|
+
- 'v*'
|
|
1384
|
+
|
|
1385
|
+
permissions:
|
|
1386
|
+
contents: write
|
|
1387
|
+
id-token: write # Required for npm provenance attestation
|
|
1388
|
+
|
|
1389
|
+
jobs:
|
|
1390
|
+
release:
|
|
1391
|
+
runs-on: ubuntu-latest
|
|
1392
|
+
steps:
|
|
1393
|
+
- uses: actions/checkout@v6
|
|
1394
|
+
with:
|
|
1395
|
+
fetch-depth: 0
|
|
1396
|
+
|
|
1397
|
+
- uses: oven-sh/setup-bun@v2
|
|
1398
|
+
with:
|
|
1399
|
+
bun-version: latest
|
|
1400
|
+
|
|
1401
|
+
# Node.js still needed for npm provenance attestation
|
|
1402
|
+
- uses: actions/setup-node@v6
|
|
1403
|
+
with:
|
|
1404
|
+
node-version: 22
|
|
1405
|
+
registry-url: 'https://registry.npmjs.org'
|
|
1406
|
+
|
|
1407
|
+
- run: bun install --frozen-lockfile
|
|
1408
|
+
|
|
1409
|
+
- run: bun run build
|
|
1410
|
+
|
|
1411
|
+
- run: bun run publint
|
|
1412
|
+
|
|
1413
|
+
- name: Publish to npm
|
|
1414
|
+
run: bun run publish-packages
|
|
1415
|
+
env:
|
|
1416
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
1417
|
+
NPM_CONFIG_PROVENANCE: true
|
|
1418
|
+
|
|
1419
|
+
- name: Extract version from tag
|
|
1420
|
+
id: version
|
|
1421
|
+
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
1422
|
+
|
|
1423
|
+
- name: Extract release notes
|
|
1424
|
+
id: changelog
|
|
1425
|
+
run: |
|
|
1426
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
1427
|
+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
|
1428
|
+
CHANGELOG=$(awk "/^## ${VERSION//./\\.}/,/^## [0-9]/" packages/*/CHANGELOG.md 2>/dev/null | head -n -1 || true)
|
|
1429
|
+
if [ -z "$CHANGELOG" ]; then
|
|
1430
|
+
if [ -n "$PREV_TAG" ]; then
|
|
1431
|
+
CHANGELOG="See [full commit history](https://github.com/${{ github.repository }}/compare/${PREV_TAG}...v${VERSION}) for details."
|
|
1432
|
+
else
|
|
1433
|
+
CHANGELOG="Release v${VERSION}"
|
|
1434
|
+
fi
|
|
1435
|
+
fi
|
|
1436
|
+
echo "body<<EOF" >> $GITHUB_OUTPUT
|
|
1437
|
+
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
|
1438
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
1439
|
+
|
|
1440
|
+
- name: Create GitHub Release
|
|
1441
|
+
uses: softprops/action-gh-release@v2
|
|
1442
|
+
with:
|
|
1443
|
+
body: ${{ steps.changelog.outputs.body }}
|
|
1444
|
+
prerelease: ${{ contains(steps.version.outputs.version, '-') }}
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
**Release process with tag-triggered workflow**:
|
|
1448
|
+
|
|
1449
|
+
1. `bun run changeset` — create changeset
|
|
1450
|
+
2. Commit the changeset file
|
|
1451
|
+
3. `bun run changeset version` — bumps versions, updates CHANGELOG
|
|
1452
|
+
4. Commit the version bump
|
|
1453
|
+
5. `git tag vX.X.X && git push --tags` — triggers the release workflow
|
|
1454
|
+
|
|
1455
|
+
**Key advantages over Changesets Action**:
|
|
1456
|
+
|
|
1457
|
+
- **npm provenance**: OIDC-based attestation proves packages were published from CI, not
|
|
1458
|
+
a developer machine
|
|
1459
|
+
- **GitHub Releases**: Automatically creates a GitHub Release with extracted CHANGELOG
|
|
1460
|
+
notes
|
|
1461
|
+
- **Pre-release detection**: Versions containing `-` (e.g., `1.0.0-beta.1`) are
|
|
1462
|
+
automatically marked as pre-releases
|
|
1463
|
+
- **Manual control**: The developer decides exactly when to publish by creating a tag
|
|
1464
|
+
|
|
1465
|
+
**Key requirements**:
|
|
1466
|
+
|
|
1467
|
+
- `id-token: write` permission is required for npm provenance
|
|
1468
|
+
- Node.js setup is still needed because npm provenance attestation uses Node.js
|
|
1469
|
+
internally
|
|
1470
|
+
- `NPM_TOKEN` secret must be configured in the repository
|
|
1471
|
+
- `NPM_CONFIG_PROVENANCE: true` enables provenance attestation
|
|
1472
|
+
|
|
1473
|
+
**Comparison with Changesets Action**:
|
|
1474
|
+
|
|
1475
|
+
| Aspect | Changesets Action | Tag-Triggered OIDC |
|
|
1476
|
+
| --- | --- | --- |
|
|
1477
|
+
| Trigger | Push to main | Push of `v*` tag |
|
|
1478
|
+
| PR creation | Automatic “Version Packages” PR | Manual |
|
|
1479
|
+
| npm provenance | Not built-in | Built-in via OIDC |
|
|
1480
|
+
| GitHub Release | Not built-in | Automatic |
|
|
1481
|
+
| Control | More automated | More manual |
|
|
1482
|
+
| Complexity | Simpler workflow file | More steps but more features |
|
|
1483
|
+
|
|
1484
|
+
**Assessment**: Tag-triggered OIDC releases are recommended for projects that want npm
|
|
1485
|
+
provenance attestation, automatic GitHub Releases, and manual control over release
|
|
1486
|
+
timing. The Changesets Action remains simpler for projects that prefer full automation.
|
|
1487
|
+
|
|
1488
|
+
* * *
|
|
1489
|
+
|
|
1026
1490
|
### 11. Git Hooks & Local Validation
|
|
1027
1491
|
|
|
1028
1492
|
#### Lefthook with Biome
|
|
@@ -1055,6 +1519,16 @@ pre-commit:
|
|
|
1055
1519
|
stage_fixed: true
|
|
1056
1520
|
priority: 1
|
|
1057
1521
|
|
|
1522
|
+
# Format markdown with flowmark (~500ms)
|
|
1523
|
+
# Biome does not format Markdown, so flowmark fills this gap
|
|
1524
|
+
# Excludes auto-generated files
|
|
1525
|
+
format-md:
|
|
1526
|
+
glob: '*.md'
|
|
1527
|
+
exclude: '(\.tbd/|\.claude/skills/|AGENTS\.md)'
|
|
1528
|
+
run: uvx flowmark@latest --auto {staged_files}
|
|
1529
|
+
stage_fixed: true
|
|
1530
|
+
priority: 1
|
|
1531
|
+
|
|
1058
1532
|
# Type check (~2s)
|
|
1059
1533
|
typecheck:
|
|
1060
1534
|
glob: '*.{ts,tsx}'
|
|
@@ -1091,8 +1565,16 @@ pre-push:
|
|
|
1091
1565
|
**Key advantage**: Using `biome check` in pre-commit combines formatting and linting
|
|
1092
1566
|
into a single command, reducing hook complexity from 2–3 commands to 1.
|
|
1093
1567
|
|
|
1568
|
+
**Markdown formatting**: Since Biome does not format Markdown, use
|
|
1569
|
+
[flowmark](https://github.com/jlevy/flowmark) (`uvx flowmark@latest --auto`) in a
|
|
1570
|
+
separate pre-commit command.
|
|
1571
|
+
The `exclude` pattern prevents reformatting auto-generated files.
|
|
1572
|
+
flowmark requires Python (via `uvx`), which is available on most development machines.
|
|
1573
|
+
|
|
1094
1574
|
**Assessment**: Simpler and faster hooks than the pnpm equivalent thanks to Biome’s
|
|
1095
1575
|
unified check command.
|
|
1576
|
+
The addition of flowmark provides consistent Markdown formatting that Biome cannot
|
|
1577
|
+
offer.
|
|
1096
1578
|
|
|
1097
1579
|
* * *
|
|
1098
1580
|
|
|
@@ -1361,8 +1843,8 @@ in core library code if the library needs to work in Node.js environments.
|
|
|
1361
1843
|
3. **Use Bunup’s auto-exports** (`exports: true`) to keep `package.json` exports
|
|
1362
1844
|
synchronized with build output.
|
|
1363
1845
|
|
|
1364
|
-
4. **Use Biome for formatting + linting** via a single `biome.json`. Use
|
|
1365
|
-
|
|
1846
|
+
4. **Use Biome for formatting + linting** via a single `biome.json`. Use
|
|
1847
|
+
`biome check --write` locally and `biome ci` in CI for stricter checks.
|
|
1366
1848
|
|
|
1367
1849
|
5. **Add `bun update` after `changeset version`** to fix workspace reference resolution
|
|
1368
1850
|
in the lockfile.
|
|
@@ -1396,6 +1878,21 @@ in core library code if the library needs to work in Node.js environments.
|
|
|
1396
1878
|
15. **Use dynamic git-based versioning** for dev builds — the pattern works identically
|
|
1397
1879
|
with Bun, and `bun` replaces `tsx` for script execution.
|
|
1398
1880
|
|
|
1881
|
+
16. **Use `biome ci`** in CI workflows instead of `biome check` — it’s stricter and
|
|
1882
|
+
produces cleaner output for CI logs.
|
|
1883
|
+
|
|
1884
|
+
17. **Consider ESM-only output** for Bun-native CLI tools and packages targeting modern
|
|
1885
|
+
Node.js (>=22). Only add CJS if specific consumers require it.
|
|
1886
|
+
|
|
1887
|
+
18. **Use flowmark** for Markdown formatting in pre-commit hooks — Biome does not format
|
|
1888
|
+
Markdown, and consistent Markdown formatting improves documentation quality.
|
|
1889
|
+
|
|
1890
|
+
19. **Add golden/CLI tests** with `tryscript` alongside `bun test` for CLI tools — they
|
|
1891
|
+
catch regressions in help text, output formatting, and argument parsing.
|
|
1892
|
+
|
|
1893
|
+
20. **Consider tag-triggered OIDC releases** as an alternative to the Changesets GitHub
|
|
1894
|
+
Action — they provide npm provenance attestation and automatic GitHub Releases.
|
|
1895
|
+
|
|
1399
1896
|
* * *
|
|
1400
1897
|
|
|
1401
1898
|
## Open Research Questions
|
|
@@ -1492,7 +1989,7 @@ testing features.
|
|
|
1492
1989
|
|
|
1493
1990
|
1. **Initialize workspace** with Bun and packages in `packages/`
|
|
1494
1991
|
|
|
1495
|
-
2. **Configure Bunup** for dual ESM/CJS output with auto-exports
|
|
1992
|
+
2. **Configure Bunup** for ESM (or dual ESM/CJS) output with auto-exports
|
|
1496
1993
|
|
|
1497
1994
|
3. **Enable `isolatedDeclarations`** in TypeScript config
|
|
1498
1995
|
|
|
@@ -1576,6 +2073,56 @@ testing features.
|
|
|
1576
2073
|
|
|
1577
2074
|
### Appendix A: Complete Package package.json Example
|
|
1578
2075
|
|
|
2076
|
+
**ESM-only** (recommended for Bun-native CLI tools and modern packages):
|
|
2077
|
+
|
|
2078
|
+
```json
|
|
2079
|
+
{
|
|
2080
|
+
"name": "get-package-name",
|
|
2081
|
+
"version": "0.1.0",
|
|
2082
|
+
"description": "Package description",
|
|
2083
|
+
"license": "MIT",
|
|
2084
|
+
"type": "module",
|
|
2085
|
+
"sideEffects": false,
|
|
2086
|
+
"main": "./dist/index.js",
|
|
2087
|
+
"types": "./dist/index.d.ts",
|
|
2088
|
+
"exports": {
|
|
2089
|
+
".": {
|
|
2090
|
+
"bun": "./src/index.ts",
|
|
2091
|
+
"import": {
|
|
2092
|
+
"types": "./dist/index.d.ts",
|
|
2093
|
+
"default": "./dist/index.js"
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
"./package.json": "./package.json"
|
|
2097
|
+
},
|
|
2098
|
+
"bin": {
|
|
2099
|
+
"package-name": "./dist/bin.js"
|
|
2100
|
+
},
|
|
2101
|
+
"files": ["dist"],
|
|
2102
|
+
"engines": {
|
|
2103
|
+
"node": ">=22"
|
|
2104
|
+
},
|
|
2105
|
+
"scripts": {
|
|
2106
|
+
"prebuild": "cp ../../README.md ./README.md",
|
|
2107
|
+
"build": "bunup",
|
|
2108
|
+
"dev": "bun --watch src/bin.ts",
|
|
2109
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
2110
|
+
"test": "bun test",
|
|
2111
|
+
"publint": "publint",
|
|
2112
|
+
"prepack": "bun run build"
|
|
2113
|
+
},
|
|
2114
|
+
"dependencies": {},
|
|
2115
|
+
"devDependencies": {
|
|
2116
|
+
"bun-types": "^1.3.8",
|
|
2117
|
+
"bunup": "^0.16.22",
|
|
2118
|
+
"publint": "^0.3.17",
|
|
2119
|
+
"typescript": "^5.9.3"
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
```
|
|
2123
|
+
|
|
2124
|
+
**Dual ESM/CJS** (for libraries that must support older Node.js or CJS consumers):
|
|
2125
|
+
|
|
1579
2126
|
```json
|
|
1580
2127
|
{
|
|
1581
2128
|
"name": "@scope/package-name",
|
|
@@ -1599,17 +2146,6 @@ testing features.
|
|
|
1599
2146
|
"default": "./dist/index.cjs"
|
|
1600
2147
|
}
|
|
1601
2148
|
},
|
|
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
2149
|
"./package.json": "./package.json"
|
|
1614
2150
|
},
|
|
1615
2151
|
"bin": {
|
|
@@ -1621,7 +2157,7 @@ testing features.
|
|
|
1621
2157
|
"dev": "bunup --watch",
|
|
1622
2158
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
1623
2159
|
"test": "bun test",
|
|
1624
|
-
"publint": "
|
|
2160
|
+
"publint": "publint",
|
|
1625
2161
|
"prepack": "bun run build"
|
|
1626
2162
|
},
|
|
1627
2163
|
"dependencies": {},
|
|
@@ -1632,9 +2168,10 @@ testing features.
|
|
|
1632
2168
|
"optional-sdk": { "optional": true }
|
|
1633
2169
|
},
|
|
1634
2170
|
"devDependencies": {
|
|
1635
|
-
"bun-types": "
|
|
1636
|
-
"bunup": "^0.16.
|
|
1637
|
-
"
|
|
2171
|
+
"bun-types": "^1.3.8",
|
|
2172
|
+
"bunup": "^0.16.22",
|
|
2173
|
+
"publint": "^0.3.17",
|
|
2174
|
+
"typescript": "^5.9.3"
|
|
1638
2175
|
}
|
|
1639
2176
|
}
|
|
1640
2177
|
```
|
|
@@ -1645,48 +2182,75 @@ testing features.
|
|
|
1645
2182
|
{
|
|
1646
2183
|
"name": "project-workspace",
|
|
1647
2184
|
"private": true,
|
|
2185
|
+
"type": "module",
|
|
1648
2186
|
"workspaces": [
|
|
1649
2187
|
"packages/*"
|
|
1650
2188
|
],
|
|
2189
|
+
"engines": {
|
|
2190
|
+
"node": ">=22"
|
|
2191
|
+
},
|
|
1651
2192
|
"scripts": {
|
|
1652
|
-
"
|
|
1653
|
-
"
|
|
1654
|
-
"
|
|
1655
|
-
"
|
|
2193
|
+
"prepare": "lefthook install",
|
|
2194
|
+
"build": "bun run --filter '*' build",
|
|
2195
|
+
"typecheck": "bun run --filter '*' typecheck",
|
|
2196
|
+
"test": "bun run --filter '*' test",
|
|
2197
|
+
"publint": "bun run --filter '*' publint",
|
|
1656
2198
|
"format": "biome format --write .",
|
|
1657
2199
|
"format:check": "biome format .",
|
|
1658
|
-
"lint": "biome
|
|
1659
|
-
"lint:check": "biome
|
|
2200
|
+
"lint": "biome check --write . && bun run typecheck",
|
|
2201
|
+
"lint:check": "biome check . && bun run typecheck",
|
|
1660
2202
|
"check": "biome check --write .",
|
|
1661
|
-
"check:ci": "biome
|
|
1662
|
-
"prepare": "lefthook install",
|
|
2203
|
+
"check:ci": "biome ci .",
|
|
1663
2204
|
"changeset": "changeset",
|
|
1664
2205
|
"version-packages": "changeset version && bun update",
|
|
1665
|
-
"release": "bun run build &&
|
|
2206
|
+
"release": "bun run build && bun run publint && bun run publish-packages",
|
|
1666
2207
|
"publish-packages": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag",
|
|
1667
2208
|
"upgrade:check": "bunx npm-check-updates --format group",
|
|
1668
|
-
"upgrade": "bunx npm-check-updates --target minor -u && bun install && bun test",
|
|
2209
|
+
"upgrade": "bunx npm-check-updates --target minor -u && bun install && bun run test",
|
|
1669
2210
|
"upgrade:major": "bunx npm-check-updates --target latest --interactive --format group"
|
|
1670
2211
|
},
|
|
1671
2212
|
"devDependencies": {
|
|
1672
|
-
"@biomejs/biome": "^2.3.
|
|
1673
|
-
"@changesets/cli": "^2.29.
|
|
1674
|
-
"
|
|
1675
|
-
"
|
|
1676
|
-
"
|
|
1677
|
-
"typescript": "^5.9.0"
|
|
2213
|
+
"@biomejs/biome": "^2.3.14",
|
|
2214
|
+
"@changesets/cli": "^2.29.8",
|
|
2215
|
+
"lefthook": "^2.0.15",
|
|
2216
|
+
"npm-check-updates": "^19.0.0",
|
|
2217
|
+
"typescript": "^5.9.3"
|
|
1678
2218
|
}
|
|
1679
2219
|
}
|
|
1680
2220
|
```
|
|
1681
2221
|
|
|
2222
|
+
**Notes**:
|
|
2223
|
+
|
|
2224
|
+
- `"check:ci": "biome ci ."` uses `biome ci` (stricter than `biome check` — errors on
|
|
2225
|
+
formatting issues)
|
|
2226
|
+
- `bun run --filter '*' <script>` delegates to each workspace package
|
|
2227
|
+
- The `@changesets/changelog-github` dependency is optional — use
|
|
2228
|
+
`"changelog": "@changesets/cli/changelog"` in `.changeset/config.json` for a simpler
|
|
2229
|
+
built-in changelog generator
|
|
2230
|
+
|
|
1682
2231
|
### Appendix C: Complete biome.json Example
|
|
1683
2232
|
|
|
1684
2233
|
```json
|
|
1685
2234
|
{
|
|
1686
|
-
"$schema": "https://biomejs.dev/schemas/2.3.
|
|
1687
|
-
"
|
|
1688
|
-
"enabled": true
|
|
2235
|
+
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
2236
|
+
"vcs": {
|
|
2237
|
+
"enabled": true,
|
|
2238
|
+
"clientKind": "git",
|
|
2239
|
+
"useIgnoreFile": true
|
|
2240
|
+
},
|
|
2241
|
+
"files": {
|
|
2242
|
+
"ignoreUnknown": true,
|
|
2243
|
+
"includes": [
|
|
2244
|
+
"**",
|
|
2245
|
+
"!**/dist",
|
|
2246
|
+
"!**/node_modules",
|
|
2247
|
+
"!**/*.d.ts",
|
|
2248
|
+
"!**/*.d.mts",
|
|
2249
|
+
"!**/bun.lock",
|
|
2250
|
+
"!**/.tbd"
|
|
2251
|
+
]
|
|
1689
2252
|
},
|
|
2253
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
1690
2254
|
"formatter": {
|
|
1691
2255
|
"enabled": true,
|
|
1692
2256
|
"indentStyle": "space",
|
|
@@ -1730,7 +2294,7 @@ testing features.
|
|
|
1730
2294
|
},
|
|
1731
2295
|
"overrides": [
|
|
1732
2296
|
{
|
|
1733
|
-
"
|
|
2297
|
+
"includes": ["**/*.test.ts", "**/*.spec.ts", "**/tests/**/*.ts"],
|
|
1734
2298
|
"linter": {
|
|
1735
2299
|
"rules": {
|
|
1736
2300
|
"suspicious": {
|
|
@@ -1740,7 +2304,7 @@ testing features.
|
|
|
1740
2304
|
}
|
|
1741
2305
|
},
|
|
1742
2306
|
{
|
|
1743
|
-
"
|
|
2307
|
+
"includes": ["**/scripts/**/*.ts"],
|
|
1744
2308
|
"linter": {
|
|
1745
2309
|
"rules": {
|
|
1746
2310
|
"suspicious": {
|
|
@@ -1749,19 +2313,19 @@ testing features.
|
|
|
1749
2313
|
}
|
|
1750
2314
|
}
|
|
1751
2315
|
}
|
|
1752
|
-
]
|
|
1753
|
-
"files": {
|
|
1754
|
-
"ignore": [
|
|
1755
|
-
"dist",
|
|
1756
|
-
"node_modules",
|
|
1757
|
-
".changeset",
|
|
1758
|
-
"bun.lock",
|
|
1759
|
-
"coverage"
|
|
1760
|
-
]
|
|
1761
|
-
}
|
|
2316
|
+
]
|
|
1762
2317
|
}
|
|
1763
2318
|
```
|
|
1764
2319
|
|
|
2320
|
+
**Key changes from Biome 1.x to 2.x**:
|
|
2321
|
+
|
|
2322
|
+
- `"organizeImports": { "enabled": true }` →
|
|
2323
|
+
`"assist": { "actions": { "source": { "organizeImports": "on" } } }`
|
|
2324
|
+
- `"vcs"` configuration is new — enables `.gitignore`-based file exclusion
|
|
2325
|
+
- `"files.ignoreUnknown": true` prevents errors on unsupported file types
|
|
2326
|
+
- `"files.includes"` with negation patterns replaces the old `"files.ignore"` array
|
|
2327
|
+
(both still work, but `includes` with negation is more flexible)
|
|
2328
|
+
|
|
1765
2329
|
### Appendix D: Complete bunup.config.ts Example
|
|
1766
2330
|
|
|
1767
2331
|
```typescript
|
|
@@ -1804,7 +2368,41 @@ export default defineWorkspace([
|
|
|
1804
2368
|
]);
|
|
1805
2369
|
```
|
|
1806
2370
|
|
|
1807
|
-
**Single-package config** (`packages/
|
|
2371
|
+
**Single-package config with multi-config array** (`packages/cli/bunup.config.ts`):
|
|
2372
|
+
|
|
2373
|
+
```typescript
|
|
2374
|
+
import { defineConfig } from 'bunup';
|
|
2375
|
+
|
|
2376
|
+
// Platform guard for cross-platform bundler workarounds
|
|
2377
|
+
const isWindows = process.platform === 'win32';
|
|
2378
|
+
|
|
2379
|
+
export default defineConfig([
|
|
2380
|
+
// Library entry point
|
|
2381
|
+
{
|
|
2382
|
+
name: 'index',
|
|
2383
|
+
entry: ['src/index.ts'],
|
|
2384
|
+
format: ['esm'],
|
|
2385
|
+
dts: true,
|
|
2386
|
+
clean: true,
|
|
2387
|
+
sourcemap: 'linked',
|
|
2388
|
+
target: 'node',
|
|
2389
|
+
},
|
|
2390
|
+
// CLI binary - separate config for different build requirements
|
|
2391
|
+
{
|
|
2392
|
+
name: 'bin',
|
|
2393
|
+
entry: ['src/bin.ts'],
|
|
2394
|
+
format: ['esm'],
|
|
2395
|
+
dts: false,
|
|
2396
|
+
clean: false, // Don't delete library output
|
|
2397
|
+
sourcemap: 'linked',
|
|
2398
|
+
target: 'node',
|
|
2399
|
+
banner: '#!/usr/bin/env bun',
|
|
2400
|
+
noExternal: isWindows ? [] : ['picocolors'], // Bundle for startup speed
|
|
2401
|
+
},
|
|
2402
|
+
]);
|
|
2403
|
+
```
|
|
2404
|
+
|
|
2405
|
+
**Simple single-package config** (`packages/core/bunup.config.ts`):
|
|
1808
2406
|
|
|
1809
2407
|
```typescript
|
|
1810
2408
|
import { defineConfig } from 'bunup';
|
|
@@ -1831,7 +2429,7 @@ export default defineConfig({
|
|
|
1831
2429
|
```yaml
|
|
1832
2430
|
# lefthook.yml
|
|
1833
2431
|
# Git hooks for code quality (Bun + Biome edition)
|
|
1834
|
-
# Pre-commit: Fast checks with auto-fix (target:
|
|
2432
|
+
# Pre-commit: Fast checks with auto-fix (target: 2-5 seconds)
|
|
1835
2433
|
# Pre-push: Full test validation with caching
|
|
1836
2434
|
|
|
1837
2435
|
pre-commit:
|
|
@@ -1840,28 +2438,46 @@ pre-commit:
|
|
|
1840
2438
|
commands:
|
|
1841
2439
|
# Format + lint with Biome (~200ms for staged files)
|
|
1842
2440
|
check:
|
|
1843
|
-
glob: '*.{js,ts,tsx,json
|
|
2441
|
+
glob: '*.{js,ts,tsx,json}'
|
|
1844
2442
|
run: bunx biome check --write {staged_files}
|
|
1845
2443
|
stage_fixed: true
|
|
1846
2444
|
priority: 1
|
|
1847
2445
|
|
|
2446
|
+
# Format markdown with flowmark (~500ms)
|
|
2447
|
+
# Biome does not format Markdown, so flowmark fills this gap
|
|
2448
|
+
# Excludes auto-generated files (.tbd/, .claude/skills/, AGENTS.md)
|
|
2449
|
+
format-md:
|
|
2450
|
+
glob: '*.md'
|
|
2451
|
+
exclude: '(\.tbd/|\.claude/skills/|AGENTS\.md)'
|
|
2452
|
+
run: uvx flowmark@latest --auto {staged_files}
|
|
2453
|
+
stage_fixed: true
|
|
2454
|
+
priority: 1
|
|
2455
|
+
|
|
1848
2456
|
# Type check with incremental mode (~2s)
|
|
1849
2457
|
typecheck:
|
|
1850
2458
|
glob: '*.{ts,tsx}'
|
|
1851
|
-
run:
|
|
2459
|
+
run: bun run typecheck
|
|
1852
2460
|
priority: 2
|
|
1853
2461
|
|
|
1854
2462
|
pre-push:
|
|
2463
|
+
parallel: true
|
|
2464
|
+
|
|
1855
2465
|
commands:
|
|
1856
|
-
|
|
2466
|
+
# Build packages
|
|
2467
|
+
build:
|
|
2468
|
+
run: bun run build
|
|
2469
|
+
priority: 1
|
|
2470
|
+
|
|
2471
|
+
# Tests with caching - skip if already passed for this commit
|
|
2472
|
+
test:
|
|
1857
2473
|
run: |
|
|
1858
2474
|
COMMIT_HASH=$(git rev-parse HEAD)
|
|
1859
2475
|
CACHE_DIR="node_modules/.test-cache"
|
|
1860
2476
|
CACHE_FILE="$CACHE_DIR/$COMMIT_HASH"
|
|
1861
2477
|
|
|
1862
|
-
# Check for uncommitted changes
|
|
2478
|
+
# Check for uncommitted changes - always run tests
|
|
1863
2479
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
1864
|
-
bun test
|
|
2480
|
+
bun run test
|
|
1865
2481
|
exit $?
|
|
1866
2482
|
fi
|
|
1867
2483
|
|
|
@@ -1873,7 +2489,7 @@ pre-push:
|
|
|
1873
2489
|
fi
|
|
1874
2490
|
|
|
1875
2491
|
# Run tests
|
|
1876
|
-
bun test
|
|
2492
|
+
bun run test
|
|
1877
2493
|
|
|
1878
2494
|
# Cache on success
|
|
1879
2495
|
if [ $? -eq 0 ]; then
|
|
@@ -1885,6 +2501,7 @@ pre-push:
|
|
|
1885
2501
|
echo "Bypass with: git push --no-verify"
|
|
1886
2502
|
exit 1
|
|
1887
2503
|
fi
|
|
2504
|
+
priority: 2
|
|
1888
2505
|
```
|
|
1889
2506
|
|
|
1890
2507
|
### Appendix F: Standalone Executable Build Example
|
|
@@ -2094,3 +2711,122 @@ Key takeaways:
|
|
|
2094
2711
|
The source-level exports pattern and TypeScript build scripts are directly applicable.
|
|
2095
2712
|
The hybrid esbuild/Vite approach is more relevant for Electron/full-stack apps than for
|
|
2096
2713
|
pure library packages where Bunup would be simpler.
|
|
2714
|
+
|
|
2715
|
+
### Appendix H: Case Study — clam (Published Bun CLI Monorepo)
|
|
2716
|
+
|
|
2717
|
+
**Repository**: [jlevy/clam](https://github.com/jlevy/clam) (MIT, shell-like interface
|
|
2718
|
+
for Claude Code and other agents)
|
|
2719
|
+
|
|
2720
|
+
This appendix documents a real-world, published Bun monorepo that closely follows the
|
|
2721
|
+
patterns in this guide.
|
|
2722
|
+
Unlike Appendix G (craft-agents-oss, a private Electron app), clam is a public npm
|
|
2723
|
+
package that exercises the full publishing pipeline.
|
|
2724
|
+
|
|
2725
|
+
#### Structure Overview
|
|
2726
|
+
|
|
2727
|
+
```
|
|
2728
|
+
clam/
|
|
2729
|
+
├── .changeset/
|
|
2730
|
+
│ └── config.json # Changesets with @changesets/cli/changelog
|
|
2731
|
+
├── .github/
|
|
2732
|
+
│ └── workflows/
|
|
2733
|
+
│ ├── ci.yml # 3-OS matrix + separate lint job
|
|
2734
|
+
│ └── release.yml # Tag-triggered OIDC release
|
|
2735
|
+
├── packages/
|
|
2736
|
+
│ └── clam/
|
|
2737
|
+
│ ├── src/
|
|
2738
|
+
│ │ ├── bin.ts # CLI entry point
|
|
2739
|
+
│ │ ├── index.ts # Library barrel exports
|
|
2740
|
+
│ │ └── lib/ # Core modules (co-located *.test.ts)
|
|
2741
|
+
│ ├── tests/
|
|
2742
|
+
│ │ └── cli.tryscript.md # Golden CLI tests
|
|
2743
|
+
│ ├── bunup.config.ts # Multi-config: library + CLI binary
|
|
2744
|
+
│ ├── package.json # ESM-only, "bun" export condition
|
|
2745
|
+
│ └── tsconfig.json # extends base, bun-types, isolatedDeclarations: false
|
|
2746
|
+
├── biome.json # VCS integration, assist API, ignoreUnknown
|
|
2747
|
+
├── bun.lock # Text JSONC lockfile
|
|
2748
|
+
├── lefthook.yml # Biome + flowmark pre-commit, cached tests pre-push
|
|
2749
|
+
├── package.json # Root workspace config
|
|
2750
|
+
└── tsconfig.base.json # ES2024, strict, isolatedDeclarations: true
|
|
2751
|
+
```
|
|
2752
|
+
|
|
2753
|
+
#### Key Patterns Demonstrated
|
|
2754
|
+
|
|
2755
|
+
**1. ESM-Only Build with Multi-Config Bunup**
|
|
2756
|
+
|
|
2757
|
+
```typescript
|
|
2758
|
+
// bunup.config.ts
|
|
2759
|
+
export default defineConfig([
|
|
2760
|
+
{
|
|
2761
|
+
name: 'index',
|
|
2762
|
+
entry: ['src/index.ts'],
|
|
2763
|
+
format: ['esm'],
|
|
2764
|
+
dts: true,
|
|
2765
|
+
clean: true,
|
|
2766
|
+
sourcemap: 'linked',
|
|
2767
|
+
target: 'node',
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
name: 'bin',
|
|
2771
|
+
entry: ['src/bin.ts'],
|
|
2772
|
+
format: ['esm'],
|
|
2773
|
+
dts: false,
|
|
2774
|
+
clean: false,
|
|
2775
|
+
sourcemap: 'linked',
|
|
2776
|
+
target: 'node',
|
|
2777
|
+
banner: '#!/usr/bin/env bun',
|
|
2778
|
+
noExternal: isWindows ? [] : ['picocolors'],
|
|
2779
|
+
},
|
|
2780
|
+
]);
|
|
2781
|
+
```
|
|
2782
|
+
|
|
2783
|
+
Separate configs for library (with DTS) and CLI binary (with shebang, dependency
|
|
2784
|
+
bundling, no DTS). ESM-only output — no CJS.
|
|
2785
|
+
|
|
2786
|
+
**2. Tag-Triggered OIDC Release with npm Provenance**
|
|
2787
|
+
|
|
2788
|
+
Release workflow triggers on `v*` tags with `id-token: write` permission.
|
|
2789
|
+
Uses `NPM_CONFIG_PROVENANCE: true` for npm provenance attestation.
|
|
2790
|
+
Automatically creates GitHub Releases with CHANGELOG extraction via
|
|
2791
|
+
`softprops/action-gh-release@v2`.
|
|
2792
|
+
|
|
2793
|
+
**3. Cross-Platform CI Matrix**
|
|
2794
|
+
|
|
2795
|
+
Tests run on ubuntu, macos, and windows with `fail-fast: false`. Linting, type checking,
|
|
2796
|
+
and publint run in a separate ubuntu-only job for efficiency.
|
|
2797
|
+
|
|
2798
|
+
**4. Dual Testing Strategy**
|
|
2799
|
+
|
|
2800
|
+
Unit tests with `bun test` (co-located `*.test.ts` files) plus golden CLI tests with
|
|
2801
|
+
`tryscript` (Markdown-based test files in `tests/`). Combined via:
|
|
2802
|
+
`"test": "bun test && tryscript run tests/cli.tryscript.md"`.
|
|
2803
|
+
|
|
2804
|
+
**5. Biome with VCS Integration**
|
|
2805
|
+
|
|
2806
|
+
Uses `"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }` and the
|
|
2807
|
+
new Biome 2.x `"assist"` API for import organization.
|
|
2808
|
+
`"check:ci": "biome ci ."` for strict CI checks.
|
|
2809
|
+
|
|
2810
|
+
**6. Intentional `isolatedDeclarations: false` at Package Level**
|
|
2811
|
+
|
|
2812
|
+
The base config enables `isolatedDeclarations: true` (as recommended), but the package
|
|
2813
|
+
overrides it to `false` because its types are complex enough to require the full
|
|
2814
|
+
TypeScript compiler for DTS generation.
|
|
2815
|
+
This is a valid pattern — enable in the base, override in packages that need it.
|
|
2816
|
+
|
|
2817
|
+
#### What This Repo Uses
|
|
2818
|
+
|
|
2819
|
+
| Area | Choice |
|
|
2820
|
+
| --- | --- |
|
|
2821
|
+
| Package manager | Bun workspaces |
|
|
2822
|
+
| Build | Bunup `^0.16.22` (multi-config array) |
|
|
2823
|
+
| Output format | ESM-only |
|
|
2824
|
+
| TypeScript | `^5.9.3`, ES2024, Bundler resolution |
|
|
2825
|
+
| Linting | Biome `^2.3.14` |
|
|
2826
|
+
| Markdown formatting | flowmark (via `uvx`) |
|
|
2827
|
+
| Testing | bun test + tryscript (golden) |
|
|
2828
|
+
| Git hooks | lefthook `^1.11.13` |
|
|
2829
|
+
| Versioning | Changesets `^2.29.8` |
|
|
2830
|
+
| CI | 3-OS matrix + separate lint job |
|
|
2831
|
+
| Release | Tag-triggered OIDC with npm provenance |
|
|
2832
|
+
| Package validation | publint `^0.3.17` |
|