Skip to content

Commit 50415b3

Browse files
committed
ci: 用提交记录生成 release notes,并修复更新按钮主题色
1 parent b70e7f3 commit 50415b3

3 files changed

Lines changed: 172 additions & 8 deletions

File tree

.github/workflows/release.yml

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jobs:
1414
steps:
1515
- name: Checkout
1616
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
1719

1820
- name: Derive version from tag
1921
shell: pwsh
@@ -26,6 +28,67 @@ jobs:
2628
"TAG_NAME=$tag" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
2729
"VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
2830
31+
- name: Generate release notes from commits
32+
shell: pwsh
33+
run: |
34+
git fetch --force --tags
35+
36+
$tag = $env:TAG_NAME
37+
if ([string]::IsNullOrWhiteSpace($tag)) {
38+
throw "TAG_NAME is empty"
39+
}
40+
41+
$repo = "${{ github.repository }}"
42+
43+
$prev = ""
44+
try {
45+
$commit = (git rev-list -n 1 $tag).Trim()
46+
if (-not [string]::IsNullOrWhiteSpace($commit)) {
47+
$prev = (git describe --tags --abbrev=0 "$commit^" 2>$null).Trim()
48+
}
49+
} catch {}
50+
51+
if ([string]::IsNullOrWhiteSpace($prev)) {
52+
# Fallback: best-effort previous version tag by semver-ish sorting.
53+
$prev = (git tag --list "v*" --sort=-v:refname | Where-Object { $_ -ne $tag } | Select-Object -First 1)
54+
}
55+
56+
$range = ""
57+
if (-not [string]::IsNullOrWhiteSpace($prev)) {
58+
$range = "$prev..$tag"
59+
}
60+
61+
$lines = @()
62+
if (-not [string]::IsNullOrWhiteSpace($range)) {
63+
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse $range)
64+
} else {
65+
# First release tag / missing history: include a small recent window.
66+
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse -n 50)
67+
}
68+
69+
if (-not $lines -or $lines.Count -eq 0) {
70+
$lines = @("- 修复了一些已知问题,提升了稳定性。")
71+
}
72+
73+
$max = 60
74+
if ($lines.Count -gt $max) {
75+
$total = $lines.Count
76+
$lines = @($lines | Select-Object -First $max)
77+
$lines += "- ...(共 $total 条提交,更多请查看完整变更链接)"
78+
}
79+
80+
$body = @()
81+
$body += "## 更新内容 ($tag)"
82+
$body += ""
83+
$body += $lines
84+
85+
if (-not [string]::IsNullOrWhiteSpace($prev)) {
86+
$body += ""
87+
$body += "完整变更: https://github.com/$repo/compare/$prev...$tag"
88+
}
89+
90+
($body -join "`n") | Out-File -FilePath release-notes.md -Encoding utf8
91+
2992
- name: Setup Node.js
3093
uses: actions/setup-node@v4
3194
with:
@@ -71,7 +134,7 @@ jobs:
71134
with:
72135
tag_name: ${{ env.TAG_NAME }}
73136
name: ${{ env.TAG_NAME }}
74-
generate_release_notes: true
137+
body_path: release-notes.md
75138
files: |
76139
desktop/dist/*Setup*.exe
77140
desktop/dist/*Setup*.exe.blockmap

desktop/src/main.cjs

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,25 +265,126 @@ function setWindowProgressBar(value) {
265265
} catch {}
266266
}
267267

268+
function looksLikeHtml(input) {
269+
if (!input) return false;
270+
const s = String(input);
271+
if (!s.includes("<") || !s.includes(">")) return false;
272+
// Be conservative: only treat the note as HTML if it contains common tags we expect from GitHub-rendered bodies.
273+
return /<(p|div|br|ul|ol|li|a|strong|em|tt|code|pre|h[1-6])\b/i.test(s);
274+
}
275+
276+
function htmlToPlainText(html) {
277+
if (!html) return "";
278+
279+
let text = String(html);
280+
281+
// Drop script/style blocks entirely.
282+
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
283+
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
284+
285+
// Keep links readable after stripping tags.
286+
text = text.replace(
287+
/<a\s+[^>]*href=(["'])([^"']+)\1[^>]*>([\s\S]*?)<\/a>/gi,
288+
(_m, _q, href, inner) => {
289+
const innerText = String(inner).replace(/<[^>]*>/g, "").trim();
290+
const url = String(href || "").trim();
291+
if (!url) return innerText;
292+
if (!innerText) return url;
293+
return `${innerText} (${url})`;
294+
}
295+
);
296+
297+
// Preserve line breaks / list structure before stripping remaining tags.
298+
text = text.replace(/<\s*br\s*\/?>/gi, "\n");
299+
text = text.replace(/<\/\s*(p|div|h1|h2|h3|h4|h5|h6)\s*>/gi, "\n");
300+
text = text.replace(/<\s*li[^>]*>/gi, "- ");
301+
text = text.replace(/<\/\s*li\s*>/gi, "\n");
302+
text = text.replace(/<\/\s*(ul|ol)\s*>/gi, "\n");
303+
304+
// Strip remaining tags.
305+
text = text.replace(/<[^>]*>/g, "");
306+
307+
// Decode the handful of entities we commonly see from GitHub-rendered HTML.
308+
const named = {
309+
nbsp: " ",
310+
amp: "&",
311+
lt: "<",
312+
gt: ">",
313+
quot: '"',
314+
apos: "'",
315+
"#39": "'",
316+
};
317+
text = text.replace(/&([a-z0-9#]+);/gi, (m, name) => {
318+
const key = String(name || "").toLowerCase();
319+
if (named[key] != null) return named[key];
320+
321+
// Numeric entities (decimal / hex).
322+
const decMatch = key.match(/^#(\d+)$/);
323+
if (decMatch) {
324+
const n = Number(decMatch[1]);
325+
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
326+
try {
327+
return String.fromCodePoint(n);
328+
} catch {
329+
return m;
330+
}
331+
}
332+
return m;
333+
}
334+
335+
const hexMatch = key.match(/^#x([0-9a-f]+)$/i);
336+
if (hexMatch) {
337+
const n = Number.parseInt(hexMatch[1], 16);
338+
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
339+
try {
340+
return String.fromCodePoint(n);
341+
} catch {
342+
return m;
343+
}
344+
}
345+
return m;
346+
}
347+
348+
return m;
349+
});
350+
351+
// Normalize whitespace/newlines.
352+
text = text.replace(/\r\n/g, "\n");
353+
text = text.replace(/\n{3,}/g, "\n\n");
354+
return text.trim();
355+
}
356+
268357
function normalizeReleaseNotes(releaseNotes) {
269358
if (!releaseNotes) return "";
270-
if (typeof releaseNotes === "string") return releaseNotes;
359+
360+
const normalizeText = (value) => {
361+
if (value == null) return "";
362+
const raw = typeof value === "string" ? value : String(value);
363+
const trimmed = raw.trim();
364+
if (!trimmed) return "";
365+
if (looksLikeHtml(trimmed)) return htmlToPlainText(trimmed);
366+
return trimmed;
367+
};
368+
369+
if (typeof releaseNotes === "string") return normalizeText(releaseNotes);
271370
if (Array.isArray(releaseNotes)) {
272371
const parts = [];
273372
for (const item of releaseNotes) {
274373
const version = item?.version ? String(item.version) : "";
275374
const note = item?.note;
276375
const noteText =
277376
typeof note === "string" ? note : note != null ? JSON.stringify(note, null, 2) : "";
278-
const block = [version ? `v${version}` : "", noteText].filter(Boolean).join("\n");
377+
const block = [version ? `v${version}` : "", normalizeText(noteText)]
378+
.filter(Boolean)
379+
.join("\n");
279380
if (block) parts.push(block);
280381
}
281382
return parts.join("\n\n");
282383
}
283384
try {
284-
return JSON.stringify(releaseNotes, null, 2);
385+
return normalizeText(JSON.stringify(releaseNotes, null, 2));
285386
} catch {
286-
return String(releaseNotes);
387+
return normalizeText(releaseNotes);
287388
}
288389
}
289390

frontend/components/DesktopUpdateDialog.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<span v-if="remainingText">剩余 {{ remainingText }}</span>
4545
</div>
4646
<div class="mt-2 h-2 w-full rounded bg-gray-200 overflow-hidden">
47-
<div class="h-2 bg-emerald-500" :style="{ width: `${percent}%` }" />
47+
<div class="h-2 bg-wechat-green" :style="{ width: `${percent}%` }" />
4848
</div>
4949
</div>
5050

@@ -69,7 +69,7 @@
6969

7070
<button
7171
v-if="readyToInstall"
72-
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
72+
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
7373
type="button"
7474
@click="emitInstall"
7575
>
@@ -86,7 +86,7 @@
8686
忽略此版本
8787
</button>
8888
<button
89-
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
89+
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
9090
type="button"
9191
@click="emitUpdate"
9292
>

0 commit comments

Comments
 (0)