fix(server): fail fast with a clear message on unsupported Node.js versions#6584
fix(server): fail fast with a clear message on unsupported Node.js versions#6584gaurav0107 wants to merge 3 commits into
Conversation
…rsions Running `flowise start` on Node < 20 crashes at startup with an opaque `ReferenceError: File is not defined`, thrown from cheerio's transitive undici when it references the `File` global (added in Node 20). Flowise declares `engines.node: ^24`, but that field is only advisory, so users on older runtimes hit the cryptic crash with no hint of the real cause. Add a small, dependency-free version guard that runs at the very top of bin/run (before @oclif/core loads any command) and prints an actionable message naming the required and current Node versions, then exits 1. The guard reads the required major from engines.node, fails open on unparseable input, and is wrapped so it can never itself block startup. The logic lives in a pure, unit-tested helper. Closes FlowiseAI#5670
There was a problem hiding this comment.
Code Review
This pull request introduces a Node.js version check at startup to fail fast with a clear error message if the running Node.js version is unsupported (specifically to prevent opaque crashes on Node < 20 where the File global is missing). It adds utility functions for parsing and asserting the Node.js version along with corresponding unit tests. The review feedback suggests simplifying redundant string conversions in parseMajor, using loose equality (== null) for nullish checks in isSupportedNodeVersion, and removing unnecessary imports from @jest/globals in the test file to avoid potential dependency resolution issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| */ | ||
| export const isSupportedNodeVersion = (current: string, requiredMajor: number): boolean => { | ||
| const major = parseMajor(current) | ||
| if (major === null) return true |
There was a problem hiding this comment.
According to the repository's standard idioms, loose equality (== null) should be used for nullish checks to cover both null and undefined. Additionally, ensure that the check returns false if the node's version is missing to prevent synchronization UI elements from appearing for nodes that do not support versioning.
| if (major === null) return true | |
| if (major == null) return false |
References
- In JavaScript/TypeScript, use loose equality (
== null) as a standard idiom for a 'nullish' check that covers bothnullandundefined. - When implementing version comparison for nodes, ensure that the check returns false if the node's version is missing. This prevents synchronization UI elements from appearing for nodes that do not support versioning, maintaining consistency with legacy implementations.
There was a problem hiding this comment.
Thanks for the review. I intentionally kept this returning true here (and used strict === null): parseMajor returns number | null (never undefined), and the goal is to fail open — when the running Node version string can't be parsed (nightly/custom builds), treat it as supported rather than block startup. Returning false would fail closed and could block legitimate users on unusual-but-valid runtimes. Note this is the Node.js process runtime version, not component/node versioning, so the legacy-versioning guidance doesn't apply.
| export const parseMajor = (version: string): number | null => { | ||
| const match = /^v?(\d+)\./.exec(String(version ?? '').trim()) | ||
| return match ? parseInt(match[1], 10) : null | ||
| } |
There was a problem hiding this comment.
Since the version parameter is already typed as a string, the nullish coalescing and String() conversion are redundant. We can simplify this to directly trim the string.
| export const parseMajor = (version: string): number | null => { | |
| const match = /^v?(\d+)\./.exec(String(version ?? '').trim()) | |
| return match ? parseInt(match[1], 10) : null | |
| } | |
| export const parseMajor = (version: string): number | null => { | |
| const match = /^v?(\\d+)\\./.exec(version.trim()) | |
| return match ? parseInt(match[1], 10) : null | |
| } |
There was a problem hiding this comment.
Good call — simplified to version.trim() in ec6e557. Thanks.
| @@ -0,0 +1,78 @@ | |||
| import { describe, expect, it } from '@jest/globals' | |||
There was a problem hiding this comment.
Done in ec6e557 — dropped the @jest/globals import to rely on Jest's global describe/expect/it (typed via @types/jest), matching the majority of the server test suite. Thanks.
Drop the unused `err` binding from the bin/run try/catch.
- parseMajor: drop the redundant String()/nullish coalescing now that the parameter is typed as string - nodeVersion.test.ts: rely on Jest's global describe/expect/it (typed via @types/jest) instead of importing from @jest/globals, matching the majority convention of the server test suite
Summary
Running
npx flowise starton an unsupported Node.js version crashes at startup with an opaque error:The
Fileglobal only exists on Node.js >= 20, andundici(pulled in transitively viacheerio, used by the Cheerio document loader and the Web Scraper tool) references it at module-load time. Flowise declaresengines.node: "^24", but that field is only advisory —npm install -g flowisesucceeds on older runtimes and the user only finds out via the cryptic crash, with no hint that the real cause is their Node version. This matches the diagnosis in the issue thread ("are you on version 18+?").This PR adds a small, dependency-free version guard that runs at the very top of
bin/run, before@oclif/coreloads any command (thestartcommand imports the server — and thereforecheerio— at module scope, so the check has to happen first). When the running Node.js major is below the one declared inengines.node, it prints an actionable message and exits with code 1:Design notes:
engines.node, so it stays in sync automatically.bin/runis wrapped intry/catch, so the check can never itself break startup (e.g. whendist/is not built in a dev tree).src/utils/nodeVersion.ts);bin/runonly wires it in.It does not attempt to make Flowise run on an unsupported Node.js version (not possible —
undicineeds the platformFileglobal); it surfaces the real requirement instead of a stack trace.Verification
pnpm --filter flowise test— new suitesrc/utils/nodeVersion.test.tspasses (13/13): version parsing,enginesrange parsing, support comparison (including fail-open), the user-facing message, andassertNodeVersion.tsc(server) — clean, no new type errors introduced.eslint+prettier --check— clean on all changed files.assertNodeVersion('v18.0.0')returns the error message andprocess.exit(1)path;v24+returns{ ok: true }.Scope is limited to
packages/server(bin/run+ one new helper + its test); no proprietary paths are touched.Closes #5670