Skip to content

Commit 08a00c5

Browse files
imsyyclaude
andcommitted
👷 ci: 重写发布流程,改用 gh CLI 并合并多架构更新清单
弃用有重复草稿/资源竞态的 softprops/action-gh-release,移除 continue-on-error 防止失败被吞,新增 concurrency 串行化,并用脚本合并 Win/Mac 的 latest*.yml、修复 latest-linux-arm64.yml 缺失。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent de7f0c2 commit 08a00c5

2 files changed

Lines changed: 139 additions & 25 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env node
2+
/**
3+
* 整理 GitHub Release 待发布资源(CommonJS,便于用 NODE_PATH 引入隔离安装的 js-yaml)
4+
*
5+
* 背景:各平台/架构在独立 job 构建并各自上传 artifact。electron-builder 会为
6+
* Windows / macOS 生成同名的 `latest.yml` / `latest-mac.yml`(分别只含本架构条目),
7+
* 直接上传会互相覆盖,导致另一架构无法自动更新。Linux 则使用按架构区分的
8+
* `latest-linux.yml` / `latest-linux-arm64.yml`,天然不冲突。
9+
*
10+
* 本脚本将所有 artifact 扁平化到输出目录:
11+
* - 合并 Windows / macOS 的 latest*.yml(合并 files 列表,path/sha512 优先指向 x64)
12+
* - 保留各架构 Linux 清单原样
13+
* - 剔除调试文件 builder-debug.yml
14+
* - 同名二进制去重(保留首个),避免 release 资源重名冲突
15+
*
16+
* 用法: node prepare-release-assets.cjs <artifactsDir> <outDir>
17+
*/
18+
"use strict";
19+
20+
const fs = require("fs");
21+
const path = require("path");
22+
const yaml = require("js-yaml");
23+
24+
const srcDir = process.argv[2];
25+
const outDir = process.argv[3];
26+
27+
if (!srcDir || !outDir) {
28+
console.error("用法: node prepare-release-assets.cjs <artifactsDir> <outDir>");
29+
process.exit(1);
30+
}
31+
32+
/** 需要跨架构合并的更新清单 */
33+
const MERGE_MANIFESTS = new Set(["latest.yml", "latest-mac.yml"]);
34+
/** 不需要上传的文件 */
35+
const SKIP_FILES = new Set(["builder-debug.yml"]);
36+
37+
/** 递归收集文件(跳过 *-unpacked 目录) */
38+
function walk(dir, out = []) {
39+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
40+
const full = path.join(dir, entry.name);
41+
if (entry.isDirectory()) {
42+
if (entry.name.endsWith("-unpacked")) continue;
43+
walk(full, out);
44+
} else {
45+
out.push(full);
46+
}
47+
}
48+
return out;
49+
}
50+
51+
fs.mkdirSync(outDir, { recursive: true });
52+
53+
/** name -> 解析后的清单文档数组 */
54+
const manifestDocs = new Map();
55+
/** 已写入输出目录的文件名 */
56+
const seen = new Set();
57+
58+
for (const file of walk(srcDir)) {
59+
const base = path.basename(file);
60+
if (SKIP_FILES.has(base)) continue;
61+
62+
if (MERGE_MANIFESTS.has(base)) {
63+
const doc = yaml.load(fs.readFileSync(file, "utf8"));
64+
if (!manifestDocs.has(base)) manifestDocs.set(base, []);
65+
manifestDocs.get(base).push(doc);
66+
continue;
67+
}
68+
69+
if (seen.has(base)) {
70+
console.warn(`⚠️ 跳过重复同名文件: ${base}`);
71+
continue;
72+
}
73+
seen.add(base);
74+
fs.copyFileSync(file, path.join(outDir, base));
75+
}
76+
77+
for (const [name, docs] of manifestDocs) {
78+
let merged;
79+
if (docs.length === 1) {
80+
merged = docs[0];
81+
} else {
82+
merged = { ...docs[0] };
83+
const byUrl = new Map();
84+
for (const doc of docs) {
85+
for (const f of doc.files || []) {
86+
if (!byUrl.has(f.url)) byUrl.set(f.url, f);
87+
}
88+
}
89+
merged.files = [...byUrl.values()];
90+
// 基准 path/sha512 优先指向 x64(多数用户);electron-updater v6 仍会按架构从 files 中匹配
91+
const x64 = merged.files.find((f) => /x64|x86_64/i.test(f.url));
92+
if (x64) {
93+
merged.path = x64.url;
94+
merged.sha512 = x64.sha512;
95+
}
96+
}
97+
fs.writeFileSync(path.join(outDir, name), yaml.dump(merged, { lineWidth: -1 }));
98+
console.log(`✅ 合并清单 ${name}(files: ${(merged.files || []).length})`);
99+
}
100+
101+
console.log(`release-assets 准备完成,共 ${fs.readdirSync(outDir).length} 个文件`);

