get-tbd 0.1.27 → 0.1.28

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.
@@ -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-18
8
+ **Last Updated**: 2026-05-21
9
9
 
10
10
  **Related**:
11
11
 
@@ -14,6 +14,7 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
14
14
  - [tsdown Documentation](https://tsdown.dev/)
15
15
  - [publint Documentation](https://publint.dev/docs/)
16
16
  - [Companion: Bun Monorepo Patterns](./bun-monorepo-patterns.md)
17
+ - [Supply-Chain Mitigation](#supply-chain-mitigation) (in this document)
17
18
 
18
19
  * * *
19
20
 
@@ -23,24 +24,31 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
23
24
 
24
25
  | Tool / Package | Version | Check For Updates |
25
26
  | --- | --- | --- |
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
- | **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
- | **publint** | ^0.3.0 | [npmjs.com/package/publint](https://www.npmjs.com/package/publint) |
31
- | **@changesets/cli** | ^2.29.0 | [github.com/changesets/changesets/releases](https://github.com/changesets/changesets/releases) |
32
- | **@types/node** | ^24.0.0 | Should match Node.js major version (^25.0.0 also available) |
33
- | **actions/checkout** | v6 | [github.com/actions/checkout/releases](https://github.com/actions/checkout/releases) — Latest (v6.0.2), requires Runner v2.329.0+ |
34
- | **actions/setup-node** | v6 | [github.com/actions/setup-node/releases](https://github.com/actions/setup-node/releases) |
35
- | **pnpm/action-setup** | v4 | [github.com/pnpm/action-setup/releases](https://github.com/pnpm/action-setup/releases) |
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.1.1 latest. v2 dropped regexp `exclude`, `skip_output`. |
38
- | **npm-check-updates** | ^19.0.0 | [npmjs.com/package/npm-check-updates](https://www.npmjs.com/package/npm-check-updates) |
39
- | **tsx** | ^4.21.0 | [github.com/privatenumber/tsx/releases](https://github.com/privatenumber/tsx/releases) |
40
- | **prettier** | ^3.0.0 | [github.com/prettier/prettier/releases](https://github.com/prettier/prettier/releases) |
27
+ | **Node.js** | 24 (LTS "Krypton") | [nodejs.org/releases](https://nodejs.org/en/about/previous-releases) — Node 24 Active LTS until Oct 2026. **Node 26 Current** shipped 2026-05-05 (Temporal API enabled by default, V8 14.6, Undici 8.0). **Node 20 went EOL 2026-03-24.** Node 26 is the last release on the old two-major-per-year cadence; starting v27, all majors become LTS (one per year). |
28
+ | **pnpm** | ^11.0.0 (11.2.x too recent) | [github.com/pnpm/pnpm/releases](https://github.com/pnpm/pnpm/releases) — **Pinned to 11.0.0 (2026-04-28) per the 14-day rule**; 11.2.0/11.2.2 shipped 2026-05-20/21 (1–2 days old today). Breaking changes: pure ESM (requires **Node 22+**); SQLite-based store (`$STORE/index.db`); **`minimumReleaseAge` defaults to 1440 minutes (1 day)** for supply-chain hygiene; **`blockExoticSubdeps` defaults to `true`**; `onlyBuiltDependencies`/`neverBuiltDependencies` removed and replaced by **`allowBuilds`**; `patchedDependencies` format simplified; experimental Rust-based `@pnpm/pacquet` engine arrives in 11.2+. v11.1 added `pnpm audit signatures`, `pnpm bugs`, `pnpm owner`. |
29
+ | **TypeScript** | ^6.0.3 | [github.com/microsoft/TypeScript/releases](https://github.com/microsoft/TypeScript/releases) — **6.0.3 stable** (shipped 2026-03-23). 6.0 is the last JavaScript-based release; `strict: true` is now the default, ESM is the default module system, ~9 compiler settings flipped defaults. **TS 7.0 Beta** (Project Corsa, Go rewrite) shipped 2026-04-21 as `@typescript/native-preview` (binary: `tsgo`); claims ~10× type-check speed and ~3× less memory. Stable expected mid-to-late 2026. Do not adopt `tsgo` for production builds yet. |
30
+ | **tsdown** | ^0.22.0 | [github.com/rolldown/tsdown/releases](https://github.com/rolldown/tsdown/releases) — 0.22.0 (2026-05-07). Has not reached 1.0; incremental 0.20→0.22 releases since Feb 2026; no deprecations. Requires Node.js 20.19+. |
31
+ | **publint** | ^0.3.20 (0.3.21 too recent) | [npmjs.com/package/publint](https://www.npmjs.com/package/publint) — **Pinned to 0.3.20 (2026-05-08) per the 14-day rule**; 0.3.21 (2026-05-13) is 9 days old today. Re-enabled TS/TSX file existence checks; `exports["default"]` support; bug fixes. |
32
+ | **@changesets/cli** | ^2.31.0 | [github.com/changesets/changesets/releases](https://github.com/changesets/changesets/releases) — 2.31.0 latest. No native Bun support added. |
33
+ | **@types/node** | ^24.0.0 | [@types/node npm](https://www.npmjs.com/package/@types/node) — Track Node.js major version. @types/node@25.x available; @types/node@26.x will follow Node 26. |
34
+ | **actions/checkout** | v6 | [github.com/actions/checkout/releases](https://github.com/actions/checkout/releases) — v6.0.2 (2026-01-09). Credentials now stored in `$RUNNER_TEMP` rather than `.git/config`; Node 24 runtime; requires runner ≥ 2.327.1. |
35
+ | **actions/setup-node** | v6 | [github.com/actions/setup-node/releases](https://github.com/actions/setup-node/releases) — Supports Node 24 by default. **Note GitHub's 2026-06-02 deadline forcing Node.js 20 actions to Node.js 24.** |
36
+ | **pnpm/action-setup** | v6 | [github.com/pnpm/action-setup/releases](https://github.com/pnpm/action-setup/releases) — **v6 required for pnpm 11+ support.** v4 (previously documented) does not handle pnpm 11's ESM-only distribution correctly. |
37
+ | **changesets/action** | v1 | [github.com/changesets/action](https://github.com/changesets/action) — Still v1. No v2. |
38
+ | **lefthook** | ^2.1.5 (2.1.7/2.1.8 too recent) | [github.com/evilmartians/lefthook/releases](https://github.com/evilmartians/lefthook/releases) — **Pinned to 2.1.5 (2026-04-06) per the 14-day rule**; 2.1.7/2.1.8 both shipped 2026-05-19. Patch-level since 2.1.1. v2 still excludes regexp `exclude` and `skip_output` from v1. |
39
+ | **npm-check-updates** | ^22.0.0 (22.2.0 too recent) | [npmjs.com/package/npm-check-updates](https://www.npmjs.com/package/npm-check-updates) — **Major version jump from 19 to 22.** **Pinned to 22.0.0 (2026-04-25) per the 14-day rule**; 22.2.0 (2026-05-12) is 10 days old today. Now pure ESM; named imports only (`import { run } from 'npm-check-updates'`); `.ncurc.js` with `module.exports` no longer works in `"type": "module"` projects (use `.ncurc.cjs`). **Ships `--cooldown <days>` to refuse versions younger than the specified age** — primary enforcement for the 14-day package-age rule. See [Supply-Chain Mitigation](#supply-chain-mitigation). |
40
+ | **tsx** | ^4.21.0 (4.22.x too recent) | [github.com/privatenumber/tsx/releases](https://github.com/privatenumber/tsx/releases) — **Pinned to 4.21.0 (2025-11-30) per the 14-day rule**; 4.22.0 shipped 2026-05-14 (8 days old) and 4.22.3 shipped 2026-05-19 (3 days old). Bump on next refresh once 4.22.x has aged. |
41
+ | **prettier** | ^3.8.3 | [github.com/prettier/prettier/releases](https://github.com/prettier/prettier/releases) — 3.8.3 stable. Prettier 4.0 is in alpha (4.0.0-alpha.13, CLI performance rewrite) — **not stable yet**; do not adopt. |
41
42
  | **eslint-config-prettier** | ^10.0.0 | [github.com/prettier/eslint-config-prettier/releases](https://github.com/prettier/eslint-config-prettier/releases) |
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.18 latest. Browser Mode stable, visual regression testing added. `coverage.all` removed in v4. |
43
+ | **ESLint** | ^10.0.0 | [github.com/eslint/eslint/releases](https://github.com/eslint/eslint/releases) — **ESLint 10.0.0 shipped 2026-02-06.** **Breaking**: `.eslintrc.*` configuration is completely removed — flat config (`eslint.config.js`) is the only supported format. Download size reduced (11 MB → 9.4 MB). Improved JSX reference tracking. **Minimum Node.js v20.19.0.** ESLint 9.x EOL is 2026-08-06. |
44
+ | **Vitest** | ^4.1.5 (4.1.6/4.1.7 too recent) | [github.com/vitest-dev/vitest/releases](https://github.com/vitest-dev/vitest/releases) — **Pinned to 4.1.5 (2026-04-21) per the 14-day rule**; 4.1.7 (2026-05-20) is 2 days old today. v4.1 (Mar 2026) added Vite 8 support, test tags, extended chai-style assertions for mocking. Vitest now reuses installed Vite instead of bundling. Browser Mode stable, visual regression added. `coverage.all` was removed in v4. Vitest 5.0.0-beta.3 in pre-release (requires Node 22+, Vite 6.4+) — **do not adopt yet**. |
45
+ | **Zod** | ^4.4.3 | [github.com/colinhacks/zod/releases](https://github.com/colinhacks/zod/releases) — **Zod 4 fully stable.** 14× faster string parsing, 7× faster array parsing, 6.5× faster object parsing vs Zod 3; core bundle 2.3× smaller. New `@zod/mini` package (~1.9 KB gzipped) for tree-shakable frontend validation. Migration from Zod 3 required — see [zod.dev/v4/changelog](https://zod.dev/v4/changelog). |
46
+ | **commander** | ^15.0.0 | [github.com/tj/commander.js/releases](https://github.com/tj/commander.js/releases) — Commander 15 shipping May 2026, **ESM-only, requires Node v22.12.0+**. Commander 14 moves to maintenance (security only) until May 2027. |
47
+ | **picocolors** | ^1.1.1 | [npmjs.com/package/picocolors](https://www.npmjs.com/package/picocolors) — Last release October 2024. Stable; no changes expected. |
48
+ | **dotenv** | ^17.4.2 | [npmjs.com/package/dotenv](https://www.npmjs.com/package/dotenv) — Stable. **Prefer Node.js native `--env-file` for Node ≥20.6** (production-ready since Node 24 LTS); use dotenv only when you need variable expansion, multiline values, or custom precedence logic. |
49
+ | **atomically** | ^2.1.1 | [npmjs.com/package/atomically](https://www.npmjs.com/package/atomically) — 2.1.1 (2026-02-08). Still maintained. |
50
+ | **yaml** | ^2.8.4 | [npmjs.com/package/yaml](https://www.npmjs.com/package/yaml) — 2.8.4 (2026-05-02). v3.0.0-1 is tagged "next" (pre-release) — do not adopt yet. |
51
+ | **@vitest/coverage-v8** | ^4.1.7 | [npmjs.com/package/@vitest/coverage-v8](https://www.npmjs.com/package/@vitest/coverage-v8) — Track Vitest version. |
44
52
 
45
53
  ### Reminders When Updating
46
54
 
@@ -67,6 +75,11 @@ author: Joshua Levy (github.com/jlevy) with LLM assistance
67
75
 
68
76
  6. **Review “Open Research Questions”** section for any resolved items
69
77
 
78
+ 7. **Honor the 14-day package-age rule** when bumping versions in code examples. See
79
+ [Supply-Chain Mitigation](#supply-chain-mitigation) — versions cited here should
80
+ be ≥14 days old at the time the table is updated, except where a clearly-noted
81
+ security exception applies.
82
+
70
83
  * * *
71
84
 
72
85
  ## Executive Summary
@@ -141,8 +154,20 @@ recommendations from the TypeScript and JavaScript ecosystem maintainers.
141
154
 
142
155
  - `pnpm deploy` command enables isolated production deployments for Docker
143
156
 
144
- **Assessment**: pnpm is the consensus choice for TypeScript monorepos in 2025, offering
145
- superior disk efficiency and stricter dependency management than npm or yarn.
157
+ - **pnpm 11 (shipped 2026-04-28)** adds significant supply-chain hardening
158
+ defaults: `minimumReleaseAge: 1440` (1 day) and `blockExoticSubdeps: true`.
159
+ We recommend overriding `minimumReleaseAge` to 14 days — see
160
+ [Supply-Chain Mitigation](#supply-chain-mitigation).
161
+
162
+ - **pnpm 11 is pure ESM and requires Node.js 22+.** Lifecycle script gating
163
+ has moved from `onlyBuiltDependencies`/`neverBuiltDependencies` to
164
+ **`allowBuilds`** (an explicit allowlist).
165
+
166
+ - The store is now a single SQLite database (`$STORE/index.db`) for faster
167
+ cache reads; this is invisible to users but matters for CI cache configs.
168
+
169
+ **Assessment**: pnpm remains the consensus choice for TypeScript monorepos, with
170
+ pnpm 11 adding meaningful supply-chain defaults and faster store I/O.
146
171
 
147
172
  **Key Configuration** (`pnpm-workspace.yaml`):
148
173
 
@@ -150,6 +175,13 @@ superior disk efficiency and stricter dependency management than npm or yarn.
150
175
  packages:
151
176
  - 'packages/*'
152
177
  - 'apps/*'
178
+
179
+ # Supply-chain hardening (see Supply-Chain Mitigation section)
180
+ minimumReleaseAge: 20160 # 14 days in minutes
181
+ blockExoticSubdeps: true # Default in pnpm 11; explicit is good
182
+ allowBuilds: # Replaces onlyBuiltDependencies in pnpm 11
183
+ - esbuild
184
+ - sharp
153
185
  ```
154
186
 
155
187
  **Root `.npmrc`**:
@@ -157,6 +189,8 @@ packages:
157
189
  ```ini
158
190
  save-workspace-protocol=true
159
191
  prefer-workspace-packages=true
192
+ # Belt-and-suspenders with pnpm-workspace.yaml above:
193
+ minimum-release-age=20160
160
194
  ```
161
195
 
162
196
  **References**:
@@ -614,7 +648,7 @@ It checks:
614
648
  "prepack": "pnpm build"
615
649
  },
616
650
  "devDependencies": {
617
- "publint": "^0.3.0"
651
+ "publint": "^0.3.21"
618
652
  }
619
653
  }
620
654
  ```
@@ -755,7 +789,7 @@ jobs:
755
789
  with:
756
790
  fetch-depth: 0
757
791
 
758
- - uses: pnpm/action-setup@v4
792
+ - uses: pnpm/action-setup@v6
759
793
 
760
794
  - uses: actions/setup-node@v6
761
795
  with:
@@ -1084,6 +1118,16 @@ Vite’s transformation pipeline.
1084
1118
 
1085
1119
  - Visual regression testing (added in Vitest 4.0)
1086
1120
 
1121
+ - **Vitest 4.1 (Mar 2026)** added Vite 8 support, test tags for organizing tests,
1122
+ and extended chai-style assertions for mocking. Vitest now reuses the
1123
+ installed Vite instead of bundling a separate dependency.
1124
+
1125
+ - **`coverage.all` was removed in v4** — use `coverage.include` and
1126
+ `coverage.exclude` to control which files are reported.
1127
+
1128
+ - **Vitest 5.0.0-beta** is in pre-release (requires Node 22+ and Vite 6.4+).
1129
+ Stay on 4.1.x for production until stable.
1130
+
1087
1131
  **Installation**:
1088
1132
 
1089
1133
  ```bash
@@ -1185,7 +1229,7 @@ jobs:
1185
1229
  steps:
1186
1230
  - uses: actions/checkout@v6
1187
1231
 
1188
- - uses: pnpm/action-setup@v4
1232
+ - uses: pnpm/action-setup@v6
1189
1233
 
1190
1234
  - uses: actions/setup-node@v6
1191
1235
  with:
@@ -1213,7 +1257,7 @@ jobs:
1213
1257
  os: [ubuntu-latest, macos-latest, windows-latest]
1214
1258
  steps:
1215
1259
  - uses: actions/checkout@v6
1216
- - uses: pnpm/action-setup@v4
1260
+ - uses: pnpm/action-setup@v6
1217
1261
  - uses: actions/setup-node@v6
1218
1262
  with:
1219
1263
  node-version: 24
@@ -1227,7 +1271,7 @@ jobs:
1227
1271
  runs-on: ubuntu-latest
1228
1272
  steps:
1229
1273
  - uses: actions/checkout@v6
1230
- - uses: pnpm/action-setup@v4
1274
+ - uses: pnpm/action-setup@v6
1231
1275
  - uses: actions/setup-node@v6
1232
1276
  with:
1233
1277
  node-version: 24
@@ -1248,7 +1292,7 @@ jobs:
1248
1292
  - `actions/checkout@v6` requires Actions Runner v2.329.0+ (stores credentials under
1249
1293
  $RUNNER_TEMP)
1250
1294
 
1251
- - `pnpm/action-setup@v4` includes built-in caching (no `version:` needed if
1295
+ - `pnpm/action-setup@v6` includes built-in caching (no `version:` needed if
1252
1296
  `packageManager` is set in `package.json`)
1253
1297
 
1254
1298
  - `actions/setup-node@v6` with `cache: pnpm` provides additional caching
@@ -1294,7 +1338,7 @@ jobs:
1294
1338
  with:
1295
1339
  fetch-depth: 0
1296
1340
 
1297
- - uses: pnpm/action-setup@v4
1341
+ - uses: pnpm/action-setup@v6
1298
1342
  with:
1299
1343
  version: 10
1300
1344
 
@@ -1752,12 +1796,22 @@ pnpm add -Dw npm-check-updates
1752
1796
 
1753
1797
  | Flag | Description |
1754
1798
  | --- | --- |
1799
+ | `--cooldown <days>` | **Refuse versions younger than N days. Use `--cooldown 14` for the 14-day package-age rule (see [Supply-Chain Mitigation](#supply-chain-mitigation)).** |
1755
1800
  | `--target minor` | Only upgrade to latest minor/patch (safe) |
1756
1801
  | `--target patch` | Only upgrade to latest patch (safest) |
1757
1802
  | `--target latest` | Upgrade to latest version (includes major) |
1758
1803
  | `--format group` | Group output by update type (major/minor/patch) |
1759
1804
  | `--interactive` | Select which packages to upgrade |
1760
1805
  | `-u` | Update package.json (otherwise just reports) |
1806
+ | `--errorLevel 2` | Exit non-zero when upgrades are available (useful in CI gates) |
1807
+
1808
+ **v22 breaking changes** (versus the v19 previously documented):
1809
+
1810
+ - Pure ESM. Named imports only: `import { run } from 'npm-check-updates'`. Default
1811
+ exports no longer work.
1812
+ - Config files: `.ncurc.js` with `module.exports` no longer works in
1813
+ `"type": "module"` projects. Use `.ncurc.cjs`.
1814
+ - Node.js requirement: `^20.19.0 || ^22.12.0 || >=24.0.0`.
1761
1815
 
1762
1816
  **Upgrade Targets Explained**:
1763
1817
 
@@ -1791,15 +1845,15 @@ potentially breaking changes (major), enabling a safer, more frequent upgrade ca
1791
1845
  Add structured upgrade scripts to your root `package.json` that encode your upgrade
1792
1846
  workflow. This makes upgrades consistent and discoverable.
1793
1847
 
1794
- **Root `package.json` scripts**:
1848
+ **Root `package.json` scripts** (with `--cooldown 14` baked in per the 14-day rule):
1795
1849
 
1796
1850
  ```json
1797
1851
  {
1798
1852
  "scripts": {
1799
- "upgrade:check": "ncu --format group",
1800
- "upgrade": "ncu --target minor -u && pnpm install && pnpm test",
1801
- "upgrade:patch": "ncu --target patch -u && pnpm install && pnpm test",
1802
- "upgrade:major": "ncu --target latest --interactive --format group"
1853
+ "upgrade:check": "ncu --cooldown 14 --format group",
1854
+ "upgrade": "ncu --cooldown 14 --target minor -u && pnpm install && pnpm test",
1855
+ "upgrade:patch": "ncu --cooldown 14 --target patch -u && pnpm install && pnpm test",
1856
+ "upgrade:major": "ncu --cooldown 14 --target latest --interactive --format group"
1803
1857
  }
1804
1858
  }
1805
1859
  ```
@@ -1959,7 +2013,7 @@ For running TypeScript CLI commands directly, **tsx** is the recommended choice:
1959
2013
  "markform:bin": "node packages/markform/dist/bin.mjs"
1960
2014
  },
1961
2015
  "devDependencies": {
1962
- "tsx": "^4.21.0"
2016
+ "tsx": "^4.22.3"
1963
2017
  }
1964
2018
  }
1965
2019
  ```
@@ -2480,6 +2534,242 @@ than discovering them when users try to use the library in browser/edge contexts
2480
2534
 
2481
2535
  * * *
2482
2536
 
2537
+ ## Supply-Chain Mitigation
2538
+
2539
+ > **Sync note**: this section is normative and is duplicated **verbatim** in both
2540
+ > `bun-monorepo-patterns.md` and `pnpm-monorepo-patterns.md`. When you change one,
2541
+ > change the other in the same commit. Tooling-specific commands (e.g.
2542
+ > `bun audit` vs. `pnpm audit`) are called out inline.
2543
+
2544
+ Modern TypeScript supply-chain attacks land through fresh package versions — a
2545
+ maintainer's npm token is compromised, malware is published as a patch release, and
2546
+ the registry yanks it 1–10 days later once detection catches up. The
2547
+ **Shai-Hulud 2.0** campaign of May 2026 compromised 170+ npm packages this way, and
2548
+ similar campaigns ran through 2025 (`debug`, `chalk`, `ansi-styles` lookalikes;
2549
+ `expr-eval` post-install RCE). pnpm shipped `minimumReleaseAge` as a default in
2550
+ v11.0 (1 day). Bun ships a built-in trusted-dependency allowlist and `bun audit`.
2551
+ The settings below are stricter than the ecosystem defaults and intentionally so.
2552
+
2553
+ ### The 14-day package-age rule
2554
+
2555
+ **Never install or upgrade to a package version less than 14 days old.**
2556
+
2557
+ Why 14 days specifically:
2558
+
2559
+ - **Detection window**: most malicious publishes are reported and yanked within
2560
+ 3–7 days; 14 days gives a generous buffer past that median.
2561
+ - **Patch-level releases ship dangerous code more often**: many compromises arrive
2562
+ as `1.2.3 → 1.2.4` patch bumps. A trailing window neutralizes the entire class
2563
+ of "fresh patch is malicious" attacks regardless of which dependency moved.
2564
+ - **Cheap to enforce, cheap to wait**: there is essentially no business cost to
2565
+ waiting 14 days on a routine upgrade, and the upside is large.
2566
+
2567
+ Scope:
2568
+
2569
+ - Applies to **`dependencies`, `devDependencies`, `peerDependencies`, and
2570
+ `optionalDependencies`** alike. devDependencies (bundlers, linters, test
2571
+ runners) have historically been *more* dangerous than runtime deps because
2572
+ they execute with full developer privileges on every install.
2573
+ - Applies to **transitive dependencies** to the extent the package manager
2574
+ enforces it (see below). Direct deps are the primary control point.
2575
+ - Applies to **upgrades and new installs**. Existing pins resolved before this
2576
+ policy was adopted are grandfathered until their next planned upgrade.
2577
+
2578
+ Exception process: if a security patch within the 14-day window is needed —
2579
+ e.g., a CVE patch published yesterday that fixes a vulnerability you are
2580
+ exposed to — the exception **must** be documented in the upgrade commit message
2581
+ or PR description, including:
2582
+
2583
+ - The CVE ID (or vulnerability description if no CVE yet).
2584
+ - A link to the upstream release notes.
2585
+ - A reviewer sign-off line (`Reviewed-by: …`).
2586
+
2587
+ No exception is "trivial" — even a `prettier` patch is in scope. The whole
2588
+ point of the rule is that we don't trust ourselves to spot-judge which fresh
2589
+ versions are safe.
2590
+
2591
+ ### Enforcement: pnpm
2592
+
2593
+ pnpm 11 ships **`minimumReleaseAge`** as a config setting (default 1440 minutes
2594
+ = 1 day). Override it to 14 days in `.npmrc` or `pnpm-workspace.yaml`:
2595
+
2596
+ ```ini
2597
+ # .npmrc
2598
+ # 14 days in minutes (14 * 24 * 60).
2599
+ minimum-release-age=20160
2600
+ ```
2601
+
2602
+ ```yaml
2603
+ # pnpm-workspace.yaml
2604
+ minimumReleaseAge: 20160
2605
+ ```
2606
+
2607
+ pnpm will refuse to resolve to any version published less than that long ago.
2608
+ Use a per-package allowlist exception (`minimumReleaseAgeExclude`) only with
2609
+ the documented exception process above.
2610
+
2611
+ pnpm 11 also defaults **`blockExoticSubdeps`** to `true` (blocks transitive
2612
+ dependencies installed from non-registry sources like `github:`, `git+ssh://`,
2613
+ or arbitrary URLs). Keep this default on.
2614
+
2615
+ Lifecycle script hygiene in pnpm 11 uses the new **`allowBuilds`** allowlist
2616
+ (replacing `onlyBuiltDependencies` / `neverBuiltDependencies`). Declare the
2617
+ exact packages allowed to run install scripts:
2618
+
2619
+ ```yaml
2620
+ # pnpm-workspace.yaml
2621
+ allowBuilds:
2622
+ - esbuild
2623
+ - sharp
2624
+ ```
2625
+
2626
+ Everything not in this list runs without `postinstall`/`preinstall`/`install`
2627
+ scripts. This is the single most important mitigation pnpm gives you out of
2628
+ the box.
2629
+
2630
+ `pnpm audit` and `pnpm audit signatures` (added in 11.1) provide vulnerability
2631
+ and provenance checks:
2632
+
2633
+ ```bash
2634
+ pnpm audit --audit-level=moderate
2635
+ pnpm audit signatures
2636
+ ```
2637
+
2638
+ ### Enforcement: cross-tool tooling
2639
+
2640
+ **`npm-check-updates --cooldown`** is the upgrade-time check, complementing
2641
+ pnpm's resolution-time `minimumReleaseAge`. Use it in scripts even on
2642
+ pnpm-managed projects:
2643
+
2644
+ ```bash
2645
+ # Refuse any candidate version younger than 14 days
2646
+ pnpm dlx npm-check-updates --cooldown 14
2647
+
2648
+ # Interactive upgrade with the 14-day filter applied
2649
+ pnpm dlx npm-check-updates --cooldown 14 --target minor --interactive
2650
+
2651
+ # CI-style check: exits non-zero if upgrades are available
2652
+ pnpm dlx npm-check-updates --cooldown 14 --errorLevel 2
2653
+ ```
2654
+
2655
+ **Direct registry query** (when in doubt about a specific version):
2656
+
2657
+ ```bash
2658
+ # Show all publish times for a package
2659
+ npm view typescript time
2660
+
2661
+ # Show the time of a specific version
2662
+ npm view typescript time.6.0.3
2663
+ ```
2664
+
2665
+ If `npm view <pkg> time.<version>` reports less than 14 days ago, **wait**.
2666
+
2667
+ ### Enforcement: lockfile discipline
2668
+
2669
+ - **Always commit `pnpm-lock.yaml`**.
2670
+ - **Use `pnpm install --frozen-lockfile` in CI** so any drift between
2671
+ `package.json` and `pnpm-lock.yaml` fails the build instead of silently
2672
+ resolving to fresh versions.
2673
+ - **Never run `pnpm update` (or `pnpm install` on a fresh `node_modules` with
2674
+ an unpinned `package.json`) without lockfile review.** Treat lockfile diffs
2675
+ the same as code diffs.
2676
+ - In a monorepo, **a single root `pnpm-lock.yaml`** is the source of truth;
2677
+ per-package lockfiles are an anti-pattern.
2678
+
2679
+ ### Enforcement: provenance and signatures
2680
+
2681
+ - Prefer dependencies that publish with [npm provenance attestations](https://docs.npmjs.com/generating-provenance-statements).
2682
+ Major projects (TypeScript, Vitest, Prettier, ESLint, etc.) ship these.
2683
+ - Run `pnpm audit signatures` in CI on a periodic basis to catch tampered
2684
+ packages.
2685
+
2686
+ ### Sample CI gate
2687
+
2688
+ Add this job to the CI workflow alongside lint/test:
2689
+
2690
+ ```yaml
2691
+ audit:
2692
+ runs-on: ubuntu-latest
2693
+ steps:
2694
+ - uses: actions/checkout@v6
2695
+ - uses: pnpm/action-setup@v6
2696
+ with:
2697
+ run_install: false
2698
+ - uses: actions/setup-node@v6
2699
+ with:
2700
+ node-version: 24
2701
+ cache: pnpm
2702
+ - run: pnpm install --frozen-lockfile
2703
+ - name: Vulnerability audit
2704
+ run: pnpm audit --audit-level=moderate
2705
+ - name: Signature audit
2706
+ run: pnpm audit signatures
2707
+ - name: Package-age check
2708
+ run: pnpm dlx npm-check-updates --cooldown 14 --errorLevel 0
2709
+ # errorLevel 0 logs but doesn't fail — flip to 2 once you've cleared
2710
+ # the existing backlog.
2711
+ ```
2712
+
2713
+ ### Recommended local script
2714
+
2715
+ Drop a `scripts/check-package-age.mjs` one-liner in the repo that a pre-push
2716
+ hook can call:
2717
+
2718
+ ```ts
2719
+ #!/usr/bin/env tsx
2720
+ // Refuses to commit if any direct dependency in package.json was published
2721
+ // less than COOLDOWN_DAYS ago. Intended for the pre-push hook.
2722
+ import pkg from '../package.json' with { type: 'json' };
2723
+
2724
+ const COOLDOWN_MS = 14 * 24 * 60 * 60 * 1000;
2725
+ const now = Date.now();
2726
+ const all = { ...pkg.dependencies, ...pkg.devDependencies };
2727
+
2728
+ let violations = 0;
2729
+ for (const [name, spec] of Object.entries(all)) {
2730
+ const version = String(spec).replace(/^[\^~=<>]+/, '');
2731
+ const meta = await (await fetch(`https://registry.npmjs.org/${name}`)).json();
2732
+ const publishedAt = meta.time?.[version];
2733
+ if (!publishedAt) continue;
2734
+ const ageMs = now - new Date(publishedAt).getTime();
2735
+ if (ageMs < COOLDOWN_MS) {
2736
+ const days = (ageMs / 86_400_000).toFixed(1);
2737
+ console.error(`✗ ${name}@${version} is only ${days} days old (< 14)`);
2738
+ violations++;
2739
+ }
2740
+ }
2741
+ process.exit(violations > 0 ? 1 : 0);
2742
+ ```
2743
+
2744
+ Wire it into lefthook:
2745
+
2746
+ ```yaml
2747
+ pre-push:
2748
+ commands:
2749
+ package-age:
2750
+ glob: "package.json"
2751
+ run: pnpm tsx scripts/check-package-age.ts
2752
+ ```
2753
+
2754
+ ### Exception bookkeeping
2755
+
2756
+ When you take an exception, leave a one-line marker in `package.json` adjacent
2757
+ to the pin:
2758
+
2759
+ ```jsonc
2760
+ {
2761
+ "devDependencies": {
2762
+ // Exception: CVE-2026-XXXX patch within 14d window. Reviewed 2026-05-21.
2763
+ "vitest": "4.1.7"
2764
+ }
2765
+ }
2766
+ ```
2767
+
2768
+ (JSON strict-parsers will reject `//` comments — use a JSONC-aware tool or
2769
+ move the note to a `README.md` / `CHANGELOG.md` entry instead.)
2770
+
2771
+ * * *
2772
+
2483
2773
  ## Comparative Analysis
2484
2774
 
2485
2775
  ### Build Tools Comparison
@@ -2514,7 +2804,14 @@ than discovering them when users try to use the library in browser/edge contexts
2514
2804
 
2515
2805
  ## Best Practices
2516
2806
 
2517
- 1. **Scope your package names**: Use `@org/package-name` format for easier GitHub
2807
+ 1. **Follow the 14-day package-age rule** for every dependency install and upgrade.
2808
+ See [Supply-Chain Mitigation](#supply-chain-mitigation). Set
2809
+ `minimumReleaseAge: 20160` (14 days in minutes) in pnpm config; use
2810
+ `ncu --cooldown 14`; declare lifecycle-script-eligible packages via
2811
+ `allowBuilds`; run `pnpm audit` and `pnpm audit signatures` in CI; commit
2812
+ `pnpm-lock.yaml` and use `pnpm install --frozen-lockfile` in CI.
2813
+
2814
+ 2. **Scope your package names**: Use `@org/package-name` format for easier GitHub
2518
2815
  Packages integration and namespace clarity.
2519
2816
 
2520
2817
  2. **Structure for splitting**: Organize internal code (`core/`, `cli/`, `adapters/`) to
@@ -2600,21 +2897,52 @@ than discovering them when users try to use the library in browser/edge contexts
2600
2897
  Rolldown Vite’s Library Mode.
2601
2898
  Monitor for announcements that may affect best practices.
2602
2899
 
2603
- 2. **TypeScript 6.0/7.0 Transition**: TypeScript 7.0 (Go rewrite) is now available in
2604
- Visual Studio 2026 Insiders preview with ~8x faster project load times.
2605
- Install via `npm install -D @typescript/native-preview`. TypeScript 6.0 will serve as
2606
- a “bridge” release deprecating features for 7.0 alignment.
2607
- Monitor for migration guidance as 7.0 approaches stable.
2608
-
2609
- 3. **Native TypeScript Execution**: TypeScript 5.8+ supports `--erasableSyntaxOnly`
2610
- flag, enabling direct execution in Node.js 23.6+ without transpilation.
2611
- This may reduce the need for tsx in some workflows.
2612
- Monitor for broader adoption and tooling support.
2613
-
2614
- 4. **ESLint v10 multi-config**: ESLint v10.0.0 is in RC phase (Jan 2026) with improved
2615
- multi-config support, Node.js ^20.19.0 || ^22.13.0 || >=24 required, and complete
2616
- removal of eslintrc config system.
2617
- Monitor for final release.
2900
+ 2. ~~**TypeScript 6.0**~~: **SHIPPED** 2026-03-23 (currently 6.0.3). The last
2901
+ JavaScript-based release. `strict: true` is now the default, ESM is the
2902
+ default module system, and ~9 compiler settings flipped defaults. Adopt for
2903
+ the codebase; review `tsconfig.base.json` for now-redundant flag declarations.
2904
+
2905
+ 3. **TypeScript 7.0 (Project Corsa, Go rewrite)**: Beta shipped 2026-04-21 as
2906
+ `@typescript/native-preview` (binary `tsgo`). Claims ~10× type-check speed
2907
+ and ~3× less memory; passes 95%+ of the test suite. Available in
2908
+ Visual Studio 2026 18.6 Insiders by default. **Do not adopt for production
2909
+ builds yet** — wait for stable (expected mid-to-late 2026). Monitor for
2910
+ tsdown/Vitest compatibility announcements.
2911
+
2912
+ 4. **Native TypeScript Execution**: TypeScript 5.8+ supports `--erasableSyntaxOnly`,
2913
+ enabling direct execution in Node.js 23.6+ without transpilation. With
2914
+ TypeScript 6.0 stable and Node 24 LTS, this is increasingly viable for
2915
+ scripts. Monitor for broader tooling adoption (linters, coverage tools).
2916
+
2917
+ 5. ~~**ESLint v10**~~: **SHIPPED** 2026-02-06. `.eslintrc.*` configuration is
2918
+ completely removed — flat config (`eslint.config.js`) is the only supported
2919
+ format. Download size reduced 11 MB → 9.4 MB. Minimum Node.js v20.19.0.
2920
+ ESLint 9.x EOL is 2026-08-06 — migrate now.
2921
+
2922
+ 6. ~~**pnpm 11**~~: **SHIPPED** 2026-04-28 (currently 11.2.2). Breaking changes:
2923
+ pure ESM (requires Node 22+), SQLite-based store, `minimumReleaseAge` default
2924
+ (1 day), `blockExoticSubdeps` default `true`, `allowBuilds` replacing
2925
+ `onlyBuiltDependencies`. Experimental Rust-based `@pnpm/pacquet` engine in
2926
+ 11.2+. Migrate via the pnpm 11 migration guide; bump
2927
+ `pnpm/action-setup` to v6 in CI.
2928
+
2929
+ 7. ~~**Zod 4**~~: **SHIPPED** (currently 4.4.3). 14× faster string parsing,
2930
+ 7× faster array parsing, 6.5× faster object parsing vs Zod 3; core bundle
2931
+ 2.3× smaller; new `@zod/mini` (~1.9 KB gzipped) for tree-shakable frontend
2932
+ validation. Migration from Zod 3 required — see
2933
+ [zod.dev/v4/changelog](https://zod.dev/v4/changelog).
2934
+
2935
+ 8. **Commander 15 (ESM-only)**: Commander 15 ships May 2026, requires
2936
+ Node v22.12.0+, drops CJS. Commander 14 moves to maintenance (security
2937
+ only) until May 2027. Upgrade path for CLI tools using Commander.
2938
+
2939
+ 9. **`dotenv` vs Node `--env-file`**: Node 20.6+ has built-in `--env-file`
2940
+ support, and with Node 24 LTS it is production-ready. Most new projects
2941
+ should default to `--env-file` and reserve `dotenv` for advanced needs
2942
+ (variable expansion, multiline values, custom precedence logic).
2943
+
2944
+ 10. **Vitest 5**: 5.0.0-beta.3 in pre-release; requires Node 22+ and
2945
+ Vite 6.4+. Stable not yet shipped. Stay on 4.1.x for now.
2618
2946
 
2619
2947
  * * *
2620
2948
 
@@ -2791,9 +3119,9 @@ ready for public release.
2791
3119
  },
2792
3120
  "devDependencies": {
2793
3121
  "@types/node": "^24.0.0",
2794
- "publint": "^0.3.0",
2795
- "tsdown": "^0.18.0",
2796
- "typescript": "^5.9.0"
3122
+ "publint": "^0.3.20",
3123
+ "tsdown": "^0.22.0",
3124
+ "typescript": "^6.0.3"
2797
3125
  }
2798
3126
  }
2799
3127
  ```
@@ -2804,9 +3132,9 @@ ready for public release.
2804
3132
  {
2805
3133
  "name": "project-workspace",
2806
3134
  "private": true,
2807
- "packageManager": "pnpm@10.28.2",
3135
+ "packageManager": "pnpm@11.0.0",
2808
3136
  "engines": {
2809
- "node": ">=24"
3137
+ "node": ">=22"
2810
3138
  },
2811
3139
  "scripts": {
2812
3140
  "build": "pnpm -r build",
@@ -2821,25 +3149,32 @@ ready for public release.
2821
3149
  "changeset": "changeset",
2822
3150
  "version-packages": "changeset version",
2823
3151
  "release": "pnpm build && pnpm publint && changeset publish",
2824
- "upgrade:check": "ncu --format group",
2825
- "upgrade": "ncu --target minor -u && pnpm install && pnpm test",
2826
- "upgrade:major": "ncu --target latest --interactive --format group"
3152
+ "upgrade:check": "ncu --cooldown 14 --format group",
3153
+ "upgrade": "ncu --cooldown 14 --target minor -u && pnpm install && pnpm test",
3154
+ "upgrade:major": "ncu --cooldown 14 --target latest --interactive --format group",
3155
+ "audit": "pnpm audit --audit-level=moderate && pnpm audit signatures"
2827
3156
  },
2828
3157
  "devDependencies": {
2829
- "@changesets/cli": "^2.29.0",
3158
+ "@changesets/cli": "^2.31.0",
2830
3159
  "@changesets/changelog-github": "^0.5.0",
2831
- "@eslint/js": "^9.0.0",
2832
- "eslint": "^9.0.0",
3160
+ "@eslint/js": "^10.0.0",
3161
+ "eslint": "^10.0.0",
2833
3162
  "eslint-config-prettier": "^10.0.0",
2834
- "lefthook": "^2.0.15",
2835
- "npm-check-updates": "^19.0.0",
2836
- "prettier": "^3.0.0",
2837
- "typescript": "^5.9.0",
3163
+ "lefthook": "^2.1.5",
3164
+ "npm-check-updates": "^22.0.0",
3165
+ "prettier": "^3.8.3",
3166
+ "typescript": "^6.0.3",
2838
3167
  "typescript-eslint": "^8.0.0"
2839
3168
  }
2840
3169
  }
2841
3170
  ```
2842
3171
 
3172
+ All pinned versions above are ≥14 days old as of 2026-05-21 per the
3173
+ [Supply-Chain Mitigation](#supply-chain-mitigation) policy. Newer releases exist
3174
+ (`pnpm` 11.2.2, `lefthook` 2.1.8, `npm-check-updates` 22.2.0,
3175
+ `publint` 0.3.21, `vitest` 4.1.7) but were too fresh at this document update.
3176
+ Bump on the next refresh once the 14-day window has elapsed.
3177
+
2843
3178
  ### Appendix C: ESLint Flat Config Example
2844
3179
 
2845
3180
  #### Minimal Configuration