// ============================================================ // build.mjs — 构建总管 // // 用法:node build.mjs [选项] // // 平台(必选其一): // --win Windows // --mac macOS // --linux Linux // // 版本(可选,默认 personal): // --personal 个人版 // --enterprise 企业版 // // 操作(可选组合,默认只执行 --build): // --build 仅构建(默认) // --clean 构建前清理 release/ dist/ dist-electron/ // --sign 代码签名 // --upload 上传安装包到 OSS // --publish 发布版本信息到后端 API // --all 全流程(clean + build + sign + upload + publish) // // 签名(仅 --sign 时可用): // --cert 证书文件路径 (.pfx) // --cert-password

证书密码 // // 示例: // node build.mjs --win --personal # 仅打包 // node build.mjs --win --personal --upload --publish # 打包 + 上传 + 发布 // node build.mjs --win --enterprise --all # 全流程 // node build.mjs --win --personal --sign --cert ./cert.pfx --cert-password xxx // ============================================================ import { spawn } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = __dirname; // ANSI 颜色工具(在其它代码之前定义,因为 printHelp() 在参数解析时就会调用) const c = (code, text) => `\x1b[${code}m${text}\x1b[0m`; const dim = (t) => c(2, t); const bold = (t) => c(1, t); const cyan = (t) => c(36, t); const green = (t) => c(32, t); const yellow = (t) => c(33, t); // ---------- 参数解析 ---------- const args = process.argv.slice(2); const opts = { win: false, mac: false, linux: false, personal: false, enterprise: false, clean: false, build: false, sign: false, upload: false, publish: false, cert: '', certPassword: '', }; let i = 0; while (i < args.length) { const a = args[i]; switch (a) { case '--win': opts.win = true; break; case '--mac': opts.mac = true; break; case '--linux': opts.linux = true; break; case '--personal': opts.personal = true; break; case '--enterprise': opts.enterprise = true; break; case '--clean': opts.clean = true; break; case '--build': opts.build = true; break; case '--sign': opts.sign = true; break; case '--upload': opts.upload = true; break; case '--publish': opts.publish = true; break; case '--all': opts.clean = opts.build = opts.sign = opts.upload = opts.publish = true; break; case '--cert': opts.cert = args[++i] || ''; break; case '--cert-password': opts.certPassword = args[++i] || ''; break; case '--help': // fallthrough case '-h': printHelp(); process.exit(0); break; // eslint 友好 default: // 忽略未知参数 break; } i++; } // ---------- 校验 ---------- const platformCount = [opts.win, opts.mac, opts.linux].filter(Boolean).length; if (platformCount !== 1) { console.error('错误: 请指定且仅指定一个平台(--win / --mac / --linux)'); process.exit(1); } const editionCount = [opts.personal, opts.enterprise].filter(Boolean).length; if (editionCount === 0) { opts.personal = true; // 默认个人版 } else if (editionCount > 1) { console.error('错误: --personal 和 --enterprise 不能同时使用'); process.exit(1); } // 默认执行 build if (!opts.clean && !opts.build && !opts.sign && !opts.upload && !opts.publish) { opts.build = true; } const platform = opts.win ? 'win32' : opts.mac ? 'darwin' : 'linux'; const edition = opts.personal ? 'personal' : 'enterprise'; const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')); const version = pkg.version; // ---------- 工具函数 ---------- function run(cmd, args, opts = {}) { return new Promise((resolve, reject) => { const child = spawn(cmd, args, { cwd: ROOT, stdio: 'inherit', shell: true, ...opts, env: { ...process.env, ...(opts.env || {}) }, }); child.on('close', (code) => { if (code === 0) resolve(); else reject(new Error(`${cmd} ${args.join(' ')} 退出码: ${code}`)); }); child.on('error', reject); }); } function runScript(name, ...args) { const scriptPath = join(ROOT, 'scripts', `${name}.mjs`); return run('node', [scriptPath, ...args]); } function step(title) { console.log(`\n${'='.repeat(60)}`); console.log(` ${title}`); console.log(`${'='.repeat(60)}\n`); } // ---------- 主流程 ---------- async function main() { const electronBuilderPlatform = opts.win ? '--win' : opts.mac ? '--mac' : '--linux'; const editionEnv = { EDITION: edition }; console.log('┌──────────────────────────────────────┐'); console.log(`│ HeiXiu 构建 v${version} ${platform}/${edition}`.padEnd(45) + '│'); console.log('└──────────────────────────────────────┘'); console.log(`操作: ${[ opts.clean && '清理', opts.build && '构建', opts.sign && '签名', opts.upload && '上传', opts.publish && '发布', ].filter(Boolean).join(' → ')}`); // ── Step 1: 清理 ── if (opts.clean) { step('1/ 清理构建产物'); await runScript('clean'); } // ── Step 2: 构建 ── if (opts.build) { step('2/ 构建应用'); // 签名环境变量(electron-builder 原生支持 CSC_LINK / CSC_KEY_PASSWORD) const signEnv = {}; if (opts.sign) { if (opts.cert) signEnv.CSC_LINK = opts.cert; if (opts.certPassword) signEnv.CSC_KEY_PASSWORD = opts.certPassword; console.log(opts.cert ? `签名证书: ${opts.cert}` : '签名: 使用系统证书存储'); } // 2a. TypeScript 编译 console.log('[tsc] 类型检查...'); await run('npx', ['tsc'], { env: editionEnv }); // 2b. Vite 构建 console.log('[vite] 打包前端 + Electron...'); await run('npx', ['vite', 'build'], { env: editionEnv }); // 2c. electron-builder 打包 console.log(`[electron-builder] ${electronBuilderPlatform} 打包...`); await run('npx', ['electron-builder', electronBuilderPlatform], { env: { ...editionEnv, ...signEnv }, }); // 2d. 生成 update-info.json console.log('[info] 生成 update-info.json...'); await runScript('generate-update-info', platform, edition, version); } // ── Step 3: 上传 OSS ── if (opts.upload) { step('3/ 上传 OSS'); await runScript('upload-oss', platform, edition, version); } // ── Step 4: 发布到 API ── if (opts.publish) { step('4/ 发布版本到 API'); await runScript('publish-release', platform, edition, version); } console.log('\n✅ 全部完成'); } function printHelp() { const W = 62; const box = (title) => { const pad = Math.max(0, W - 4 - title.length); console.log(`\n${cyan('┌')}${cyan('─'.repeat(W - 2))}${cyan('┐')}`); console.log(`${cyan('│')} ${bold(title)}${' '.repeat(pad)}${cyan('│')}`); console.log(`${cyan('└')}${cyan('─'.repeat(W - 2))}${cyan('┘')}`); }; const row = (flag, desc) => { console.log(` ${green(flag.padEnd(22))}${dim(desc)}`); }; const hdr = (t) => console.log(`\n ${bold(yellow(t))}`); console.log(''); console.log(cyan(' ╔') + cyan('═══════════════════════════════════════════════════') + cyan('╗')); console.log(cyan(' ║') + bold(' 船长·HeiXiu — 构建工具') + ' ' + cyan('║')); console.log(cyan(' ╚') + cyan('═══════════════════════════════════════════════════') + cyan('╝')); // 用法 box('用法'); console.log(` ${bold('node build.mjs')} ${dim('<平台> [版本] [操作...]')}`); console.log(` ${bold('npm run build:win')} ${dim('— 等效快捷命令')}`); // 平台 hdr('平台(必选其一)'); row('--win', 'Windows'); row('--mac', 'macOS'); row('--linux', 'Linux'); // 版本 hdr('版本(可选,默认 --personal)'); row('--personal', '个人版'); row('--enterprise', '企业版'); // 操作 hdr('操作(可选组合,不指定则默认 --build)'); row('--build', '构建应用(tsc + vite + electron-builder + info)'); row('--clean', '构建前清理 release/ dist/ dist-electron/'); row('--sign', '代码签名(需配合 --cert 或环境变量)'); row('--upload', '上传安装包到 OSS'); row('--publish', '发布版本信息到后端 API'); row('--all', '全流程 = clean + build + sign + upload + publish'); // 签名 hdr('签名参数(配合 --sign)'); row('--cert ', '证书文件路径(.pfx / .p12)'); row('--cert-password

