#!/usr/bin/env node /** * postinstall —— 只重建"必须按 Electron ABI 重编"的原生模块, 其余一概不碰。 * * 为什么不用 `electron-builder install-app-deps`: * 它会重建**所有**原生模块, 并给子进程带上 `npm_config_build_from_source=false`。 * npm 11 对未知的 npm_config_* 保留原始字符串, 于是子进程里它是字符串 "false" —— * 在 JS 里是真值 —— pkg-prebuilds-verify 直接 exit 1 * (node_modules/pkg-prebuilds/bin/verify.mjs:14), 强制走源码编译。 * node-hid 于是去编译它自带的 node-addon-api 3.2.1, 和现代 Node 头文件不兼容, 编译失败, * 整个 `npm install` 挂掉 —— 而它本来根本不需要编译。 * * 真正需要按 Electron ABI 重编的只有 better-sqlite3: 它不是 N-API, 绑死 V8/ABI * (Electron 32 → node ABI 128), 必须对着 Electron 头文件编译。 * 其余原生模块 (@lydell/node-pty / node-hid / @serialport/bindings-cpp / usb) * 自带 N-API 预编译产物, 任何 ABI 都能加载 —— 重编既没必要, 又有害。 * * 环境变量: * LNM_ELECTRON_DISTURL 覆盖 electron 头文件源 (默认走 npmmirror 镜像) */ 'use strict' const { execFileSync } = require('child_process') const fs = require('fs') const os = require('os') const path = require('path') const root = path.resolve(__dirname, '..') const log = (...a) => console.log('[postinstall]', ...a) function readJSON(file) { try { return JSON.parse(fs.readFileSync(file, 'utf8')) } catch { return null } } function npmExec() { const cli = process.env.npm_execpath if (cli && fs.existsSync(cli)) { return { cmd: process.execPath, args: [cli] } } return { cmd: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: [] } } function electronBin() { const base = path.join(root, 'node_modules/electron/dist') const name = process.platform === 'win32' ? 'electron.exe' : 'electron' const p = path.join(base, name) return fs.existsSync(p) ? p : null } // ---------------------------------------------------------------- electron 版本 const electronPkg = readJSON(path.join(root, 'node_modules/electron/package.json')) if (!electronPkg) { log('electron 未安装, 跳过原生模块重建') process.exit(0) } const electronVersion = electronPkg.version log(`electron ${electronVersion}`) // ------------------------------------------------------- 只重建 better-sqlite3 const disturl = process.env.LNM_ELECTRON_DISTURL || process.env.ELECTRON_MIRROR || 'https://npmmirror.com/mirrors/electron' const env = { ...process.env, npm_config_runtime: 'electron', npm_config_target: electronVersion, npm_config_arch: process.arch, npm_config_target_arch: process.arch, npm_config_platform: process.platform, npm_config_target_platform: process.platform, npm_config_disturl: disturl, npm_config_devdir: path.join(process.env.HOME || process.env.USERPROFILE || '', '.electron-gyp'), // better-sqlite3 必须真编译 (prebuild-install 的 electron 预编译产物不一定覆盖当前 ABI)。 // 注意: 这个开关只作用于下面显式重建的 better-sqlite3 一个包, 不会波及 node-hid 等 // N-API 模块 —— 那些包根本不进这条路径, 继续用自带预编译产物。 npm_config_build_from_source: 'true', npm_config_update_binary: 'true', npm_config_fallback_to_build: 'true', } const npm = npmExec() log('重建 better-sqlite3 (Electron ABI) ...') try { execFileSync(npm.cmd, [...npm.args, 'rebuild', 'better-sqlite3'], { cwd: root, env, stdio: 'inherit', }) } catch (err) { console.error('[postinstall] better-sqlite3 重建失败 —— 应用启动会因 sqlite 打不开而崩。') console.error('[postinstall] 手动修复: cd node_modules/better-sqlite3 && npx node-gyp rebuild --release') process.exit(1) } // --------------------------------------------------------------- 装完就验一次 const bin = electronBin() if (!bin) { log('找不到 electron 可执行文件, 跳过 sqlite 校验 (不影响开发)') } else { const probe = path.join(os.tmpdir(), `lnm-sqlite-probe-${process.pid}.js`) // 探针放在系统临时目录, 所以不能用裸 require —— 解析路径会以 /tmp 为基准, // 找不到项目里的 node_modules。这里写绝对路径。 const sqlitePath = path.join(root, 'node_modules', 'better-sqlite3') fs.writeFileSync( probe, `const D=require(${JSON.stringify(sqlitePath)});const db=new D(':memory:');db.exec('create table t(a)');db.prepare('insert into t values (?)').run(1);if(db.prepare('select count(*) c from t').get().c!==1)throw new Error('sqlite write/read mismatch');console.log('sqlite-ok');` ) try { const out = execFileSync(bin, [probe], { cwd: root, env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], }) if (!out.includes('sqlite-ok')) throw new Error(`unexpected output: ${out}`) log('better-sqlite3 在 Electron ABI 下验证通过') } catch (err) { console.error('[postinstall] better-sqlite3 在 Electron 下加载/读写失败:', err.message) process.exit(1) } finally { fs.rmSync(probe, { force: true }) } } // ------------------------------------------- N-API 模块: 确认预编译产物还在 // node-pty 1.2 是"主包 + 平台包"拆包架构: 二进制在 @lydell/node-pty-- 里, // 主包本身没有 prebuilds/ 目录 —— 别按主包去找, 否则只会误报。 const prebuilt = [ ['node-hid', 'prebuilds'], ['@serialport/bindings-cpp', 'prebuilds'], ] for (const [pkg, sub] of prebuilt) { const dir = path.join(root, 'node_modules', pkg, sub) if (!fs.existsSync(dir)) { log(`提醒: ${pkg} 缺少 ${sub}/ 预编译产物, 该功能可能不可用`) } } // node-pty: 二进制在平台包里 (node-pty--/prebuilds/-/pty.node) const ptyPlat = `@lydell/node-pty-${process.platform}-${process.arch}` const ptyBin = path.join(root, 'node_modules', ptyPlat, 'prebuilds', `${process.platform}-${process.arch}`) if (!fs.existsSync(ptyBin)) { log(`提醒: ${ptyPlat} 缺少 ${path.relative(root, ptyBin)}, 远程终端可能不可用`) } log('done')