Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
4 changes: 2 additions & 2 deletions src/lib/preflight.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions tests/preflight-compat.test.mjs
Original file line number Diff line number Diff line change
@@ -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');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

【P2 兼容性与测试漏洞风险】
当前测试中使用硬编码的单引号 from 'node: 来匹配 node: 协议导入。如果 TypeScript 编译器、格式化工具(如 Prettier)或未来的构建流程将生成的 JS 代码中的单引号转换为双引号(例如 from "node:fs"),此断言将无法匹配,从而导致测试静默通过(即无法检测出引入了 node: 导入的回归问题)。

建议将正则表达式修改为同时支持单引号和双引号,以提高测试的健壮性。

Suggested change
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, /\?\?/, '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'\)/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

【P2 兼容性与测试漏洞风险】
类似于上述问题,这里的断言也硬编码了单引号。如果构建输出或格式化改变了引号类型(例如使用双引号),这些测试将会失败(对于 assert.match)或失效(对于 assert.doesNotMatch)。

建议使用支持单双引号的正则表达式,使测试更加健壮。

Suggested change
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['"]\)/);

});
Loading