Files
ele-HeiXiu/build/scripts/publish-release.mjs
YoungestSongMo 337d6c1205 feat: 构建系统重构 + VCHSM 架构迁移 + 右键菜单修复 + 尺寸参数统一
## 构建、打包与分发系统重构 (build/)

- 构建系统收敛至 build/ 目录:build.mjs + electron-builder.js + 6 个脚本

- electron-builder 配置从 package.json 提取为 build/electron-builder.js

- 新增 --edition (personal/enterprise) 和 --channel (stable/beta/alpha) 参数

- OSS 路径按渠道隔离

- 新增 pre-build-check.mjs:Git/CHANGELOG/版本/环境变量校验

- 新增 bump-version.mjs:版本号管理 + CHANGELOG 自动生成

- updater.ts 发送 channel/edition/deviceFingerprint

- 新增 src/shared/utils/rollout.ts 灰度测试工具

- 新增 10 条 NPM 脚本

## VCHSM 五层架构迁移

- 7 个业务模块 + ~500 处导入路径同步

## 修复

- MediaCardContextMenu 改用共享 ContextMenu 组件

- ComboBox 尺寸参数显示统一 (size-format.ts)

## 文档

- UpdateA.md / build/README.md / README.md / .env.example 更新

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 18:41:07 +08:00

150 lines
4.8 KiB
JavaScript
Raw Permalink 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.
// ============================================================
// 发布版本信息到后端 API
//
// 用法(由 build.mjs 调用):
// node build/scripts/publish-release.mjs <platform> <version> <channel> <edition> <ossPrefix> <releaseDirName>
//
// 也支持手动调用:
// node build/scripts/publish-release.mjs <platform> <version> [channel] [edition] [ossPrefix] [--url <url>] [--json <json>]
//
// 环境变量:
// UPDATE_API_URL — API 基地址(默认 https://www.heixiu.com
// UPDATE_API_KEY — API 鉴权 Key通过 Authorization: Bearer 头传递)
// ============================================================
import { readFileSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import axios from 'axios';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..', '..');
// ---------- 参数 ----------
const args = process.argv.slice(2);
const positional = [];
let urlOverride = '';
let jsonOverride = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--url' && args[i + 1]) {
urlOverride = args[++i];
} else if (args[i] === '--json' && args[i + 1]) {
jsonOverride = args[++i];
} else if (!args[i].startsWith('--')) {
positional.push(args[i]);
}
}
const platform = positional[0];
const version = positional[1];
const channel = positional[2] || 'stable';
const edition = positional[3] || 'personal';
const ossPrefix = positional[4] || `releases/${version}`;
const releaseDirName = positional[5] || version;
if (!platform || !version) {
console.error('用法: node build/scripts/publish-release.mjs <platform> <version> [channel] [edition] [ossPrefix] [releaseDirName]');
process.exit(1);
}
// ---------- 读取 update-info.json ----------
let updateInfo;
if (jsonOverride) {
try {
updateInfo = JSON.parse(jsonOverride);
} catch {
console.error('错误: --json 参数不是有效的 JSON');
process.exit(1);
}
} else {
const releaseDir = join(ROOT, 'release', releaseDirName);
let infoFile;
try {
const all = readdirSync(releaseDir);
infoFile = all.find((f) =>
f.includes(platform) && f.endsWith('-update-info.json'),
);
if (!infoFile) {
console.error(`未找到 ${platform} 的 update-info.json${releaseDir} 中)`);
process.exit(1);
}
} catch (err) {
console.error(`错误: ${releaseDir}${err.message}`);
process.exit(1);
}
updateInfo = JSON.parse(readFileSync(join(releaseDir, infoFile), 'utf-8'));
if (urlOverride) updateInfo.downloadUrl = urlOverride;
}
// ---------- 发送到 API ----------
const apiBase = (process.env.UPDATE_API_URL || 'https://www.heixiu.com').replace(/\/+$/, '');
const apiKey = process.env.UPDATE_API_KEY || '';
const payload = {
platform,
channel,
edition,
latestVersion: updateInfo.latestVersion || version,
downloadUrl: updateInfo.downloadUrl,
fileSize: updateInfo.fileSize,
sha512: updateInfo.sha512,
changelog: updateInfo.changelog,
releaseDate: updateInfo.releaseDate,
forceUpdate: updateInfo.forceUpdate || false,
minimumVersion: updateInfo.minimumVersion || '0.0.0',
// 增量更新
...(updateInfo.blockMapUrl && { blockMapUrl: updateInfo.blockMapUrl }),
...(updateInfo.blockMapSize != null && { blockMapSize: updateInfo.blockMapSize }),
// 元数据
_meta: {
ossPrefix,
releaseDirName,
publishedAt: new Date().toISOString(),
},
};
const endpoint = `${apiBase}/api/v1/releases`;
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('📡 发布版本到 API');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(`POST ${endpoint}`);
console.log(`版本: ${version}`);
console.log(`平台: ${platform}`);
console.log(`渠道: ${channel}`);
console.log(`版本类型: ${edition}`);
console.log(`URL: ${payload.downloadUrl}\n`);
try {
const response = await axios.post(endpoint, payload, {
headers: {
'Content-Type': 'application/json',
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
},
timeout: 30000,
});
console.log('✅ 发布成功');
console.log(`响应: ${JSON.stringify(response.data, null, 2)}`);
} catch (err) {
const status = err.response?.status;
const data = err.response?.data;
if (status) {
console.error(`❌ 发布失败 HTTP ${status}: ${JSON.stringify(data)}`);
} else {
console.error(`❌ 请求失败: ${err.message}`);
}
console.log('\n💡 手动备选 — 把以下 JSON 发给后端:');
console.log(JSON.stringify(payload, null, 2));
process.exit(1);
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');