Skip to content

feat!: declare real engines.node across all packages - #603

Merged
aversini merged 1 commit into
mainfrom
feat/engines-node-22
Jul 21, 2026
Merged

aversini merged 1 commit into
mainfrom
feat/engines-node-22

Conversation

@aversini

Copy link
Copy Markdown
Collaborator

Follow-up to #601. Replaces the inert top-level "node" field with a real engines.node of >=22 across all 14 packages.

The bug

Every package declared a top-level "node" field — ">=16", ">=18" or ">=20" depending on the package. npm and pnpm read engines.node, not that field. None of these floors were ever enforced.

Proven by packing @node-cli/logger and installing it with engine-strict=true on Node 24:

Manifest Result
old "node": ">=99" installs silently — the field is a no-op
new "engines": { "node": ">=99" } EBADENGINE, hard fail
new "engines": { "node": ">=22" } installs cleanly

The declared values were also simply wrong. @node-cli/logger claimed >=16 while depending on ora@9, which requires Node 20.

What the dependency graph actually requires

Real floor Packages Driven by
≥22 run, npmrc, search execa@10, directly or via run
≥20 logger, parser, perf, comments, timer, bundlesize, static-server ora, meow, glob, open
≥20.17 bundlecheck, secret @inquirer/*, better-sqlite3
unconstrained utilities, envtools lodash-es / no runtime deps

Why a uniform >=22 rather than per-package floors

  • Node 20 reached EOL on 2026-04-30. A >=20 floor declares support for an unsupported runtime.
  • CI only tests 24.x. Declaring a floor below what is tested is a claim not backed by evidence.
  • @node-cli/run already requires 22 via execa 10, and search and npmrc inherit it.
  • Per-package floors drift. They are accurate only until the next dependency bump, and nothing verifies them. One rule cannot silently go stale.

The root manifest declares the same floor so contributor installs are gated too.

Verification

  • pnpm install, pnpm build (14 projects), pnpm lint (13 projects), pnpm test (12 projects) — all pass
  • Enforcement verified end to end against a real packed tarball, per the table above

Release impact

feat! with a BREAKING CHANGE footer — release-please will cut a major for each touched package. This is a genuine narrowing of the supported runtime contract: installs that were previously silent will now warn, or fail under engine-strict.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WxPdHpfW5B4evcecAmHHqa

Every package carried a non-standard top-level "node" field (">=16",
">=18" or ">=20"). npm and pnpm read engines.node, not that field, so
none of these floors were ever enforced — a consumer on Node 18 could
install @node-cli/logger with engine-strict enabled and get no warning,
despite ora@9 requiring Node 20 at runtime.

All 14 packages now declare a real engines.node of ">=22", replacing the
inert field in place. The floor is uniform rather than per-package: Node
20 reached EOL on 2026-04-30, CI only ever tests 24.x, and @node-cli/run
already required 22 via execa 10 — so a single rule is both honest and
immune to drift as dependencies raise their own floors.

The root manifest declares the same floor so contributor installs are
gated too.

Verified by packing @node-cli/logger and installing it with
engine-strict=true: the old "node" field at ">=99" installs silently,
while engines.node at ">=99" correctly fails with EBADENGINE, and the
real ">=22" installs cleanly on Node 24. Full build, lint and test
suites pass.

BREAKING CHANGE: all @node-cli/* packages now require Node.js 22 or later.
@aversini
aversini merged commit 7d2859e into main Jul 21, 2026
4 checks passed
@aversini
aversini deleted the feat/engines-node-22 branch July 21, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant