Skip to content

feat!: Drop Node.js 20 support. Node.js >=22.12 is now required#1125

Draft
macdonst wants to merge 13 commits into
mainfrom
deprecate-node-20
Draft

feat!: Drop Node.js 20 support. Node.js >=22.12 is now required#1125
macdonst wants to merge 13 commits into
mainfrom
deprecate-node-20

Conversation

@macdonst
Copy link
Copy Markdown

@macdonst macdonst commented May 21, 2026

DO NOT MERGE

This will create a new major version of the CLI and may screw up some work in progress with plugins. Check with the participants of this slack thread before proceeding.

Description

Node 20 officially exited it's maintenance mode on April 30th, 2026. This means we can finally drop support for this version of Node in the CLI. The purpose of this PR is to update the code to warn users if they are running on a node version less than 22.12, update the engines version of node in all our package.json files and drop node 20 from our testing matrix.

This PR intentionally does not remove any node packages that may not be required anymore. For example:

rimraf - sticks around in case anyone is contributing from Windows
tinyglobby - node 22 now supports fs.glob but our usage of tinyglobby includes some options that fs.glob doesn't support out of the box so more consideration is required here.
nanoid - we only use one method in from this package, customAlphabet. To me this is a top candidate to get vendored as we can copy (with attribution) approximately 50 LOC from an MIT project instead of leaving us open to a supply chain for a package that gets over 100 million downloads a week. Single maintainer.
execa - used frequently in this repo and we should probably examine if we can replace it with node:child_process and once again not leave us open to a supply chain for a package that gets over 100 million downloads a week. Single maintainer.

This PR replaces: #1033 which was going to be a rebase nightmare.

What to review

Should be pretty self explanatory. Did I miss any node 20 references?

Testing

All existing tests are passing. I didn't think it was necessary to add a test isSupportedNodeVersion.


Note

Medium Risk
Medium risk because it’s a breaking runtime/CI matrix change that will block users and environments still on Node 20, and it adjusts global stubbing behavior for Node 26 web globals.

Overview
Drops Node.js 20 support across the CLI monorepo, bumping engines.node to >=22.12 (root + published packages), updating the CLI runtime version gate/error message, and recording the breaking change via a major changeset.

Updates CI/test tooling to match the new baseline by removing Node 20 from GitHub Actions matrices (adding Node 26), shifting coverage collection to Node 22, bumping @types/node to ^22.x, and targeting node22 for the cli-test esbuild worker bundle.

Hardens browser stub injection for newer Node versions: setupBrowserStubs now overrides/restores existing getter-only globals via property descriptors, and getBrowserStubs() always prefers JSDOM’s localStorage/sessionStorage/Storage for consistent realm identity; adds a regression test for the Node 26 localStorage getter-only case.

Reviewed by Cursor Bugbot for commit 4f01217. Bugbot is set up for automated code reviews on this repo. Configure here.

Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@macdonst macdonst requested a review from a team as a code owner May 21, 2026 15:03
@claude
Copy link
Copy Markdown

claude Bot commented May 21, 2026

Claude finished @macdonst's task in 3m 48s —— View job


Claude PR Review

  • Read changed files
  • Verify changeset
  • Check Node version gate logic
  • Review browser stubs changes for Node 26 compatibility
  • Verify package.json engine fields
  • Check CI workflow updates
  • Post review feedback

