Repository navigation
chore(toolchain): pnpm 11 + devEngines (blocked on upstream pnpm bug) - #144
lollipop-onl wants to merge 11 commits into
Conversation
Every open alert pointed at a transitive dependency: all direct dependencies are already on their latest release, so no manifest could be bumped to fix them. The vulnerable copies were pinned by upstream packages (astro/starlight, unbuild, backlog-js) that have not yet released updated ranges, which left `overrides` as the only way to reach the patched versions without waiting on those projects. Overrides live in pnpm-workspace.yaml because pnpm 10 no longer reads the `pnpm` field from package.json; placing them there is silently ignored apart from a warning. vite is unified on 8.x rather than held at ^7.3.5: vitest 4.1.11 and vitefu both require vite@8, so pinning the 7.x line would trade the alerts for unmet peer ranges. The rollup-plugin-dts>typescript pin is required, not incidental. Raising the other ranges shifted that subtree onto typescript 7, which dropped the JS compiler API it relies on, and `pnpm build` died in rollup-plugin-dts with `Cannot read properties of undefined (reading 'useCaseSensitiveFileNames')`. The baseline only avoided this by happening to resolve 6.x. esbuild 0.25.12 (via unbuild) stays as-is; it sits outside the vulnerable >=0.27.3 <0.28.1 range. Verified: 767 tests, typecheck, oxfmt, CLI + 105 docs pages built, CLI smoke-tested, and `pnpm audit` reports no known vulnerabilities. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pnpm 10 is a major behind, and staying on it defers the resolver change rather than avoiding it. Doing the bump on its own keeps the wholesale lockfile regeneration separable from the dependency decisions that follow, so a resolver regression can be bisected to this commit alone. Two pnpm 11 behaviour changes had to be absorbed: `onlyBuiltDependencies` is renamed to `allowBuilds` and takes a map rather than a list. pnpm 11 does not read the old key, so leaving it would have silently stopped esbuild, lefthook, sharp and turbo from running their install scripts; pnpm rewrote the file with `set this to true or false` placeholders on first install, which is how it surfaced. pnpm 11 also enforces the `packageManager` field instead of merely reading it: a pnpm whose version disagrees now refuses to run rather than warning. Nested invocations therefore have to come from Corepack, which is already what `mise.toml`'s `postinstall = "npx corepack enable"` hook arranges. The lockfile is regenerated wholesale, as expected across a resolver major. The one peer warning pnpm 11 reports -- unbuild@3.6.1 wanting typescript ^5.9.2 -- predates this change; the pnpm 10 lockfile resolved the same `unbuild@3.6.1(typescript@6.0.3)` pairing, and pnpm 11 only reports it more prominently. Verified with install, test (767 tests / 116 files), typecheck, build (CLI + 105 docs pages, internal links valid), format:check and audit (no known vulnerabilities). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `rollup-plugin-dts>typescript: ^6.0.3` override was the load-bearing stopgap from the previous change: typescript 7 (tsgo) ships no JS compiler API, so `rollup-plugin-dts` crashed with `ts.sys` undefined and `pnpm build` failed while tests and typecheck stayed green. Upstream has since solved this properly. rollup-plugin-dts 6.5.0 added typescript 7 to its peer range and gained a runtime fallback: it probes the installed `typescript` for `createProgram`, and when that is missing it loads the `@typescript/typescript6` compatibility package, which provides the TypeScript 6.0 API. The optional peer is pulled in automatically because `autoInstallPeers` is on by default, so no manifest entry is needed. No override is required to reach it either. unbuild 3.6.1 asks for `rollup-plugin-dts: ^6.2.1`, and 6.5.1 satisfies that range on its own, so the fix arrives through normal resolution rather than another pin. Both workarounds are therefore deleted rather than retargeted, and the catalog comment explaining the typescript hold goes with them. Confirmed by building with `declaration: true` forced on, which is the code path that actually loads rollup-plugin-dts -- the CLI leaves `declaration` unset and emits no .d.ts files, so a passing default build alone would not have exercised it. Verified with test (767 tests / 116 files), typecheck, build --force (CLI + 105 docs pages, internal links valid) and audit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e caught up Every one of these overrides existed only because some upstream package still pinned a vulnerable range, and each was documented as a stopgap to revisit once those ranges widened. They have widened. The dependency tree now asks for open ranges that reach the patched releases by ordinary caret resolution -- postcss `^8.5.13`, esbuild `^0.27.0 || ^0.28.0`, picomatch `^3 || ^4`, vite `^6 || ^7 || ^8` -- so forcing the same versions by hand no longer changes the outcome. Removing all eleven entries and re-resolving from scratch produces byte-identical resolutions for every overridden package: postcss 8.5.26, nanoid 3.3.18, js-yaml 4.3.1, svgo 4.0.2, vite 8.2.2, qs 6.15.3, defu 6.1.7, picomatch 2.3.2 + 4.0.5, esbuild 0.25.12 + 0.28.2, and smol-toml 1.8.0 -- the last one already ahead of the `^1.6.1` the override pinned. Keeping them would be worse than redundant. A stale override silently caps a dependency at whatever was current when it was written, so it eventually holds the tree *back* from a later patch instead of pulling it forward, and it hides that fact behind a green audit. Each of the 24 advisories was re-checked individually against the resolved versions in the regenerated lockfile rather than trusting `pnpm audit` alone; none of the installed versions falls inside its vulnerable range. esbuild 0.25.12 remains in the tree via unbuild and sits outside the affected `>=0.27.3 <0.28.1` window, as before. Verified with test (767 tests / 116 files), typecheck, build --force (CLI + 105 docs pages, internal links valid), format:check, audit (no known vulnerabilities) and a CLI smoke test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Node version was asserted in four places that could drift apart, and nothing checked that a contributor's Node actually satisfied any of them -- mise.toml silently provisioned 24, CI tested 20/22/24, release.yml pinned 24, and apps/cli declared >=20.18 for consumers. `devEngines.runtime` in the root package.json now states the range the development toolchain requires, and pnpm enforces it on every install: [ERROR] This project requires Node.js >=26. Your current Node.js is v24.18.0 Two of the four sites collapse into that. release.yml's `node-version: 24` is gone: its `actions/setup-node` step exists only for `registry-url`, which writes the .npmrc that `pnpm publish` needs, and mise-action runs immediately after and puts the mise.toml Node on PATH -- so the pin was shadowed and purely decorative. mise.toml keeps `node = "24"` because mise cannot read devEngines, but it now documents devEngines as the source of truth. Two are deliberately left alone. `engines.node` in apps/cli is a *published* constraint: it tells consumers of the @nulab/bee package which Node they can install it on. devEngines constrains the machine building this repo. They currently hold the same `>=20.18` value, but collapsing them would mean either publishing a development requirement to users or letting a toolchain change silently retarget the released package. They are kept as separate contracts. The CI matrix stays [20, 22, 24], but the jobs had to be restructured, because pnpm 11 declares `engines.node: ">=22.13"` and dies outright on Node 20: Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: node:sqlite Dependency installation therefore runs on the mise.toml Node, and only the test run is moved onto the matrix version. This required selecting that version explicitly with `mise exec node@<version>`: mise.toml pins node = 24, so a bare `node` resolves to 24 and all three legs would have tested the same runtime while appearing to pass. Vitest is also invoked as `node node_modules/vitest/vitest.mjs` rather than `pnpm exec vitest`, since `pnpm exec` would put pnpm back onto the Node under test. Node 20 coverage is consequently runtime-only -- installing under Node 20 is no longer exercised -- which is the honest trade for keeping a Node 20 guarantee while running a pnpm that cannot execute there. Verified locally on v20.20.0, v22.21.1 and v24.18.0: 767 tests pass on each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous comment said only that these two are held back because the CLI supports Node >=20.18. It did not say what would let the next reader raise them, so the pin looked like a preference rather than a blocked decision. Both majors are still genuinely out of reach: commander 15 requires Node >=22.12 and undici 8 requires >=22.19, while @nulab/bee publishes `engines.node: ">=20.18"`. Raising either would break `npm install` for Node 20 users, which makes this a release decision rather than a dependency bump. The comment now names the exact condition that unblocks it -- dropping Node 20 from the published engines and the CI matrix -- and records that Node 20 reached EOL on 2026-04-30, so the reader has the argument for doing so rather than having to rediscover it. undici's entry gets a back-reference, since it sits far enough from commander's in the catalog to be edited on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PR was opened against the #143 branch, and ci.yml only triggers on pull requests targeting main, so the restructured Node matrix -- the part of this change most worth verifying -- had never run in CI. Retargeting the base does not re-fire workflows on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Parking this. The pnpm 11 bump is blocked by an upstream bug, and the parts worth keeping do not depend on it. The blocker: pnpm 11 (verified on both 11.23.0 and 11.24.0) does not install optional native bindings whose
Measured on the runner: pnpm 10 installs 574 packages, pnpm 11 installs 564. Packages with simple ranges ( This is unrelated to Node 20 — the CLI and all 767 tests run fine on Node 20. It is pnpm 11's installer. Superseded by #149, which does the override removal on pnpm 10. That was the part that actually mattered, and it needs nothing from this branch. Not carried over, all gated behind pnpm 11:
Leaving this open as the record of the investigation. Worth retrying once pnpm fixes the |
Follow-up to #143, which resolved all 24 Dependabot alerts in the least invasive way available and explicitly deferred the toolchain work. This does that work and deletes the stopgaps #143 introduced.
Based on
claude/dependabot-alerts-resolution-b4bc9a, so this targets #143 rather thanmain. Retarget tomainonce #143 merges.Result
Every override from #143 is gone — all twelve — and the 24 alerts stay resolved.
overridesentries1. pnpm 11
Two behaviour changes had to be absorbed, both of which fail quietly:
onlyBuiltDependencies(list) is renamedallowBuilds(map). pnpm 11 does not read the old key, so esbuild/lefthook/sharp/turbo would have silently stopped running install scripts.packageManageris now enforced, not just read — a mismatched pnpm refuses to run. Nested invocations must come from Corepack, whichmise.toml'spostinstallhook already arranges.2. typescript 7 — the load-bearing pin is gone
rollup-plugin-dts6.5.0 added typescript 7 to its peer range and a runtime fallback: it probes the installedtypescriptforcreateProgramand loads@typescript/typescript6when it is missing. The optional peer arrives automatically viaautoInstallPeers.No override is needed to reach it —
unbuildasks forrollup-plugin-dts: ^6.2.1and 6.5.1 satisfies that on its own. So both the override and the catalog hold were deleted rather than retargeted.Confirmed by forcing
declaration: true, which is the code path that actually loadsrollup-plugin-dts. The CLI leavesdeclarationunset and emits no.d.ts, so a passing default build would not have exercised it — worth knowing, since this is the failure #143 hit.3. All ten security overrides removed
Upstream ranges have widened (
postcss ^8.5.13,esbuild ^0.27.0 || ^0.28.0,picomatch ^3 || ^4,vite ^6 || ^7 || ^8), so ordinary caret resolution now reaches the patched releases. Removing all ten and re-resolving from scratch gives identical versions: postcss 8.5.26, nanoid 3.3.18, js-yaml 4.3.1, svgo 4.0.2, vite 8.2.2, qs 6.15.3, defu 6.1.7, picomatch 2.3.2 + 4.0.5, esbuild 0.25.12 + 0.28.2, smol-toml 1.8.0 — the last already ahead of the^1.6.1the override pinned.Keeping them would be worse than redundant: a stale override caps a dependency at whatever was current when it was written, eventually holding the tree back from a later patch while hiding that behind a green audit.
4. devEngines
devEngines.runtimestates the range the development toolchain needs, and pnpm enforces it per-install:Two of the four drift sites collapse into it.
release.yml'snode-version: 24is deleted — itssetup-nodeexists only forregistry-url, andmise-actionruns right after and shadows the pin anyway.mise.tomlkeepsnode = "24"(mise cannot readdevEngines) but now documentsdevEnginesas the source of truth.Two are deliberately not collapsed.
engines.nodeinapps/cliis a published constraint on consumers of@nulab/bee;devEnginesconstrains the machine building this repo. They hold the same>=20.18today, but merging them would mean either publishing a development requirement to users or letting a toolchain change silently retarget the released package.I also did not add
devEngines.packageManager: it injects 199 lockfile lines vendoring pnpm binaries for 7 platforms, duplicating what Corepack already does. Note that declaring it with a version differing frompackageManagersilently ignorespackageManager— and Corepack rejects ranges (expected a semver version) while pnpm accepts them.pnpm@11declaresengines.node: ">=22.13"and dies on Node 20:The product is unaffected — the built CLI and all 767 tests pass on Node 20. Only pnpm can't execute there. Per maintainer decision, the matrix stays
[20, 22, 24]and the job was restructured instead: dependencies install on themise.tomlNode, and only the test run moves to the matrix version.Two non-obvious details, both commented in the workflow:
mise exec node@<version>.mise.tomlpinsnode = "24", so a barenoderesolves to 24 and all three legs would have tested the same runtime while appearing to pass.node node_modules/vitest/vitest.mjs, notpnpm exec vitest, which would put pnpm back onto the Node under test.Trade-off: Node 20 coverage is now runtime-only — installing under Node 20 is no longer exercised.
Recommendation (not actioned — needs a release decision)
commanderandundicistay one major back.commander@15needs Node >=22.12 andundici@8needs >=22.19, both above the published>=20.18, so raising either breaksnpm installfor Node 20 users.Node 20 reached EOL on 2026-04-30. Dropping it would mean:
engines.node→>=22.12, matrix →[22, 24], both majors taken, and the CI workaround above becomes unnecessary. That is a breaking change for CLI users and needs a version/release-notes decision, so I have left it — the catalog comments now name the exact unblocking condition.Verification
Every check run on a clean
--frozen-lockfileinstall:pnpm install— clean; the only peer warning (unbuild@3.6.1wantingtypescript ^5.9.2) predates this PR: the pnpm 10 lockfile resolved the sameunbuild@3.6.1(typescript@6.0.3)pairingpnpm test— 767 passed, 116 files (unchanged)pnpm typecheck— 5/5pnpm build --force— CLI + docs, 105 pages, all internal links valid, 0 cachedpnpm format:check— 333 files cleanpnpm audit— no known vulnerabilities--version/--helpcorrectpnpm auditalone: 0 vulnerableesbuild@0.25.12remains viaunbuild, outside the vulnerable>=0.27.3 <0.28.1window, as before.🤖 Generated with Claude Code