/** * 桌面打包脚本:交互确认版本号 → electron-builder → 复制友好文件名 * * 交互: * 上次打包版本号:x.y.z * 1) 使用上次版本号 * 2) 输入新版本号 * * 产物: * - 自动更新用 ASCII 名:xk-desktop-{version}-win-x64.exe(latest.yml 引用) * - 人工/后台上传用:萧康医药 {version}.exe * * 非交互(CI):XK_DESKTOP_VERSION=1.2.3 或 --version=1.2.3 */ import { spawn } from 'node:child_process'; import { copyFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { createInterface } from 'node:readline'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const desktopRoot = join(__dirname, '..'); const packageJsonPath = join(desktopRoot, 'package.json'); const releaseDir = join(desktopRoot, 'release'); /** semver 简校验 */ function isValidVersion(v) { return /^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$/.test(v); } function readPkg() { return JSON.parse(readFileSync(packageJsonPath, 'utf8')); } function writePkgVersion(version) { const pkg = readPkg(); pkg.version = version; writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8'); } /** 从 argv / 环境变量取强制版本(跳过交互) */ function getForcedVersion() { const arg = process.argv.find((a) => a.startsWith('--version=')); if (arg) return arg.slice('--version='.length).trim(); if (process.env.XK_DESKTOP_VERSION) return String(process.env.XK_DESKTOP_VERSION).trim(); return ''; } function ask(question) { const rl = createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(String(answer || '').trim()); }); }); } /** * 交互确认本次打包版本 * @returns {Promise} */ async function resolveVersion() { const forced = getForcedVersion(); if (forced) { if (!isValidVersion(forced)) { throw new Error(`无效版本号: ${forced}(请用如 1.0.1)`); } console.log(`使用指定版本号: ${forced}`); return forced; } if (!process.stdin.isTTY) { const current = readPkg().version || '1.0.0'; console.log(`非交互环境,沿用 package.json 版本: ${current}`); return current; } const last = readPkg().version || '1.0.0'; console.log(''); console.log('========== 桌面端版本确认 =========='); console.log(`上次打包版本号:${last}`); console.log('请选择:'); console.log(` 1) 使用上次版本号(${last})`); console.log(' 2) 输入新的版本号'); console.log(''); let choice = await ask('请输入选项 [1/2](默认 1): '); if (choice === '' || choice === '1') { return last; } if (choice !== '2') { console.log('未识别选项,默认使用上次版本号'); return last; } for (;;) { const next = await ask('请输入新版本号(如 1.0.1): '); if (isValidVersion(next)) { return next; } console.log('格式不正确,请使用 semver,例如 1.0.1'); } } function runBuilder() { // electron-builder 下载 Electron / NSIS 等会打 GitHub;国内网络常超时。 // 根 .npmrc 有镜像配置,但 spawn 出的进程不一定带上,这里强制写入环境变量。 const electronMirror = process.env.ELECTRON_MIRROR || 'https://npmmirror.com/mirrors/electron/'; const builderBinariesMirror = process.env.ELECTRON_BUILDER_BINARIES_MIRROR || 'https://npmmirror.com/mirrors/electron-builder-binaries/'; const env = { ...process.env, ELECTRON_MIRROR: electronMirror, ELECTRON_BUILDER_BINARIES_MIRROR: builderBinariesMirror, // 兼容 app-builder-lib 读取的 npm_config_* 形态 npm_config_electron_mirror: electronMirror, npm_config_electron_builder_binaries_mirror: builderBinariesMirror, }; console.log(`Electron 镜像: ${electronMirror}`); console.log(`Builder 工具链镜像: ${builderBinariesMirror}`); return new Promise((resolve, reject) => { const child = spawn( 'pnpm exec electron-builder --config electron-builder.config.cjs', { cwd: desktopRoot, env, shell: true, stdio: 'inherit', }, ); child.on('error', reject); child.on('close', (code) => { if (code === 0) resolve(); else reject(new Error(`electron-builder 失败,退出码 ${code}`)); }); }); } /** * 复制 Windows 安装包为「萧康医药 {version}.exe」方便后台/人工上传 * 保留原 ASCII 文件名供 electron-updater 的 latest.yml 使用 */ function copyFriendlyWinExe(version) { if (!existsSync(releaseDir)) { console.warn(`未找到 release 目录: ${releaseDir}`); return; } const files = readdirSync(releaseDir); // 优先精确匹配 builder artifactName const exact = `xk-desktop-${version}-win-x64.exe`; let src = files.includes(exact) ? exact : ''; if (!src) { src = files.find( (f) => f.endsWith('.exe') && f.includes(version) && !f.includes('萧康') && !f.toLowerCase().includes('blockmap'), ) || ''; } if (!src) { console.warn('未找到 Windows 安装包,跳过友好文件名复制'); return; } const destName = `萧康医药 ${version}.exe`; const from = join(releaseDir, src); const to = join(releaseDir, destName); copyFileSync(from, to); console.log(`\n已生成上传用安装包: ${to}`); console.log(`(自动更新仍使用: ${from})`); } async function main() { const version = await resolveVersion(); const current = readPkg().version; if (version !== current) { writePkgVersion(version); console.log(`已写入 package.json version = ${version}`); } else { console.log(`版本号保持 ${version}`); } console.log('\n开始 electron-builder 打包…\n'); await runBuilder(); copyFriendlyWinExe(version); console.log('\n打包流程结束'); } main().catch((err) => { console.error(`\n❌ ${err.message || err}`); process.exit(1); });