', '证书密码'); console.log(` ${dim('也可通过 CSC_LINK / CSC_KEY_PASSWORD 环境变量设置')}`); // 示例 box('示例'); console.log(` ${dim('# 日常开发')}`); console.log(` ${bold('node build.mjs --win --personal')}`); console.log(` ${bold('npm run build:win')} ${dim('— 等效')}`); console.log(''); console.log(` ${dim('# 打包 + 上传 OSS + 发布到 API')}`); console.log(` ${bold('node build.mjs --win --personal --upload --publish')}`); console.log(''); console.log(` ${dim('# 全流程(清理 → 构建 → 签名 → 上传 → 发布)')}`); console.log(` ${bold('node build.mjs --win --enterprise --all --cert ./cert.pfx --cert-password xxx')}`); console.log(''); console.log(` ${dim('# 仅清理')}`); console.log(` ${bold('npm run build:clean')}`); // 环境变量 box('环境变量'); console.log(` ${yellow('UPDATE_API_URL')} ${dim('版本检查 API 基地址')}`); console.log(` ${yellow('UPDATE_API_KEY')} ${dim('发布 API 鉴权 Key')}`); console.log(` ${yellow('UPDATE_DOWNLOAD_BASE')} ${dim('OSS 下载基地址(预判 URL 用)')}`); console.log(` ${yellow('OSS_ENDPOINT')} ${dim('阿里云 OSS endpoint')}`); console.log(` ${yellow('OSS_BUCKET')} ${dim('阿里云 OSS bucket 名称')}`); console.log(` ${yellow('OSS_ACCESS_KEY_ID')} ${dim('阿里云 AccessKey ID')}`); console.log(` ${yellow('OSS_ACCESS_KEY_SECRET')} ${dim('阿里云 AccessKey Secret')}`); console.log(` ${yellow('CSC_LINK')} ${dim('签名证书路径(与 --cert 等效)')}`); console.log(` ${yellow('CSC_KEY_PASSWORD')} ${dim('签名证书密码(与 --cert-password 等效)')}`); // 快捷命令 box('npm 快捷命令'); console.log(` ${bold('npm run build:win')} ${dim('→ node build.mjs --win --personal --build')}`); console.log(` ${bold('npm run build:win:enterprise')} ${dim('→ node build.mjs --win --enterprise --build')}`); console.log(` ${bold('npm run build:mac')} ${dim('→ node build.mjs --mac --personal --build')}`); console.log(` ${bold('npm run build:linux')} ${dim('→ node build.mjs --linux --personal --build')}`); console.log(` ${bold('npm run build:clean')} ${dim('→ node scripts/clean.mjs')}`); console.log(` ${bold('npm run upload:oss')} ${dim('→ node scripts/upload-oss.mjs')}`); console.log(` ${bold('npm run publish:release')} ${dim('→ node scripts/publish-release.mjs')}`); console.log(''); } // ---------- 入口 ---------- main().catch((err) => { console.error(`\n❌ 构建失败: ${err.message}`); process.exit(1); });