Repository navigation
Conversation
Studio had no engines.node, so a dev on the wrong Node (jsdom-based unit tests break above Node 24; .nvmrc pins 24.21.0) got a wholesale, unexplained suite failure instead of a clear error. - engines.node: ">=24 <25", matching .nvmrc. - scripts/check-node-version.mjs, wired in as vitest.config.ts's globalSetup: runs once before any test file, throwing (vitest's own error reporting, not a bare process.exit that could kill an embedded host) with a clear message on any Node outside the range, regardless of how vitest is invoked (pnpm test*, `pnpm exec vitest`, an IDE "run test"). The range parser is anchored to the exact ">=N <N" shape and throws on anything else, so an edit to engines.node can't silently stop being enforced. - No pnpm-workspace.yaml engineStrict: it would also block harper-pro's prod-bundle build, which runs `pnpm install`/`pnpm run build:local` against studio's `prod` branch on Node 22 and latest (26.x) in create-release.yaml and publish-docker.yaml. pnpm's own engines check also only fires when install does real work, so it can't reliably guard "pnpm install" after a Node switch with node_modules already in place either way — the test-time guard above is the actual, reliable enforcement. Recorded as a DESIGN.md invariant since "add engineStrict" is the natural way to misread this as incomplete. - README troubleshooting note updated off the stale "Node 20+". Does not touch studio#1689 (undici WebSocket teardown), a separate issue out of scope here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3s4BXF9ivRLFRaHBBoNQy Dispatch-Task: studio-node-engines-guard
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces a Node.js version enforcement mechanism by adding a Node engine restriction (>=24 <25) to package.json and a custom check script (check-node-version.mjs) executed during Vitest's global setup. It also updates documentation in DESIGN.md and README.md to reflect these changes. Feedback on the changes suggests improving the robustness of the version parser to handle leading 'v' characters (such as in process.version) and adding a corresponding unit test to verify this behavior.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
dawsontoth
approved these changes
Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⊙ Problem
Studio has no
engines.node, and the unit suite fails wholesale on Node > 24 (jsdom-based suites) with no hint why —.nvmrcpins 24.21.0, but nothing enforces it. A dev who drifts off that version gets a confusing pile of jsdom failures instead of a one-line "wrong Node" message.💡 Solution
package.json:engines.node:">=24 <25", matching.nvmrc.scripts/check-node-version.mjs, wired intovitest.config.ts'sglobalSetup: runs once before any test file, on any Node, regardless of how vitest is invoked (pnpm test/test:watch/test:coverage,pnpm exec vitest, an IDE "run test"). Throws a clear, one-line message naming the required range — vitest reports it natively rather than the process exiting out from under an embedded host (e.g. an IDE's vitest child).isSupported, same file) is anchored to the exact>=N <Nshapeengines.nodecurrently uses, and throws on anything else, so a later edit to that field can't silently stop being enforced — pinned byscripts/check-node-version.test.mjs.README.md:142troubleshooting line updated off the stale "Node 20+".DESIGN.md:67: recorded the engineStrict/harper-pro invariant below, since the natural way to "finish" strict enforcement is to flipengineStricton — which would reopen the problem this PR avoids.⚖️ Alternatives
✅ Verification
node/direct calls: Node 24.21.0 → passes; Node 22/26 → throws the intended diagnostic. Confirmed on this box's actual Node 26.2.0, and via the independent review's own executed checks on 22.23.1/26.2.0.pnpm exec vitest run(full suite, globalSetup temporarily removed to exercise it on this sandbox's only available Node, 26.2.0): 396 files / 3667 tests pass. One file (src/lib/monaco/editorApi.test.ts) timed out under parallel load on an earlier run and passed cleanly both in isolation and on a clean rerun — a pre-existing, load-dependent flake unrelated to this change.pnpm exec tsc -b: no new errors (this worktree has pre-existing, unrelated monaco-editor duplicate-type errors from two differentnode_modulestrees — a worktree artifact, not introduced here).oxlint/dprint check: clean across the whole repo.pnpm exec vitest run scripts/check-node-version.test.mjs: 8/8 pass, including the anchored-regex fail-closed cases.engineStrict/harper-pro blocker (fixed by removing it); round 2 found a fail-open parser bug, aprocess.exitrisk, a dead code path, and narrative-comment/dangling-reference issues (all fixed); round 3 closed with severitynit— remaining items are documented, deliberate trade-offs (Node 24 prereleases pass on major-only comparison;pnpm dev/buildstay unguarded on purpose, matching theengineStrictdecision above).Related PRs: none found
Review-Coverage: authored=claude; ran=gemini,cursor-composer,codex,cursor-muse; adjudicated=domain; declined=cursor-grok,cursor-kimi; rounds=3; full=2 @ 0ed8de2
Review-Attention: read ~7m (sensitive: package.json; decisions: guard-source-of-truth, vitest-only-enforcement, fail-closed-parser) @ 0ed8de2