Files
ele-HeiXiu/build.mjs
YoungestSongMo 8b697e775d feat: 去除构建时版控变量 + 窗口标题动态版控 + 退出登录保留缓存 (0.0.24)
版控体系重构(13 文件):
- 移除 build.mjs --personal/--enterprise 参数和 EDITION 环境变量
- 统一安装包文件名:船长-HeiXiu-{os}-{版本号}-Setup.{ext}
- 简化 package.json 构建脚本,移除 :enterprise 变体
- scripts/*.mjs 不再接受 edition 参数
- 移除 shared/constants/app.ts 的 parseEdition()

窗口标题动态版控:
- 新格式:船长-HeiXiu-{os}-{版本号}(登录前)/ ·{版控} 后缀(登录后)
- 链路:AppProvider(account_type) → useEffect → appRuntime IPC → main process
- 移除 updater.ts 更新检查 API 的 edition 查询参数

本地存储生命周期修正:
- 退出登录不再自动清空缓存(user_id 隔离,重新登录命中缓存)
- clearUserStorage 添加 isOpen() 竞态守卫
- 移除 closeDatabase() 避免退出→重新登录后数据库不可用
- 手动「清理缓存」按钮保留

文档:
- CHANGELOG.md 新增 0.0.24 条目
- TODO.md 新增已完成项
2026-06-22 13:36:42 +08:00

301 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// build.mjs — 构建总管
//
// 用法node build.mjs [选项]
//
// 平台(必选其一):
// --win Windows
// --mac macOS
// --linux Linux
//
// 操作(可选组合,默认只执行 --build
// --build 仅构建(默认)
// --clean 构建前清理 release/ dist/ dist-electron/
// --sign 代码签名
// --upload 上传安装包到 OSS
// --publish 发布版本信息到后端 API
// --all 全流程clean + build + sign + upload + publish
//
// 签名(仅 --sign 时可用):
// --cert <path> 证书文件路径 (.pfx)
// --cert-password <p> 证书密码
//
// 示例:
// node build.mjs --win # 仅打包
// node build.mjs --win --upload --publish # 打包 + 上传 + 发布
// node build.mjs --win --all # 全流程
// node build.mjs --win --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,
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 '--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);
}
// 默认执行 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 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';
console.log('┌──────────────────────────────────────┐');
console.log(`│ HeiXiu 构建 v${version} ${platform}`.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']);
// 2b. Vite 构建
console.log('[vite] 打包前端 + Electron...');
await run('npx', ['vite', 'build']);
// 2c. electron-builder 打包
console.log(`[electron-builder] ${electronBuilderPlatform} 打包...`);
await run('npx', ['electron-builder', electronBuilderPlatform], {
env: { ...signEnv },
});
// 2d. 生成 update-info.json
console.log('[info] 生成 update-info.json...');
await runScript('generate-update-info', platform, version);
}
// ── Step 3: 上传 OSS ──
if (opts.upload) {
step('3/ 上传 OSS');
await runScript('upload-oss', platform, version);
}
// ── Step 4: 发布到 API ──
if (opts.publish) {
step('4/ 发布版本到 API');
await runScript('publish-release', platform, 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('操作(可选组合,不指定则默认 --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 <path>', '证书文件路径(.pfx / .p12');
row('--cert-password <p>', '证书密码');
console.log(` ${dim('也可通过 CSC_LINK / CSC_KEY_PASSWORD 环境变量设置')}`);
// 示例
box('示例');
console.log(` ${dim('# 日常构建')}`);
console.log(` ${bold('node build.mjs --win')}`);
console.log(` ${bold('npm run build:win')} ${dim('— 等效')}`);
console.log('');
console.log(` ${dim('# 打包 + 上传 OSS + 发布到 API')}`);
console.log(` ${bold('node build.mjs --win --upload --publish')}`);
console.log('');
console.log(` ${dim('# 全流程(清理 → 构建 → 签名 → 上传 → 发布)')}`);
console.log(` ${bold('node build.mjs --win --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 --build')}`);
console.log(` ${bold('npm run build:mac')} ${dim('→ node build.mjs --mac --build')}`);
console.log(` ${bold('npm run build:linux')} ${dim('→ node build.mjs --linux --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);
});