Files
ele-HeiXiu/scripts/generate-update-info.mjs
YoungestSongMo 767bb8b297 feat: 初始化船长·HeiXiu 桌面应用项目(错误是svg的问题)
Electron + React 19 + TypeScript + Ant Design 6 + Tailwind CSS 4

  【核心架构】
  - Electron 主进程(窗口管理、平台检测、图标适配)
  - Vite 构建链(双产物 dist/ + dist-electron/)
  - 共享层 shared/(类型、常量、工具函数)
  - IPC 通信框架 + 安全守卫

  【主题系统】
  - 蓝紫渐变主题(亮色/暗色双模式)
  - Ant Design ConfigProvider 适配(lightAlgorithm / darkAlgorithm)
  - Tailwind CSS 4 @theme 指令 + CSS 变量
  - 设计令牌三处同步(tokens.ts / globals.css / antd-theme.ts)

  【导航栏】
  - 自定义 H5 导航栏替代系统菜单
  - 个人版/企业版双布局
  - 未登录拦截 + 事件总线驱动登录弹窗

  【登录注册】
  - Modal 弹层(Portal)+ Tabs 切换
  - 登录/注册表单动态字段渲染
  - 记住密码 / 自动登录 / 用户协议

  【更新系统】
  - 自定义 API 版本检查(替代 electron-updater generic provider)
  - 手动下载 + SHA512 校验 + spawn NSIS 安装
  - 渲染进程更新状态机 Hook(useUpdater)
  - 检查更新按钮 + 下载进度展示

  【构建工具链】
  - build.mjs 构建总管(--win/--mac/--linux --personal/--enterprise --build/--clean/--sign/--upload/--publish)
  - scripts/ 自动化脚本(clean / generate-update-info / upload-oss / publish-release)
  - electron-builder NSIS 安装器(可选路径、多版本打包)
  - update-info.json 自动生成(fileSize/sha512/changelog/releaseDate)

  【代码质量】
  - ESLint + TypeScript strict + Prettier
  - husky + lint-staged(提交前自动检查)
  - commitlint(规范提交信息)
  - Playwright E2E + Vitest 单元测试框架
  - rollup-plugin-visualizer 打包体积分析

  【文档】
  - README.md(快速开始 + 目录结构 + 命令速查)
  - UpdateA.md(发布手册:API/签名/发版流程/数据库)
  - CHANGELOG.md(更新日志)
  - .env.example(构建环境变量模板)
2026-06-03 15:00:51 +08:00

171 lines
5.6 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> <edition> [version] [--url <url>]
//
// 两种用法:
// 方式 A预判 URL打包后自动拼接 URL → 适用于 OSS 路径可预测时
// node scripts/generate-update-info.mjs win32 personal
//
// 方式 B指定 URL上传后用实际 URL 重新生成 → 适用于上传后才知道 URL 时
// node scripts/generate-update-info.mjs win32 personal 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 参数
let urlOverride = '';
const positional = [];
for (let i = 0; i < args.length; i++) {
if (args[i] === '--url' && args[i + 1]) {
urlOverride = args[++i];
} else if (!args[i].startsWith('--')) {
positional.push(args[i]);
}
}
const platform = positional[0];
const edition = positional[1];
if (!platform || !edition) {
console.error('用法: node scripts/generate-update-info.mjs <platform> <edition> [version] [--url <url>]');
console.error('示例: node scripts/generate-update-info.mjs win32 personal');
console.error(' node scripts/generate-update-info.mjs win32 personal 0.1.0 --url "https://..."');
process.exit(1);
}
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
const version = positional[2] || 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)}...`);
// ---------- 构建下载 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,
// --- 附加:方便后端录入 ---
_meta: {
platform,
edition,
fileName,
generatedAt: new Date().toISOString(),
},
};
const outputPath = join(
releaseDir,
`${productName}-${platform}-${edition}-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));