From 30d90b6eaf58f5b980ef879213908c79aac412bc Mon Sep 17 00:00:00 2001 From: Kris Xia Date: Sat, 6 Jun 2026 10:11:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=20Node=20=E5=90=AF=E5=8A=A8=E9=A2=84=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli.ts | 23 +++++++++++++++-------- src/lib/preflight.ts | 4 ++-- tests/preflight-compat.test.mjs | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 tests/preflight-compat.test.mjs diff --git a/src/cli.ts b/src/cli.ts index 838c20f..31446fc 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,12 +2,19 @@ // 必须放最前:Node 版本守卫,先于任何会用到全局 fetch 的模块求值 import './lib/preflight.js'; -import { createProgram } from './lib/command.js'; -import { logger } from './utils/logger.js'; -// CLI 入口 -const program = createProgram(); -program.parseAsync(process.argv).catch((error) => { - logger.error(error instanceof Error ? error.message : String(error)); - process.exit(1); -}); +// CLI 入口依赖动态加载,确保低版本 Node 先执行 preflight 并给出可读提示 +Promise.all([import('./lib/command.js'), import('./utils/logger.js')]) + .then(async ([{ createProgram }, { logger }]) => { + const program = createProgram(); + try { + await program.parseAsync(process.argv); + } catch (error) { + logger.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } + }) + .catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + }); diff --git a/src/lib/preflight.ts b/src/lib/preflight.ts index dbf162b..f54104f 100644 --- a/src/lib/preflight.ts +++ b/src/lib/preflight.ts @@ -1,9 +1,9 @@ // 启动期前置检查:Node 版本守卫 // 必须在任何会用到全局 fetch 的模块求值之前执行 // 因为还在 i18n 初始化前,文案直接硬编码中英双语,避免循环依赖 -import { writeSync } from 'node:fs'; +import { writeSync } from 'fs'; -const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10); +const major = Number.parseInt(process.versions.node.split('.')[0] || '0', 10); const MIN_MAJOR = 18; if (Number.isFinite(major) && major < MIN_MAJOR) { diff --git a/tests/preflight-compat.test.mjs b/tests/preflight-compat.test.mjs new file mode 100644 index 0000000..eeee9b4 --- /dev/null +++ b/tests/preflight-compat.test.mjs @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +test('preflight can be parsed by older Node before showing the version guard', () => { + const source = readFileSync(new URL('../dist/lib/preflight.js', import.meta.url), 'utf8'); + + assert.doesNotMatch(source, /\?\?/, 'preflight must not use nullish coalescing before the Node version guard runs'); + assert.doesNotMatch(source, /\?\./, 'preflight must not use optional chaining before the Node version guard runs'); + assert.doesNotMatch(source, /from 'node:/, 'preflight must not use node: imports before the Node version guard runs'); +}); + +test('cli defers application imports until after preflight runs', () => { + const source = readFileSync(new URL('../dist/cli.js', import.meta.url), 'utf8'); + + assert.match(source, /import '\.\/lib\/preflight\.js';/); + assert.doesNotMatch(source, /import \{ createProgram \} from '\.\/lib\/command\.js';/); + assert.doesNotMatch(source, /import \{ logger \} from '\.\/utils\/logger\.js';/); + assert.match(source, /import\('\.\/lib\/command\.js'\)/); + assert.match(source, /import\('\.\/utils\/logger\.js'\)/); +}); From 79aa7c70a9ad7396e047fff15584d2793d8acb61 Mon Sep 17 00:00:00 2001 From: Kris Xia Date: Sat, 6 Jun 2026 10:15:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=E6=94=BE=E5=AE=BD=E9=A2=84?= =?UTF-8?q?=E6=A3=80=E5=AF=BC=E5=85=A5=E6=96=AD=E8=A8=80=E5=BC=95=E5=8F=B7?= =?UTF-8?q?=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/preflight-compat.test.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/preflight-compat.test.mjs b/tests/preflight-compat.test.mjs index eeee9b4..6648728 100644 --- a/tests/preflight-compat.test.mjs +++ b/tests/preflight-compat.test.mjs @@ -7,15 +7,15 @@ test('preflight can be parsed by older Node before showing the version guard', ( assert.doesNotMatch(source, /\?\?/, 'preflight must not use nullish coalescing before the Node version guard runs'); assert.doesNotMatch(source, /\?\./, 'preflight must not use optional chaining before the Node version guard runs'); - assert.doesNotMatch(source, /from 'node:/, 'preflight must not use node: imports before the Node version guard runs'); + assert.doesNotMatch(source, /from ['"]node:/, 'preflight must not use node: imports before the Node version guard runs'); }); test('cli defers application imports until after preflight runs', () => { const source = readFileSync(new URL('../dist/cli.js', import.meta.url), 'utf8'); - assert.match(source, /import '\.\/lib\/preflight\.js';/); - assert.doesNotMatch(source, /import \{ createProgram \} from '\.\/lib\/command\.js';/); - assert.doesNotMatch(source, /import \{ logger \} from '\.\/utils\/logger\.js';/); - assert.match(source, /import\('\.\/lib\/command\.js'\)/); - assert.match(source, /import\('\.\/utils\/logger\.js'\)/); + assert.match(source, /import ['"]\.\/lib\/preflight\.js['"];/); + assert.doesNotMatch(source, /import \{ createProgram \} from ['"]\.\/lib\/command\.js['"];/); + assert.doesNotMatch(source, /import \{ logger \} from ['"]\.\/utils\/logger\.js['"];/); + assert.match(source, /import\(['"]\.\/lib\/command\.js['"]\)/); + assert.match(source, /import\(['"]\.\/utils\/logger\.js['"]\)/); });