fix(ci): 版本校验在浅克隆下必然误报
构建客户端 / 检查(类型 + 单元测试) (push) Waiting to run
构建客户端 / Linux x64(deb + AppImage) (push) Blocked by required conditions
构建客户端 / Windows x64(NSIS 安装包) (push) Blocked by required conditions
构建客户端 / macOS 通用(.app + .dmg) (push) Blocked by required conditions

- fix(ci): checkout 改成 fetch-depth: 0——没有标签、提交数恒为 1 时,
  version.mjs --check 算出来的版本一定是 0.0.0+1,于是每次推送都红
- fix(version): CHANGELOG 按标签区间重算全部历史,不再只渲染当前版本,
  否则每次发布都会把上一版那一段覆盖掉
- fix(build): 固件包文件名用算出来的版本,不再用 BUILD-INFO.json 里那套旧编号
This commit is contained in:
2026-10-19 02:42:53 +08:00
parent 896b21fe44
commit d9a27b69f3
8 changed files with 195 additions and 134 deletions
+97 -23
View File
@@ -74,6 +74,56 @@ function git(args, { allowFail = false } = {}) {
}
}
/** 所有 `v*` 标签,按版本号从旧到新。 */
function allTags() {
const raw = git(["tag", "--list", "v*", "--sort=version:refname"], { allowFail: true });
if (!raw) return [];
return raw.split("\n").map((line) => line.trim()).filter(Boolean);
}
/** `from..to` 之间的提交;`from` 为 null 表示从头开始。 */
function commitsBetween(from, to) {
const range = from ? `${from}..${to}` : to;
const raw = git(["log", range, "--no-merges", "--pretty=format:%H%x1f%s%x1f%b%x1e"], {
allowFail: true,
});
if (!raw) return [];
return raw
.split("\x1e")
.map((chunk) => chunk.trim())
.filter(Boolean)
.map((chunk) => {
const [hash, subject, body] = chunk.split("\x1f");
return { hash, subject: subject ?? "", body: body ?? "" };
});
}
/**
* 逐个标签还原历史发布。
*
* 改动日志必须是**可重复计算**的:同一次 `--write` 无论跑多少遍,输出都要一样。
* 所以旧版本不从文件里读回来,而是按标签区间重新算一遍——文件只是它的投影。
*/
function history() {
const tags = allTags();
const releases = [];
let previous = null;
for (const tag of tags) {
const commits = commitsBetween(previous, tag);
releases.push({
tag,
title: tag,
date: git(["log", "-1", "--pretty=format:%cs", tag], { allowFail: true }),
commits,
entries: commits.flatMap(entriesOf),
});
previous = tag;
}
return releases;
}
/** 最近的一个 `v*` 标签;没有标签时返回 null(第一次发布)。 */
function lastTag() {
const tag = git(["describe", "--tags", "--abbrev=0", "--match", "v[0-9]*"], {
@@ -178,6 +228,8 @@ function compute() {
base,
commits,
entries,
// 历史发布按标签区间重新算,渲染时用。
releases: history(),
version,
build,
dirty: git(["status", "--porcelain"]).length > 0,
@@ -222,36 +274,24 @@ function groupEntries(entries) {
.sort((a, b) => TYPE_ORDER.indexOf(a[0]) - TYPE_ORDER.indexOf(b[0]));
}
function renderChangelog(meta, result) {
/** 一节的正文:标题、统计行、按类型分组。 */
function renderSection(title, date, meta, commits, entries, span) {
const lines = [];
lines.push("# 改动记录");
lines.push("");
lines.push(
"> 本文件由 `scripts/version.mjs` 依据 git 历史生成,**请勿手改**。",
);
lines.push("> 提交信息遵循 Conventional Commits,一条一个条目;要求见该脚本的文件头。");
lines.push("> 建立这套机制之前的人工整理记录保存在 [`docs/CHANGELOG-legacy.md`](docs/CHANGELOG-legacy.md)。");
lines.push(`## ${title} — ${date}`);
lines.push("");
if (result.entries.length === 0) {
lines.push(`## v${meta.version} — ${meta.date}`);
if (entries.length === 0) {
lines.push("(这一版没有面向使用者的改动。)");
lines.push("");
lines.push("(自上一个发布以来没有面向使用者的改动。)");
lines.push("");
return lines.join("\n");
return lines;
}
const span = result.tag ? `${result.tag} → v${meta.version}` : `初始 → v${meta.version}`;
lines.push(`## v${meta.version} — ${meta.date}`);
lines.push("");
lines.push(
`构建号 **${meta.build}** · 线路协议 **${meta.protocol}** · ` +
`${result.commits.length} 个提交(${span})`,
);
const stat = [`${commits.length} 个提交${span ? `(${span})` : ""}`];
if (meta) stat.unshift(`构建号 **${meta.build}**`, `线路协议 **${meta.protocol}**`);
lines.push(stat.join(" · "));
lines.push("");
for (const [type, list] of groupEntries(result.entries)) {
for (const [type, list] of groupEntries(entries)) {
const heading = GROUPS.find(([key]) => key === type)?.[1] ?? "其它";
lines.push(`### ${heading}`);
lines.push("");
@@ -263,6 +303,40 @@ function renderChangelog(meta, result) {
lines.push("");
}
return lines;
}
/**
* 整份改动日志:当前这次要发布的 + 所有历史标签。
*
* 历史部分是从标签区间**重新算**出来的,不是把上一版的文件内容搬过来。这样跑多少遍
* `--write` 结果都一样,也不会因为某次忘了提交生成物就把历史吃掉。
*/
function renderChangelog(meta, result, releases) {
const lines = [];
lines.push("# 改动记录");
lines.push("");
lines.push("> 本文件由 `scripts/version.mjs` 依据 git 历史生成,**请勿手改**。");
lines.push("> 提交信息遵循 Conventional Commits,一条一个条目;要求见该脚本的文件头。");
lines.push(
"> 建立这套机制之前的人工整理记录保存在 [`docs/CHANGELOG-legacy.md`](docs/CHANGELOG-legacy.md)。",
);
lines.push("");
// HEAD 正好在最新标签上时(刚发布完),当前这一节没有内容,而同一个版本会在下面的
// 历史里出现一次。跳过它,免得同一版出现两个标题。
const hasCurrent = result.entries.length > 0 || releases.length === 0;
if (hasCurrent) {
const span = result.tag ? `${result.tag} → v${meta.version}` : `初始 → v${meta.version}`;
lines.push(
...renderSection(`v${meta.version}`, meta.date, meta, result.commits, result.entries, span),
);
}
for (const release of [...releases].reverse()) {
lines.push(...renderSection(release.title, release.date, null, release.commits, release.entries, ""));
}
return lines.join("\n");
}
@@ -358,7 +432,7 @@ function write(meta, result) {
2,
) + "\n",
);
put("CHANGELOG.md", renderChangelog(meta, result));
put('CHANGELOG.md', renderChangelog(meta, result, result.releases));
put("CHANGELOG.json", renderJson(meta, result));
put("desktop/src/lib/version.ts", renderTs(meta));