110 lines
4.4 KiB
JavaScript
110 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
// 自适应 vitest 启动器 —— 解决 better-sqlite3 的「双 ABI 互相覆盖」问题。
|
|
//
|
|
// 背景(2026-10-19 实地复现):
|
|
// better-sqlite3 不是 N-API,一个 .node 只对应一个 ABI:
|
|
// - Electron 30.5(NODE_MODULE_VERSION 123)要 Electron 版 —— app 运行时用
|
|
// - 系统 Node 24(NODE_MODULE_VERSION 137)要 Node 版 —— vitest 用
|
|
// 两者共用同一个路径:
|
|
// node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
|
//
|
|
// 旧做法(pretest / pretest:run / pretest:coverage 都是这一行):
|
|
// node scripts/verify-abi-node.cjs || npm rebuild better-sqlite3
|
|
// → 为了让测试能跑,**破坏性地**把 app 那份覆盖成 Node 版。
|
|
// Windows 上 .node 被运行中的 electron 进程锁住 → unlink 报
|
|
// EPERM/EBUSY → 重建失败 → pretest 非 0 退出 → npm 把整条
|
|
// `npm run test:run` 一起中止(哪怕测试本身全绿)。
|
|
// 反向也成立:测试跑完 .node 变成 Node 版,下一次 `npm run dev` 又要
|
|
// 重建回 Electron 版 —— 每次切换都可能被运行中的 app 锁死。
|
|
//
|
|
// 策略:不再为了跑测试而重建。改成二选一启动器,按「当前 .node 到底是谁的」
|
|
// 决定用谁来跑 vitest:
|
|
// 1. 系统 Node 能直接 require('better-sqlite3') → 用系统 Node 跑 vitest。
|
|
// (Linux/macOS 的 napi prebuilt 天然两用;Windows 上若已是 Node 版 .node
|
|
// 也走这条)
|
|
// 2. 否则(Windows + Electron 版 .node,也就是常态)→ 用 Electron 自带的
|
|
// Node 跑 vitest(ELECTRON_RUN_AS_NODE=1)。此时测试与 app 共用同一份
|
|
// 原生模块:零重建、零 EBUSY、也不会把 app 那份覆盖掉。
|
|
//
|
|
// 附带收益:Electron 30.5 内嵌 Node 是 v20.16.0,仍在 package.json 的
|
|
// engines(>=20.16.0 <25)范围内,所以这条路不引入新的版本约束。
|
|
//
|
|
// 退出码透传 vitest 的退出码;CI 语义不变。
|
|
'use strict';
|
|
|
|
const { spawnSync } = require('child_process');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const VITEST_ENTRY = path.join(ROOT, 'node_modules', 'vitest', 'vitest.mjs');
|
|
const argv = process.argv.slice(2);
|
|
|
|
function log(msg) {
|
|
process.stderr.write(`[run-tests] ${msg}\n`);
|
|
}
|
|
|
|
/** 系统 Node 当前能否加载 better-sqlite3(= .node 是否是 Node ABI 版本)。 */
|
|
function nativeLoadable() {
|
|
try {
|
|
const Database = require('better-sqlite3');
|
|
const db = new Database(':memory:');
|
|
db.close();
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** electron npm 包在 Node 下导出的是二进制路径字符串;拿不到就退回 dist 猜路径。 */
|
|
function electronBinary() {
|
|
try {
|
|
const p = require('electron');
|
|
if (typeof p === 'string' && fs.existsSync(p)) return p;
|
|
} catch {
|
|
/* 落到下面的猜测 */
|
|
}
|
|
const exe = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
const guess = path.join(ROOT, 'node_modules', 'electron', 'dist', exe);
|
|
return fs.existsSync(guess) ? guess : null;
|
|
}
|
|
|
|
function run(cmd, cmdArgs, env) {
|
|
const res = spawnSync(cmd, cmdArgs, { stdio: 'inherit', cwd: ROOT, env });
|
|
if (res.error) {
|
|
log(`启动失败: ${res.error.message}`);
|
|
return 1;
|
|
}
|
|
// 被信号杀死时 status 为 null
|
|
return typeof res.status === 'number' ? res.status : 1;
|
|
}
|
|
|
|
if (!fs.existsSync(VITEST_ENTRY)) {
|
|
log(`找不到 vitest 入口 ${VITEST_ENTRY} —— 先执行 npm install`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let code;
|
|
if (nativeLoadable()) {
|
|
// 路线 1:系统 Node 直跑(app 那份 .node 已经是 Node 版,或 napi 通用版)
|
|
code = run(process.execPath, [VITEST_ENTRY, ...argv], process.env);
|
|
} else {
|
|
// 路线 2:用 Electron 自带 Node 跑 —— 与 app 共用同一份原生模块
|
|
const electron = electronBinary();
|
|
if (!electron) {
|
|
log('better-sqlite3 在系统 Node 下不可加载,且找不到 electron 可执行文件。');
|
|
log(' 手动修复(选一条):');
|
|
log(' npx electron-rebuild -f -w better-sqlite3 # 给 app 用');
|
|
log(' npm rebuild better-sqlite3 # 给系统 Node 用');
|
|
process.exit(1);
|
|
}
|
|
log('better-sqlite3 当前为 Electron ABI —— 改用 Electron 自带 Node 跑 vitest');
|
|
log(' (与 app 共用同一份原生模块:不重建、不覆盖,规避 EBUSY)');
|
|
code = run(electron, [VITEST_ENTRY, ...argv], {
|
|
...process.env,
|
|
ELECTRON_RUN_AS_NODE: '1',
|
|
});
|
|
}
|
|
|
|
process.exit(code);
|