Status of earlier Cursor bot comments

  • Lockfile listed tmp/fixture-* importers — resolved. Current pnpm-lock.yaml has no tmp/* importer entries.
  • run.js violates oxfmt — resolved. bin/run.js now uses single quotes and no trailing semicolons.
  • Assignment to getter-only accessor throws on Node 26 — resolved in e4d90ac. setupBrowserStubs.ts now uses Object.defineProperty when a property descriptor exists, with a dedicated regression test covering the localStorage getter-only case (__tests__/setupBrowserStubs.test.ts:63-94).

Findings

1. @types/node catalog spec uses unusual semver syntaxpnpm-workspace.yaml:75

'@types/node': ^22.x

^22.x is non-standard — it mixes the caret range with a wildcard segment. It currently resolves to 22.19.7 and works, but it is inconsistent with every other catalog entry (e.g. ^7.22.0, ^5.9.3, ^4.17.12). Recommend ^22.0.0 for consistency, or just 22.x if the intent is to stay within Node 22 typings only.

2. isSupportedNodeVersion is not unit-tested

The PR description acknowledges this was intentional, and the function is small (packages/@sanity/cli/bin/run.js:10-14). Calling it out only because it is now the sole runtime gate keeping unsupported Node versions from booting the CLI — a one-test guard would be cheap insurance against accidental regressions (e.g. someone flipping >= to >). Non-blocking.

3. Coverage runner pinned to Node 24.github/workflows/test.yml:153

# Node 24 required: runs .ts files directly via built-in type stripping
node-version: 24

Comment is now slightly misleading — Node 22.12+ also supports --experimental-strip-types and Node 24 has it on by default. If the intent is "use a version that strips types without flags," that's Node 24+, which is still accurate. No change required, but consider tightening the comment to "Node 24+ required for default type stripping" if you touch this again.

Notes

  • All package.json engines fields updated consistently to >=22.12.
  • The CI matrices (test.yml, e2e.yml, e2e-scheduled.yml) and one-off jobs (pkg-pr-new.yml, snapshot-release.yml) are all updated. Coverage collection is correctly moved to Node 22 only.
  • Changeset (.changeset/pr-1125.md) correctly marks all five published packages as major and follows the existing summary style.
  • cli-e2e/README.md matrix text matches the new 22/24/26 versions.
  • E2E test simplification (removing skipYarnOnOldNode and the nodeMajor branch in createSanityPackageManagers.test.ts) is consistent with the dropped Node 20 baseline.
  • Removed onUnhandledError flake-suppression in vitest.config.ts was specific to win32 + v20.* and is now correctly dead code.

Out of scope but noted in PR description

The author flagged rimraf, tinyglobby, nanoid, and execa as candidates for removal/vendoring in follow-up work. Agree these are worth their own PRs rather than bundling here.


• Branch: deprecate-node-20

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

📦 Bundle Stats — @sanity/cli

Compared against main (cd110e7c)

@sanity/cli

Metric Value vs main (cd110e7)
Internal (raw) 2.1 KB -
Internal (gzip) 799 B -
Bundled (raw) 10.97 MB -98 B, -0.0%
Bundled (gzip) 2.06 MB -24 B, -0.0%
Import time 846ms -1ms, -0.1%

bin:sanity

Metric Value vs main (cd110e7)
Internal (raw) 782 B -241 B, -23.6%
Internal (gzip) 423 B -63 B, -13.0%
Bundled (raw) 9.84 MB -242 B, -0.0%
Bundled (gzip) 1.77 MB -70 B, -0.0%
Import time 2.01s +17ms, +0.9%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (cd110e7c)

Metric Value vs main (cd110e7)
Internal (raw) 98.1 KB +1.1 KB, +1.2%
Internal (gzip) 23.3 KB +580 B, +2.5%
Bundled (raw) 21.61 MB +1.1 KB, +0.0%
Bundled (gzip) 3.42 MB +508 B, +0.0%
Import time 811ms +7ms, +0.9%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (cd110e7c)

Metric Value vs main (cd110e7)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

Comment thread pnpm-lock.yaml Outdated
Comment thread packages/@sanity/cli/bin/run.js Outdated
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Coverage Delta

File Statements
packages/@sanity/cli-core/src/util/environment/setupBrowserStubs.ts 100.0% (+ 5.9%)
packages/@sanity/cli-core/src/util/environment/stubs.ts 76.3% (+ 0.4%)
packages/@sanity/cli/src/actions/deploy/deployStudioSchemasAndManifests.ts 100.0% (±0%)
packages/@sanity/cli/src/commands/schemas/deploy.ts 95.0% (±0%)
packages/@sanity/cli/src/services/documents.ts 100.0% (±0%)

Comparing 5 changed files against main @ cd110e7c2b0cdad7438f53d958a0831fea2a3773

Overall Coverage

Metric Coverage
Statements 84.3% (+ 0.0%)
Branches 74.3% (+ 0.0%)
Functions 84.2% (±0%)
Lines 84.8% (+ 0.0%)

Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@macdonst macdonst self-assigned this May 21, 2026
@macdonst macdonst marked this pull request as draft May 21, 2026 15:23
@macdonst macdonst marked this pull request as ready for review May 21, 2026 15:47
macdonst and others added 2 commits May 21, 2026 11:49
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@macdonst macdonst changed the title feat: Drop Node.js 20 support. Node.js >=22.12 is now required feat!: Drop Node.js 20 support. Node.js >=22.12 is now required May 21, 2026
Comment thread .github/workflows/e2e-scheduled.yml Outdated
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@macdonst macdonst force-pushed the deprecate-node-20 branch from c7e9f56 to 3d48751 Compare May 21, 2026 18:32
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@runeb
Copy link
Copy Markdown
Member

runeb commented May 21, 2026

Related to sanity-io/sanity#12859

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 19333cf. Configure here.

Comment thread packages/@sanity/cli-core/src/util/environment/setupBrowserStubs.ts
jonahsnider
jonahsnider previously approved these changes May 21, 2026
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
macdonst added 2 commits May 21, 2026 16:23
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
Signed-off-by: Simon MacDonald <simon.macdonald@gmail.com>
@rexxars rexxars marked this pull request as draft May 27, 2026 19:27
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.

3 participants