Files
ele-HeiXiu/scripts/generate-update-info.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

207 lines
6.8 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.
// ============================================================
// 打包后自动生成 update-info.json
//
// 用法:
// node scripts/generate-update-info.mjs <platform> [version] [--url <url>]
//
// 两种用法:
// 方式 A预判 URL打包后自动拼接 URL → 适用于 OSS 路径可预测时
// node scripts/generate-update-info.mjs win32
//
// 方式 B指定 URL上传后用实际 URL 重新生成 → 适用于上传后才知道 URL 时
// node scripts/generate-update-info.mjs win32 0.1.0 --url "https://oss.../xxx.exe"
//
// 环境变量(可选):
// UPDATE_DOWNLOAD_BASE — 方式 A 的 URL 基地址,默认 https://heixiu.oss-cn-hangzhou.aliyuncs.com/releases
// UPDATE_FORCE — 是否强制更新,默认 false
// UPDATE_MIN_VERSION — 最低兼容版本,默认 0.0.0
// UPDATE_CHANGELOG — 覆盖 CHANGELOG.md 中的日志内容
// ============================================================
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
// ---------- 参数解析 ----------
const args = process.argv.slice(2);
// 提取 --url 和 --blockmap-url 参数
let urlOverride = '';
let blockmapUrlOverride = '';
const positional = [];
for (let i = 0; i < args.length; i++) {
if (args[i] === '--url' && args[i + 1]) {
urlOverride = args[++i];
} else if (args[i] === '--blockmap-url' && args[i + 1]) {
blockmapUrlOverride = args[++i];
} else if (!args[i].startsWith('--')) {
positional.push(args[i]);
}
}
const platform = positional[0];
if (!platform) {
console.error('用法: node scripts/generate-update-info.mjs <platform> [version] [--url <url>]');
console.error('示例: node scripts/generate-update-info.mjs win32');
console.error(' node scripts/generate-update-info.mjs win32 0.1.0 --url "https://..."');
process.exit(1);
}
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
const version = positional[1] || pkg.version;
const productName = pkg.build?.productName || '船长·HeiXiu';
// ---------- 查找安装包 ----------
const releaseDir = join(ROOT, 'release', version);
let installerPath;
try {
const files = readdirSync(releaseDir);
// 按扩展名找安装包
const exts = platform === 'win32' ? ['.exe'] : platform === 'darwin' ? ['.dmg'] : ['.AppImage'];
const match = files.find((f) => exts.some((ext) => f.endsWith(ext)));
if (!match) {
console.error(`未在 ${releaseDir} 中找到安装包文件`);
process.exit(1);
}
installerPath = join(releaseDir, match);
console.log(`找到安装包: ${match}`);
} catch {
console.error(`目录不存在: ${releaseDir}\n请先执行构建(如 npm run build:win`);
process.exit(1);
}
// ---------- 计算文件信息 ----------
const fileBuffer = readFileSync(installerPath);
const stats = statSync(installerPath);
// SHA512Base64 编码,与 certutil 输出一致)
const sha512 = createHash('sha512').update(fileBuffer).digest('base64').replace(/\r?\n/g, '');
const fileSize = stats.size;
console.log(`文件大小: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`SHA512: ${sha512.slice(0, 32)}...`);
// ---------- 查找 blockmap 文件 ----------
let blockMapUrl;
let blockMapSize;
try {
const blockmapFile = readdirSync(releaseDir).find((f) => f.endsWith('.blockmap'));
if (blockmapFile) {
const blockmapPath = join(releaseDir, blockmapFile);
const blockmapStats = statSync(blockmapPath);
blockMapSize = blockmapStats.size;
if (blockmapUrlOverride) {
// 方式 B使用指定的 blockmap URL
blockMapUrl = blockmapUrlOverride;
} else {
// 方式 A根据基地址 + 版本号 + 文件名拼接
const downloadBase = process.env.UPDATE_DOWNLOAD_BASE || 'https://heixiu.oss-cn-hangzhou.aliyuncs.com/releases';
blockMapUrl = `${downloadBase.replace(/\/+$/, '')}/${version}/${encodeURI(blockmapFile)}`;
}
console.log(`Blockmap: ${blockmapFile} (${(blockMapSize / 1024).toFixed(2)} KB)`);
console.log(`Blockmap URL: ${blockMapUrl}`);
}
} catch {
// blockmap 不存在不影响主流程
blockMapUrl = undefined;
blockMapSize = undefined;
}
// ---------- 构建下载 URL ----------
const fileName = installerPath.split(/[/\\]/).pop();
let downloadUrl;
if (urlOverride) {
// 方式 B使用指定的 URL
downloadUrl = urlOverride;
console.log(`下载 URL手动指定: ${downloadUrl}`);
} else {
// 方式 A根据基地址 + 版本号 + 文件名拼接(预判 URL
const downloadBase = process.env.UPDATE_DOWNLOAD_BASE || 'https://heixiu.oss-cn-hangzhou.aliyuncs.com/releases';
downloadUrl = `${downloadBase.replace(/\/+$/, '')}/${version}/${encodeURI(fileName)}`;
console.log(`下载 URL自动拼接: ${downloadUrl}`);
}
// ---------- 读取更新日志 ----------
let changelog;
if (process.env.UPDATE_CHANGELOG) {
// 环境变量覆盖(适用于 CI/CD 流水线)
changelog = process.env.UPDATE_CHANGELOG;
} else {
// 从 CHANGELOG.md 提取最新版本的日志
const changelogPath = join(ROOT, 'CHANGELOG.md');
try {
const changelogContent = readFileSync(changelogPath, 'utf-8');
// 匹配最新版本块:## x.y.z日期 ... ## 或 EOF
const versionSection = changelogContent.match(
/^##\s+(\d+\.\d+\.\d+)[^\n]*\n([\s\S]*?)(?=^##\s|\Z)/m,
);
if (versionSection) {
changelog = versionSection[0].trim();
} else {
changelog = `## ${version}\n\n更新内容待补充`;
}
console.log(`更新日志: 已从 CHANGELOG.md 提取`);
} catch {
changelog = `## ${version}\n\n更新内容待补充`;
}
}
// ---------- 其他配置 ----------
const forceUpdate = process.env.UPDATE_FORCE === 'true';
const minimumVersion = process.env.UPDATE_MIN_VERSION || '0.0.0';
const releaseDate = new Date().toISOString().slice(0, 10);
// ---------- 生成 JSON ----------
const updateInfo = {
// --- 必填API 返回字段 ---
hasUpdate: true,
latestVersion: version,
downloadUrl,
fileSize,
sha512,
changelog,
releaseDate,
forceUpdate,
minimumVersion,
// --- 增量更新 ---
...(blockMapUrl && { blockMapUrl }),
...(blockMapSize != null && { blockMapSize }),
// --- 附加:方便后端录入 ---
_meta: {
platform,
fileName,
blockmap: blockMapUrl ? { url: blockMapUrl, size: blockMapSize } : null,
generatedAt: new Date().toISOString(),
},
};
const outputPath = join(
releaseDir,
`${productName}-${platform}-v${version}-update-info.json`,
);
writeFileSync(outputPath, JSON.stringify(updateInfo, null, 2), 'utf-8');
console.log(`\n✅ 已生成: ${outputPath}`);
console.log(JSON.stringify(updateInfo, null, 2));