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
|
# pnpm Monorepo Patterns
|
|
7
7
|
|
|
8
|
-
**Last Updated**: 2026-02-
|
|
8
|
+
**Last Updated**: 2026-02-18
|
|
9
9
|
|
|
10
10
|
**Related**:
|
|
11
11
|
|
|
@@ -23,9 +23,9 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
|
23
23
|
|
|
24
24
|
| Tool / Package | Version | Check For Updates |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| **Node.js** | 24 (LTS
|
|
27
|
-
| **pnpm** | 10.28.
|
|
28
|
-
| **TypeScript** | ^5.9.3 | [github.com/microsoft/TypeScript/releases](https://github.com/microsoft/TypeScript/releases) — 5.9.3 stable. TS 6.0 is
|
|
26
|
+
| **Node.js** | 24 (LTS “Krypton”) | [nodejs.org/releases](https://nodejs.org/en/about/previous-releases) — Active LTS until Oct 2026 |
|
|
27
|
+
| **pnpm** | 10.28.2 | [github.com/pnpm/pnpm/releases](https://github.com/pnpm/pnpm/releases) — V8 binary storage for faster cache reads |
|
|
28
|
+
| **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. |
|
|
29
29
|
| **tsdown** | ^0.20.0 | [github.com/rolldown/tsdown/releases](https://github.com/rolldown/tsdown/releases) — 0.20.0-beta.3 latest. Requires Node.js 20.19+. |
|
|
30
30
|
| **publint** | ^0.3.0 | [npmjs.com/package/publint](https://www.npmjs.com/package/publint) |
|
|
31
31
|
| **@changesets/cli** | ^2.29.0 | [github.com/changesets/changesets/releases](https://github.com/changesets/changesets/releases) |
|
|
@@ -34,13 +34,13 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
|
34
34
|
| **actions/setup-node** | v6 | [github.com/actions/setup-node/releases](https://github.com/actions/setup-node/releases) |
|
|
35
35
|
| **pnpm/action-setup** | v4 | [github.com/pnpm/action-setup/releases](https://github.com/pnpm/action-setup/releases) |
|
|
36
36
|
| **changesets/action** | v1 | [github.com/changesets/action](https://github.com/changesets/action) |
|
|
37
|
-
| **lefthook** | ^2.0.15 | [github.com/evilmartians/lefthook/releases](https://github.com/evilmartians/lefthook/releases) — 2.
|
|
37
|
+
| **lefthook** | ^2.0.15 | [github.com/evilmartians/lefthook/releases](https://github.com/evilmartians/lefthook/releases) — 2.1.1 latest. v2 dropped regexp `exclude`, `skip_output`. |
|
|
38
38
|
| **npm-check-updates** | ^19.0.0 | [npmjs.com/package/npm-check-updates](https://www.npmjs.com/package/npm-check-updates) |
|
|
39
39
|
| **tsx** | ^4.21.0 | [github.com/privatenumber/tsx/releases](https://github.com/privatenumber/tsx/releases) |
|
|
40
40
|
| **prettier** | ^3.0.0 | [github.com/prettier/prettier/releases](https://github.com/prettier/prettier/releases) |
|
|
41
41
|
| **eslint-config-prettier** | ^10.0.0 | [github.com/prettier/eslint-config-prettier/releases](https://github.com/prettier/eslint-config-prettier/releases) |
|
|
42
42
|
| **ESLint** | ^9.39.0 | [github.com/eslint/eslint/releases](https://github.com/eslint/eslint/releases) — 9.39.2 stable. v10.0.0 in RC phase (Jan 2026). |
|
|
43
|
-
| **Vitest** | ^4.0.0 | [github.com/vitest-dev/vitest/releases](https://github.com/vitest-dev/vitest/releases) — 4.0.
|
|
43
|
+
| **Vitest** | ^4.0.0 | [github.com/vitest-dev/vitest/releases](https://github.com/vitest-dev/vitest/releases) — 4.0.18 latest. Browser Mode stable, visual regression testing added. `coverage.all` removed in v4. |
|
|
44
44
|
|
|
45
45
|
### Reminders When Updating
|
|
46
46
|
|
|
@@ -261,6 +261,11 @@ Modern TypeScript monorepos use a shared base configuration extended by each pac
|
|
|
261
261
|
}
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
+
**Note on target/lib version**: Use `ES2024` when targeting Node.js 22+ (which supports
|
|
265
|
+
all ES2024 features).
|
|
266
|
+
Use `ES2023` if your minimum is Node.js 20. The target should match what your
|
|
267
|
+
`engines.node` field supports.
|
|
268
|
+
|
|
264
269
|
**Assessment**: Using `moduleResolution: "Bundler"` is appropriate when a bundler
|
|
265
270
|
(tsdown) handles the final output.
|
|
266
271
|
For maximum Node.js compatibility without a bundler, `NodeNext` would be preferred.
|
|
@@ -379,6 +384,56 @@ The project recommends migrating to tsdown.
|
|
|
379
384
|
|
|
380
385
|
* * *
|
|
381
386
|
|
|
387
|
+
#### ESM-Only Alternative (Node.js 22+)
|
|
388
|
+
|
|
389
|
+
**Status**: Recommended for Node.js-only packages
|
|
390
|
+
|
|
391
|
+
**When to use**: If your package targets Node.js 22+ exclusively and doesn’t need to
|
|
392
|
+
support CommonJS consumers (bundlers, older Node.js, or specific CJS-only tools), an
|
|
393
|
+
ESM-only build is simpler and sufficient.
|
|
394
|
+
|
|
395
|
+
**Simplified tsdown config**:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
export default defineConfig({
|
|
399
|
+
entry: { index: 'src/index.ts' },
|
|
400
|
+
format: ['esm'], // ESM only
|
|
401
|
+
platform: 'node',
|
|
402
|
+
target: 'node24',
|
|
403
|
+
sourcemap: true,
|
|
404
|
+
dts: true,
|
|
405
|
+
clean: true,
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Simplified package.json exports**:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"type": "module",
|
|
414
|
+
"main": "./dist/index.mjs",
|
|
415
|
+
"types": "./dist/index.d.mts",
|
|
416
|
+
"exports": {
|
|
417
|
+
".": {
|
|
418
|
+
"types": "./dist/index.d.mts",
|
|
419
|
+
"default": "./dist/index.mjs"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Trade-offs**:
|
|
426
|
+
|
|
427
|
+
- ✅ Simpler config, smaller package, faster builds
|
|
428
|
+
- ✅ No dual-format complexity
|
|
429
|
+
- ❌ CJS consumers cannot use the package
|
|
430
|
+
- ❌ Some bundlers may require additional config
|
|
431
|
+
|
|
432
|
+
**Assessment**: ESM-only is the right choice for modern Node.js libraries.
|
|
433
|
+
Only provide dual ESM/CJS if you have confirmed CJS consumer requirements.
|
|
434
|
+
|
|
435
|
+
* * *
|
|
436
|
+
|
|
382
437
|
### 4. Package Exports & Dual Module Support
|
|
383
438
|
|
|
384
439
|
#### Subpath Exports
|
|
@@ -606,7 +661,7 @@ Changesets provides:
|
|
|
606
661
|
```json
|
|
607
662
|
{
|
|
608
663
|
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
|
609
|
-
"changelog": "@changesets/changelog-github",
|
|
664
|
+
"changelog": "@changesets/changelog-github", // or "@changesets/cli/changelog" for simpler output
|
|
610
665
|
"commit": false,
|
|
611
666
|
"fixed": [],
|
|
612
667
|
"linked": [],
|
|
@@ -653,6 +708,100 @@ It integrates seamlessly with pnpm and GitHub Actions.
|
|
|
653
708
|
|
|
654
709
|
* * *
|
|
655
710
|
|
|
711
|
+
#### Alternative: Tag-Triggered OIDC Publishing
|
|
712
|
+
|
|
713
|
+
**Status**: Recommended for single-package repos, viable for monorepos
|
|
714
|
+
|
|
715
|
+
**Details**:
|
|
716
|
+
|
|
717
|
+
For simpler release workflows without Changesets, use tag-triggered GitHub Actions with
|
|
718
|
+
OIDC trusted publishing.
|
|
719
|
+
No NPM_TOKEN needed, no “Version Packages” PR workflow.
|
|
720
|
+
|
|
721
|
+
**Workflow**:
|
|
722
|
+
|
|
723
|
+
1. Manually bump version in package.json
|
|
724
|
+
2. Update CHANGELOG.md or release-notes.md
|
|
725
|
+
3. Commit, tag (e.g., `v1.2.3`), and push
|
|
726
|
+
4. GitHub Action publishes automatically on tag push
|
|
727
|
+
|
|
728
|
+
**One-time setup**:
|
|
729
|
+
|
|
730
|
+
1. Publish package manually once: `npm publish --access public`
|
|
731
|
+
2. Configure OIDC on npmjs.com → package settings → Trusted Publishing:
|
|
732
|
+
- Publisher: GitHub Actions
|
|
733
|
+
- Organization: your-org
|
|
734
|
+
- Repository: your-repo
|
|
735
|
+
- Workflow: `release.yml`
|
|
736
|
+
|
|
737
|
+
**Release workflow** (`.github/workflows/release.yml`):
|
|
738
|
+
|
|
739
|
+
```yaml
|
|
740
|
+
name: Release
|
|
741
|
+
|
|
742
|
+
on:
|
|
743
|
+
push:
|
|
744
|
+
tags: ['v*']
|
|
745
|
+
|
|
746
|
+
permissions:
|
|
747
|
+
contents: write
|
|
748
|
+
id-token: write # Required for OIDC
|
|
749
|
+
|
|
750
|
+
jobs:
|
|
751
|
+
release:
|
|
752
|
+
runs-on: ubuntu-latest
|
|
753
|
+
steps:
|
|
754
|
+
- uses: actions/checkout@v6
|
|
755
|
+
with:
|
|
756
|
+
fetch-depth: 0
|
|
757
|
+
|
|
758
|
+
- uses: pnpm/action-setup@v4
|
|
759
|
+
|
|
760
|
+
- uses: actions/setup-node@v6
|
|
761
|
+
with:
|
|
762
|
+
node-version: 24
|
|
763
|
+
cache: pnpm
|
|
764
|
+
registry-url: 'https://registry.npmjs.org'
|
|
765
|
+
|
|
766
|
+
- run: pnpm install --frozen-lockfile
|
|
767
|
+
- run: pnpm build
|
|
768
|
+
- run: pnpm publint
|
|
769
|
+
|
|
770
|
+
- name: Publish to npm
|
|
771
|
+
run: pnpm -r publish --access public --no-git-checks
|
|
772
|
+
env:
|
|
773
|
+
NPM_CONFIG_PROVENANCE: true # Automatic provenance attestation
|
|
774
|
+
|
|
775
|
+
- name: Create GitHub Release
|
|
776
|
+
uses: softprops/action-gh-release@v2
|
|
777
|
+
with:
|
|
778
|
+
body_path: release-notes.md
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Advantages**:
|
|
782
|
+
|
|
783
|
+
- No NPM_TOKEN secret to manage or rotate
|
|
784
|
+
- Provenance attestation included automatically
|
|
785
|
+
- Simpler workflow (no Changesets PR dance)
|
|
786
|
+
- Works with existing git tag practices
|
|
787
|
+
|
|
788
|
+
**Disadvantages**:
|
|
789
|
+
|
|
790
|
+
- Manual version bumps (vs Changesets automation)
|
|
791
|
+
- No automated changelog generation
|
|
792
|
+
- Requires public GitHub repository
|
|
793
|
+
|
|
794
|
+
**Assessment**: Ideal for single-package repos or teams comfortable with manual
|
|
795
|
+
versioning. For large monorepos with many packages, Changesets provides better
|
|
796
|
+
automation.
|
|
797
|
+
|
|
798
|
+
**References**:
|
|
799
|
+
|
|
800
|
+
- [npm Trusted Publishing](https://docs.npmjs.com/generating-provenance-statements)
|
|
801
|
+
- [GitHub OIDC tokens](https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
802
|
+
|
|
803
|
+
* * *
|
|
804
|
+
|
|
656
805
|
#### Dynamic Git-Based Versioning
|
|
657
806
|
|
|
658
807
|
**Status**: Recommended for dev builds
|
|
@@ -1020,7 +1169,7 @@ Use it for all TypeScript monorepo projects.
|
|
|
1020
1169
|
|
|
1021
1170
|
**Status**: Recommended
|
|
1022
1171
|
|
|
1023
|
-
**`.github/workflows/ci.yml
|
|
1172
|
+
**`.github/workflows/ci.yml`** (minimal single-job):
|
|
1024
1173
|
|
|
1025
1174
|
```yaml
|
|
1026
1175
|
name: CI
|
|
@@ -1037,8 +1186,6 @@ jobs:
|
|
|
1037
1186
|
- uses: actions/checkout@v6
|
|
1038
1187
|
|
|
1039
1188
|
- uses: pnpm/action-setup@v4
|
|
1040
|
-
with:
|
|
1041
|
-
version: 10
|
|
1042
1189
|
|
|
1043
1190
|
- uses: actions/setup-node@v6
|
|
1044
1191
|
with:
|
|
@@ -1053,6 +1200,46 @@ jobs:
|
|
|
1053
1200
|
- run: pnpm test
|
|
1054
1201
|
```
|
|
1055
1202
|
|
|
1203
|
+
**Multi-job CI with cross-platform testing** (recommended for CLI tools):
|
|
1204
|
+
|
|
1205
|
+
```yaml
|
|
1206
|
+
jobs:
|
|
1207
|
+
test:
|
|
1208
|
+
name: Test (${{ matrix.os }})
|
|
1209
|
+
runs-on: ${{ matrix.os }}
|
|
1210
|
+
strategy:
|
|
1211
|
+
fail-fast: false
|
|
1212
|
+
matrix:
|
|
1213
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
1214
|
+
steps:
|
|
1215
|
+
- uses: actions/checkout@v6
|
|
1216
|
+
- uses: pnpm/action-setup@v4
|
|
1217
|
+
- uses: actions/setup-node@v6
|
|
1218
|
+
with:
|
|
1219
|
+
node-version: 24
|
|
1220
|
+
cache: pnpm
|
|
1221
|
+
- run: pnpm install --frozen-lockfile
|
|
1222
|
+
- run: pnpm build
|
|
1223
|
+
- run: pnpm test
|
|
1224
|
+
|
|
1225
|
+
lint:
|
|
1226
|
+
name: Lint & Coverage
|
|
1227
|
+
runs-on: ubuntu-latest
|
|
1228
|
+
steps:
|
|
1229
|
+
- uses: actions/checkout@v6
|
|
1230
|
+
- uses: pnpm/action-setup@v4
|
|
1231
|
+
- uses: actions/setup-node@v6
|
|
1232
|
+
with:
|
|
1233
|
+
node-version: 24
|
|
1234
|
+
cache: pnpm
|
|
1235
|
+
- run: pnpm install --frozen-lockfile
|
|
1236
|
+
- run: pnpm format:check
|
|
1237
|
+
- run: pnpm lint:check
|
|
1238
|
+
- run: pnpm build
|
|
1239
|
+
- run: pnpm publint
|
|
1240
|
+
- run: pnpm test:coverage
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1056
1243
|
**Key points**:
|
|
1057
1244
|
|
|
1058
1245
|
- Node.js 24 is the current LTS ("Krypton", active until Oct 2026, maintained until Apr
|
|
@@ -1061,12 +1248,19 @@ jobs:
|
|
|
1061
1248
|
- `actions/checkout@v6` requires Actions Runner v2.329.0+ (stores credentials under
|
|
1062
1249
|
$RUNNER_TEMP)
|
|
1063
1250
|
|
|
1064
|
-
- `pnpm/action-setup@v4` includes built-in caching
|
|
1251
|
+
- `pnpm/action-setup@v4` includes built-in caching (no `version:` needed if
|
|
1252
|
+
`packageManager` is set in `package.json`)
|
|
1065
1253
|
|
|
1066
1254
|
- `actions/setup-node@v6` with `cache: pnpm` provides additional caching
|
|
1067
1255
|
|
|
1068
1256
|
- `--frozen-lockfile` ensures CI uses exact versions from lockfile
|
|
1069
1257
|
|
|
1258
|
+
- For CLI tools, cross-platform testing catches platform-specific issues (path
|
|
1259
|
+
separators, file permissions, line endings)
|
|
1260
|
+
|
|
1261
|
+
- Separating lint/coverage from tests enables parallel execution and clearer failure
|
|
1262
|
+
diagnosis
|
|
1263
|
+
|
|
1070
1264
|
**References**:
|
|
1071
1265
|
|
|
1072
1266
|
- [pnpm action-setup](https://github.com/pnpm/action-setup)
|
|
@@ -1184,7 +1378,7 @@ coverage
|
|
|
1184
1378
|
| --- | --- | --- |
|
|
1185
1379
|
| `printWidth` | 100 | Wider than default 80; fits modern screens |
|
|
1186
1380
|
| `singleQuote` | true | Common in JS ecosystem, less visual noise |
|
|
1187
|
-
| `trailingComma` |
|
|
1381
|
+
| `trailingComma` | “all” | Cleaner diffs, easier reordering |
|
|
1188
1382
|
| `semi` | true | Explicit; avoids ASI edge cases |
|
|
1189
1383
|
|
|
1190
1384
|
**Assessment**: Prettier eliminates formatting debates and ensures consistency.
|
|
@@ -1198,6 +1392,74 @@ Use `eslint-config-prettier` to disable ESLint rules that conflict with Prettier
|
|
|
1198
1392
|
|
|
1199
1393
|
* * *
|
|
1200
1394
|
|
|
1395
|
+
#### Markdown Formatting with flowmark
|
|
1396
|
+
|
|
1397
|
+
**Status**: Optional
|
|
1398
|
+
|
|
1399
|
+
**Details**:
|
|
1400
|
+
|
|
1401
|
+
For markdown files, [flowmark](https://github.com/jlevy/flowmark) provides semantic
|
|
1402
|
+
line-breaking (reflowing) that creates cleaner git diffs than traditional hard-wrap
|
|
1403
|
+
formatters.
|
|
1404
|
+
|
|
1405
|
+
**Key differences from Prettier**:
|
|
1406
|
+
|
|
1407
|
+
- Prettier doesn’t format markdown by default (prose-wrap: preserve)
|
|
1408
|
+
- flowmark breaks lines at semantic boundaries (after sentences, list items)
|
|
1409
|
+
- Result: Git diffs show actual content changes, not just rewrapping
|
|
1410
|
+
|
|
1411
|
+
**Installation**: None required if using `uvx` (uv’s tool runner)
|
|
1412
|
+
|
|
1413
|
+
**Configuration**:
|
|
1414
|
+
|
|
1415
|
+
Add to `.prettierignore` to prevent Prettier from touching markdown:
|
|
1416
|
+
|
|
1417
|
+
```
|
|
1418
|
+
*.md
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
Add a `.flowmarkignore` file at the repo root to skip tool-managed files:
|
|
1422
|
+
|
|
1423
|
+
```
|
|
1424
|
+
.tbd/
|
|
1425
|
+
node_modules/
|
|
1426
|
+
dist/
|
|
1427
|
+
attic/
|
|
1428
|
+
template/
|
|
1429
|
+
coverage/
|
|
1430
|
+
.changeset/
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
**Lefthook integration**:
|
|
1434
|
+
|
|
1435
|
+
```yaml
|
|
1436
|
+
pre-commit:
|
|
1437
|
+
commands:
|
|
1438
|
+
format-md:
|
|
1439
|
+
glob: '*.md'
|
|
1440
|
+
exclude:
|
|
1441
|
+
- CLAUDE.md
|
|
1442
|
+
- AGENTS.md
|
|
1443
|
+
- template/**
|
|
1444
|
+
run: uvx flowmark@latest --auto {staged_files}
|
|
1445
|
+
stage_fixed: true
|
|
1446
|
+
```
|
|
1447
|
+
|
|
1448
|
+
**CI consideration**: flowmark is typically NOT enforced in CI (unlike Prettier for
|
|
1449
|
+
code). Markdown formatting rarely causes functional issues, and flowmark can be brittle
|
|
1450
|
+
on edge cases (tables, complex nesting).
|
|
1451
|
+
|
|
1452
|
+
**Assessment**: Useful for projects with extensive markdown documentation.
|
|
1453
|
+
The cleaner diffs make reviews easier.
|
|
1454
|
+
Optional tool; requires `uv` installed locally.
|
|
1455
|
+
|
|
1456
|
+
**References**:
|
|
1457
|
+
|
|
1458
|
+
- [flowmark on GitHub](https://github.com/jlevy/flowmark)
|
|
1459
|
+
- [uv installation](https://docs.astral.sh/uv/)
|
|
1460
|
+
|
|
1461
|
+
* * *
|
|
1462
|
+
|
|
1201
1463
|
#### Format Scripts Pattern
|
|
1202
1464
|
|
|
1203
1465
|
**Status**: Recommended
|
|
@@ -1317,7 +1579,7 @@ pre-commit:
|
|
|
1317
1579
|
commands:
|
|
1318
1580
|
# Auto-format with prettier (~500ms)
|
|
1319
1581
|
format:
|
|
1320
|
-
glob: '*.{js,ts,tsx,json}'
|
|
1582
|
+
glob: '*.{js,ts,tsx,json,yaml,yml}'
|
|
1321
1583
|
run: npx prettier --write --log-level warn {staged_files}
|
|
1322
1584
|
stage_fixed: true
|
|
1323
1585
|
priority: 1
|
|
@@ -1717,6 +1979,202 @@ Reserve vite-node for projects that specifically need Vite’s transformation pi
|
|
|
1717
1979
|
|
|
1718
1980
|
* * *
|
|
1719
1981
|
|
|
1982
|
+
#### CJS Bootstrap for Compile Cache
|
|
1983
|
+
|
|
1984
|
+
**Status**: Recommended for CLI tools
|
|
1985
|
+
|
|
1986
|
+
**Details**:
|
|
1987
|
+
|
|
1988
|
+
Node.js 22.8.0+ supports `module.enableCompileCache()`, which caches compiled bytecode
|
|
1989
|
+
on disk for faster subsequent runs.
|
|
1990
|
+
However, this must be called **before** any ESM modules are loaded—ESM static imports
|
|
1991
|
+
are resolved before module code runs, so calling it in an ESM file is “too late.”
|
|
1992
|
+
|
|
1993
|
+
The solution is a **CJS bootstrap**: a tiny CommonJS entry point that enables compile
|
|
1994
|
+
cache, then dynamically imports the real ESM CLI binary.
|
|
1995
|
+
|
|
1996
|
+
**`src/cli/bin-bootstrap.cjs`**:
|
|
1997
|
+
|
|
1998
|
+
```js
|
|
1999
|
+
'use strict';
|
|
2000
|
+
|
|
2001
|
+
const path = require('node:path');
|
|
2002
|
+
const { pathToFileURL } = require('node:url');
|
|
2003
|
+
|
|
2004
|
+
// Enable compile cache BEFORE loading any ESM modules.
|
|
2005
|
+
// Available in Node 22.8.0+, gracefully ignored in older versions.
|
|
2006
|
+
try {
|
|
2007
|
+
const mod = require('node:module');
|
|
2008
|
+
if (typeof mod.enableCompileCache === 'function') {
|
|
2009
|
+
mod.enableCompileCache();
|
|
2010
|
+
}
|
|
2011
|
+
} catch {
|
|
2012
|
+
// Silently ignore - caching is an optimization, not required.
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
// Load the bundled CLI binary (ESM).
|
|
2016
|
+
const binPath = path.join(__dirname, 'bin.mjs');
|
|
2017
|
+
import(pathToFileURL(binPath).href);
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
**`package.json` bin field**:
|
|
2021
|
+
|
|
2022
|
+
```json
|
|
2023
|
+
{
|
|
2024
|
+
"bin": {
|
|
2025
|
+
"cli-name": "./dist/bin-bootstrap.cjs"
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
**Why this matters**: On repeated invocations (common for CLI tools), compile cache
|
|
2031
|
+
reduces startup time significantly—Node.js skips re-parsing and re-compiling JavaScript
|
|
2032
|
+
that hasn’t changed.
|
|
2033
|
+
|
|
2034
|
+
**Assessment**: Essential optimization for any CLI tool that targets Node.js 22+. The
|
|
2035
|
+
CJS bootstrap adds minimal complexity (one small file) for meaningful startup
|
|
2036
|
+
improvement.
|
|
2037
|
+
|
|
2038
|
+
* * *
|
|
2039
|
+
|
|
2040
|
+
#### Dependency Bundling for CLI Startup
|
|
2041
|
+
|
|
2042
|
+
**Status**: Recommended for CLI tools
|
|
2043
|
+
|
|
2044
|
+
**Details**:
|
|
2045
|
+
|
|
2046
|
+
CLI tools benefit from bundling their runtime dependencies directly into the binary
|
|
2047
|
+
instead of resolving them from `node_modules` at runtime.
|
|
2048
|
+
tsdown’s `noExternal` option enables this.
|
|
2049
|
+
|
|
2050
|
+
**tsdown config for bundled CLI**:
|
|
2051
|
+
|
|
2052
|
+
```typescript
|
|
2053
|
+
{
|
|
2054
|
+
entry: { bin: 'src/cli/bin.ts' },
|
|
2055
|
+
format: ['esm'],
|
|
2056
|
+
platform: 'node',
|
|
2057
|
+
target: 'node24',
|
|
2058
|
+
noExternal: [
|
|
2059
|
+
'yaml',
|
|
2060
|
+
'commander',
|
|
2061
|
+
'picocolors',
|
|
2062
|
+
'zod',
|
|
2063
|
+
// ... all runtime deps
|
|
2064
|
+
],
|
|
2065
|
+
// Acknowledge intentional bundling (suppresses tsdown 0.20+ warning)
|
|
2066
|
+
inlineOnly: false,
|
|
2067
|
+
}
|
|
2068
|
+
```
|
|
2069
|
+
|
|
2070
|
+
**Trade-offs**:
|
|
2071
|
+
|
|
2072
|
+
| Aspect | Bundled | Unbundled |
|
|
2073
|
+
| --- | --- | --- |
|
|
2074
|
+
| Startup time | Faster (no resolution) | Slower (resolves deps) |
|
|
2075
|
+
| Binary size | Larger (~1MB+ typical) | Smaller |
|
|
2076
|
+
| Deduplication | No (each package bundles its own) | Yes (shared node\_modules) |
|
|
2077
|
+
| Use case | CLI tools, serverless | Libraries |
|
|
2078
|
+
|
|
2079
|
+
**Assessment**: Bundling is the right choice for CLI tools where startup time matters.
|
|
2080
|
+
Libraries should NOT bundle dependencies (consumers may need to deduplicate).
|
|
2081
|
+
|
|
2082
|
+
* * *
|
|
2083
|
+
|
|
2084
|
+
#### Multi-Config tsdown (Array Pattern)
|
|
2085
|
+
|
|
2086
|
+
**Status**: Recommended for CLI/library hybrid packages
|
|
2087
|
+
|
|
2088
|
+
**Details**:
|
|
2089
|
+
|
|
2090
|
+
When a package serves as both a library and a CLI tool, use `defineConfig([...])` with
|
|
2091
|
+
separate configurations for each output type:
|
|
2092
|
+
|
|
2093
|
+
```typescript
|
|
2094
|
+
import { defineConfig } from 'tsdown';
|
|
2095
|
+
|
|
2096
|
+
const commonOptions = {
|
|
2097
|
+
format: ['esm'] as 'esm'[],
|
|
2098
|
+
platform: 'node' as const,
|
|
2099
|
+
target: 'node24' as const,
|
|
2100
|
+
sourcemap: true,
|
|
2101
|
+
dts: true,
|
|
2102
|
+
define: {
|
|
2103
|
+
__VERSION__: JSON.stringify(version),
|
|
2104
|
+
},
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
export default defineConfig([
|
|
2108
|
+
// Library entry points (ESM + DTS, no bundled deps)
|
|
2109
|
+
{
|
|
2110
|
+
...commonOptions,
|
|
2111
|
+
entry: {
|
|
2112
|
+
index: 'src/index.ts',
|
|
2113
|
+
cli: 'src/cli/cli.ts',
|
|
2114
|
+
},
|
|
2115
|
+
clean: true,
|
|
2116
|
+
},
|
|
2117
|
+
// CLI binary (ESM, bundled deps for fast startup)
|
|
2118
|
+
{
|
|
2119
|
+
...commonOptions,
|
|
2120
|
+
entry: { bin: 'src/cli/bin.ts' },
|
|
2121
|
+
banner: '#!/usr/bin/env node',
|
|
2122
|
+
clean: false,
|
|
2123
|
+
noExternal: ['yaml', 'commander', 'picocolors', 'zod'],
|
|
2124
|
+
inlineOnly: false,
|
|
2125
|
+
},
|
|
2126
|
+
// CJS bootstrap (enables compile cache before ESM loads)
|
|
2127
|
+
{
|
|
2128
|
+
format: ['cjs'] as 'cjs'[],
|
|
2129
|
+
platform: 'node' as const,
|
|
2130
|
+
target: 'node24' as const,
|
|
2131
|
+
sourcemap: true,
|
|
2132
|
+
dts: false,
|
|
2133
|
+
entry: { 'bin-bootstrap': 'src/cli/bin-bootstrap.cjs' },
|
|
2134
|
+
banner: '#!/usr/bin/env node',
|
|
2135
|
+
clean: false,
|
|
2136
|
+
},
|
|
2137
|
+
]);
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
**Key patterns**:
|
|
2141
|
+
|
|
2142
|
+
1. **`commonOptions` object**: Avoids duplication across configs
|
|
2143
|
+
2. **`clean: true` only on first config**: Prevents later configs from deleting earlier
|
|
2144
|
+
output
|
|
2145
|
+
3. **Separate DTS generation**: Only library entry points need `.d.mts` files
|
|
2146
|
+
4. **Different `noExternal` per config**: Bundle deps for CLI, leave unbundled for
|
|
2147
|
+
library
|
|
2148
|
+
|
|
2149
|
+
**Assessment**: This pattern provides optimal output for each use case without
|
|
2150
|
+
compromise.
|
|
2151
|
+
|
|
2152
|
+
* * *
|
|
2153
|
+
|
|
2154
|
+
#### Conditional Build Script
|
|
2155
|
+
|
|
2156
|
+
**Status**: Recommended
|
|
2157
|
+
|
|
2158
|
+
**Details**:
|
|
2159
|
+
|
|
2160
|
+
Pre-push hooks should avoid unnecessary rebuilds.
|
|
2161
|
+
A `build-if-needed` script checks whether the build output is up-to-date before running
|
|
2162
|
+
the full build:
|
|
2163
|
+
|
|
2164
|
+
```json
|
|
2165
|
+
{
|
|
2166
|
+
"scripts": {
|
|
2167
|
+
"build:check": "node packages/my-cli/scripts/build-if-needed.mjs"
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
```
|
|
2171
|
+
|
|
2172
|
+
The script compares modification times of `src/` files against `dist/` output and only
|
|
2173
|
+
triggers a build if source is newer.
|
|
2174
|
+
This makes pre-push hooks near-instant when the build is already current.
|
|
2175
|
+
|
|
2176
|
+
* * *
|
|
2177
|
+
|
|
1720
2178
|
### 14. Private Package Distribution
|
|
1721
2179
|
|
|
1722
2180
|
#### Option A: GitHub Packages (Recommended)
|
|
@@ -2123,6 +2581,17 @@ than discovering them when users try to use the library in browser/edge contexts
|
|
|
2123
2581
|
This eliminates “did I forget to build?”
|
|
2124
2582
|
confusion.
|
|
2125
2583
|
|
|
2584
|
+
20. **Use CJS bootstrap for CLI startup**: Enable Node.js compile cache via a CJS
|
|
2585
|
+
bootstrap file that runs before ESM module loading.
|
|
2586
|
+
This significantly improves repeated CLI invocation times on Node.js 22.8+.
|
|
2587
|
+
|
|
2588
|
+
21. **Bundle CLI dependencies**: Use tsdown’s `noExternal` to bundle runtime deps into
|
|
2589
|
+
the CLI binary for faster startup (no `node_modules` resolution at runtime).
|
|
2590
|
+
|
|
2591
|
+
22. **Add guard tests for node-free core**: If your library entry point should be
|
|
2592
|
+
node-free, add automated tests that verify no `node:` imports leak into the public
|
|
2593
|
+
API surface.
|
|
2594
|
+
|
|
2126
2595
|
* * *
|
|
2127
2596
|
|
|
2128
2597
|
## Open Research Questions
|
|
@@ -2335,7 +2804,7 @@ ready for public release.
|
|
|
2335
2804
|
{
|
|
2336
2805
|
"name": "project-workspace",
|
|
2337
2806
|
"private": true,
|
|
2338
|
-
"packageManager": "pnpm@10.
|
|
2807
|
+
"packageManager": "pnpm@10.28.2",
|
|
2339
2808
|
"engines": {
|
|
2340
2809
|
"node": ">=24"
|
|
2341
2810
|
},
|
|
@@ -2362,7 +2831,7 @@ ready for public release.
|
|
|
2362
2831
|
"@eslint/js": "^9.0.0",
|
|
2363
2832
|
"eslint": "^9.0.0",
|
|
2364
2833
|
"eslint-config-prettier": "^10.0.0",
|
|
2365
|
-
"lefthook": "^2.0.
|
|
2834
|
+
"lefthook": "^2.0.15",
|
|
2366
2835
|
"npm-check-updates": "^19.0.0",
|
|
2367
2836
|
"prettier": "^3.0.0",
|
|
2368
2837
|
"typescript": "^5.9.0",
|
|
@@ -2656,7 +3125,52 @@ For CLI packages, consider restricting console usage to centralized output funct
|
|
|
2656
3125
|
}
|
|
2657
3126
|
```
|
|
2658
3127
|
|
|
2659
|
-
|
|
3128
|
+
**Project-Specific Restricted Imports**:
|
|
3129
|
+
|
|
3130
|
+
Use `@typescript-eslint/no-restricted-imports` to enforce project-specific patterns.
|
|
3131
|
+
For example, requiring atomic file writes:
|
|
3132
|
+
|
|
3133
|
+
```javascript
|
|
3134
|
+
{
|
|
3135
|
+
files: ['**/*.ts'],
|
|
3136
|
+
rules: {
|
|
3137
|
+
'@typescript-eslint/no-restricted-imports': [
|
|
3138
|
+
'error',
|
|
3139
|
+
{
|
|
3140
|
+
paths: [
|
|
3141
|
+
{
|
|
3142
|
+
name: 'node:fs/promises',
|
|
3143
|
+
importNames: ['writeFile'],
|
|
3144
|
+
message: 'Use writeFile from "atomically" instead for atomic writes.',
|
|
3145
|
+
},
|
|
3146
|
+
],
|
|
3147
|
+
},
|
|
3148
|
+
],
|
|
3149
|
+
},
|
|
3150
|
+
}
|
|
3151
|
+
```
|
|
3152
|
+
|
|
3153
|
+
**CLI Command Handler Relaxations**:
|
|
3154
|
+
|
|
3155
|
+
Commander.js command handlers often have async signatures and unused parameters.
|
|
3156
|
+
Relax strict rules for these files:
|
|
3157
|
+
|
|
3158
|
+
```javascript
|
|
3159
|
+
{
|
|
3160
|
+
files: ['**/cli/commands/**/*.ts'],
|
|
3161
|
+
rules: {
|
|
3162
|
+
'@typescript-eslint/require-await': 'off',
|
|
3163
|
+
'@typescript-eslint/no-unused-vars': [
|
|
3164
|
+
'error',
|
|
3165
|
+
{ argsIgnorePattern: '^_|^options$|^id$|^query$' },
|
|
3166
|
+
],
|
|
3167
|
+
},
|
|
3168
|
+
}
|
|
3169
|
+
```
|
|
3170
|
+
|
|
3171
|
+
### Appendix D: tsdown Config Examples
|
|
3172
|
+
|
|
3173
|
+
#### Simple Library (Single Config)
|
|
2660
3174
|
|
|
2661
3175
|
```typescript
|
|
2662
3176
|
// tsdown.config.ts
|
|
@@ -2678,6 +3192,62 @@ export default defineConfig({
|
|
|
2678
3192
|
});
|
|
2679
3193
|
```
|
|
2680
3194
|
|
|
3195
|
+
#### CLI/Library Hybrid (Multi-Config with Bundling)
|
|
3196
|
+
|
|
3197
|
+
For packages that are both a library and a CLI tool, use an array of configs to optimize
|
|
3198
|
+
each output separately:
|
|
3199
|
+
|
|
3200
|
+
```typescript
|
|
3201
|
+
// tsdown.config.ts
|
|
3202
|
+
import { defineConfig } from 'tsdown';
|
|
3203
|
+
import { getGitVersion } from './scripts/git-version.mjs';
|
|
3204
|
+
|
|
3205
|
+
const version = getGitVersion();
|
|
3206
|
+
|
|
3207
|
+
const commonOptions = {
|
|
3208
|
+
format: ['esm'] as 'esm'[],
|
|
3209
|
+
platform: 'node' as const,
|
|
3210
|
+
target: 'node24' as const,
|
|
3211
|
+
sourcemap: true,
|
|
3212
|
+
dts: true,
|
|
3213
|
+
define: {
|
|
3214
|
+
__VERSION__: JSON.stringify(version),
|
|
3215
|
+
},
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3218
|
+
export default defineConfig([
|
|
3219
|
+
// Library entry points (unbundled, with type declarations)
|
|
3220
|
+
{
|
|
3221
|
+
...commonOptions,
|
|
3222
|
+
entry: {
|
|
3223
|
+
index: 'src/index.ts',
|
|
3224
|
+
cli: 'src/cli/cli.ts',
|
|
3225
|
+
},
|
|
3226
|
+
clean: true,
|
|
3227
|
+
},
|
|
3228
|
+
// CLI binary (bundled deps for fast startup, shebang banner)
|
|
3229
|
+
{
|
|
3230
|
+
...commonOptions,
|
|
3231
|
+
entry: { bin: 'src/cli/bin.ts' },
|
|
3232
|
+
banner: '#!/usr/bin/env node',
|
|
3233
|
+
clean: false,
|
|
3234
|
+
noExternal: ['yaml', 'commander', 'picocolors', 'zod'],
|
|
3235
|
+
inlineOnly: false,
|
|
3236
|
+
},
|
|
3237
|
+
// CJS bootstrap (enables compile cache before ESM loads)
|
|
3238
|
+
{
|
|
3239
|
+
format: ['cjs'] as 'cjs'[],
|
|
3240
|
+
platform: 'node' as const,
|
|
3241
|
+
target: 'node24' as const,
|
|
3242
|
+
sourcemap: true,
|
|
3243
|
+
dts: false,
|
|
3244
|
+
entry: { 'bin-bootstrap': 'src/cli/bin-bootstrap.cjs' },
|
|
3245
|
+
banner: '#!/usr/bin/env node',
|
|
3246
|
+
clean: false,
|
|
3247
|
+
},
|
|
3248
|
+
]);
|
|
3249
|
+
```
|
|
3250
|
+
|
|
2681
3251
|
### Appendix E: Complete lefthook.yml Example
|
|
2682
3252
|
|
|
2683
3253
|
```yaml
|