fix(ci): 版本校验在浅克隆下必然误报
- 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:
+97
-23
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user