From 94592757b35491113f26de8afaaefd3a531dd2a6 Mon Sep 17 00:00:00 2001 From: lxcario Date: Tue, 23 Jun 2026 22:35:11 +0300 Subject: [PATCH] feat(cli): add runtime Node.js version check with clear error message --- src/index.ts | 11 +++++++++ src/version-guard.test.ts | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/version-guard.test.ts diff --git a/src/index.ts b/src/index.ts index e2cf663..9fb97f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,15 @@ #!/usr/bin/env node + +// Guard: exit early with a clear message on unsupported Node.js versions. +// This runs before any ESM/modern-syntax imports that would crash with cryptic errors. +const major = Number(process.versions.node.split('.')[0]); +if (major < 20) { + process.stderr.write( + `Error: testsprite requires Node.js >= 20 (found ${process.versions.node}).\nInstall the latest LTS from https://nodejs.org\n`, + ); + process.exit(1); +} + import { Command, CommanderError } from 'commander'; import { createAgentCommand } from './commands/agent.js'; import { createAuthCommand } from './commands/auth.js'; diff --git a/src/version-guard.test.ts b/src/version-guard.test.ts new file mode 100644 index 0000000..1389996 --- /dev/null +++ b/src/version-guard.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest'; + +/** + * The version guard logic extracted for testability. + * The real guard lives at the top of src/index.ts as imperative code. + * These tests verify the parsing and threshold logic. + */ + +function parseMajorVersion(nodeVersion: string): number { + return Number(nodeVersion.split('.')[0]); +} + +function shouldRejectVersion(nodeVersion: string): boolean { + return parseMajorVersion(nodeVersion) < 20; +} + +describe('Node.js version guard logic', () => { + it('rejects Node 18.x', () => { + expect(shouldRejectVersion('18.19.1')).toBe(true); + }); + + it('rejects Node 16.x', () => { + expect(shouldRejectVersion('16.20.2')).toBe(true); + }); + + it('rejects Node 14.x', () => { + expect(shouldRejectVersion('14.21.3')).toBe(true); + }); + + it('accepts Node 20.x', () => { + expect(shouldRejectVersion('20.11.0')).toBe(false); + }); + + it('accepts Node 22.x', () => { + expect(shouldRejectVersion('22.1.0')).toBe(false); + }); + + it('accepts Node 21.x', () => { + expect(shouldRejectVersion('21.0.0')).toBe(false); + }); + + it('parses major version correctly from semver string', () => { + expect(parseMajorVersion('20.11.1')).toBe(20); + expect(parseMajorVersion('18.0.0')).toBe(18); + expect(parseMajorVersion('22.3.0')).toBe(22); + }); + + it('the running Node satisfies the guard (meta-test)', () => { + // This test is running on Node >= 20, so it must pass the guard. + expect(shouldRejectVersion(process.versions.node)).toBe(false); + }); +});