.github/workflows/release.yml

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ on:
99
env:
1010
NODE_VERSION: 22.x
1111

12+
# 同一个 tag 的发布串行执行,避免并发上传资源产生竞态
13+
concurrency:
14+
group: release-${{ github.ref }}
15+
cancel-in-progress: false
16+
1217
jobs:
1318
# ===================================================================
1419
# 并行构建所有平台和架构
@@ -257,32 +262,40 @@ jobs:
257262
git commit -m "docs: 更新变更日志 $GITHUB_REF_NAME [skip ci]" || echo "No changes to commit"
258263
git push origin HEAD:main || echo "Failed to push to main. This might happen if tag is not on main."
259264
265+
- name: Setup Node.js
266+
uses: actions/setup-node@v6
267+
with:
268+
node-version: ${{ env.NODE_VERSION }}
269+
260270
- name: Download all artifacts
261271
uses: actions/download-artifact@v8
262272
with:
263273
path: artifacts
264-
# 创建 GitHub Release 并上传所有产物
265-
- name: Create GitHub Release and Upload Assets
266-
uses: softprops/action-gh-release@v2
267-
continue-on-error: true
268-
with:
269-
token: ${{ secrets.GITHUB_TOKEN }}
270-
body_path: release_notes.md
271-
generate_release_notes: true
272-
draft: false
273-
# 发布为预发布
274-
prerelease: false
275-
# 全部上传
276-
files: |
277-
!artifacts/**/*-unpacked/**
278-
artifacts/**/*.exe
279-
artifacts/**/*.dmg
280-
artifacts/**/*.zip
281-
artifacts/**/*.AppImage
282-
artifacts/**/*.deb
283-
artifacts/**/*.rpm
284-
artifacts/**/*.pacman
285-
artifacts/**/*.snap
286-
artifacts/**/*.tar.gz
287-
artifacts/**/*.yml
288-
artifacts/**/*.blockmap
274+
275+
# 整理待发布资源:合并多架构 Win/Mac 更新清单、保留各架构 Linux 清单、剔除调试文件
276+
- name: Prepare release assets
277+
shell: bash
278+
run: |
279+
npm install --no-save --prefix "$RUNNER_TEMP/jsyaml" js-yaml@4.1.0
280+
NODE_PATH="$RUNNER_TEMP/jsyaml/node_modules" \
281+
node .github/scripts/prepare-release-assets.cjs artifacts release-assets
282+
echo "待发布资源:"
283+
ls -1 release-assets
284+
285+
# 用官方 gh CLI 确定性创建并发布 Release(替代有重复草稿/竞态问题的第三方 action)
286+
- name: Create GitHub Release
287+
env:
288+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
289+
shell: bash
290+
run: |
291+
TAG="${GITHUB_REF#refs/tags/}"
292+
# 重跑场景:若同名 release 已存在则先删除,避免脏状态(保留 tag)
293+
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
294+
gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" --yes
295+
fi
296+
gh release create "$TAG" \
297+
--repo "$GITHUB_REPOSITORY" \
298+
--title "$TAG" \
299+
--notes-file release_notes.md \
300+
--verify-tag \
301+
release-assets/*

0 commit comments

Comments
 (0)