mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 06:10:52 +08:00
ci: 用提交记录生成 release notes,并修复更新按钮主题色
This commit is contained in:
65
.github/workflows/release.yml
vendored
65
.github/workflows/release.yml
vendored
@@ -14,6 +14,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Derive version from tag
|
||||
shell: pwsh
|
||||
@@ -26,6 +28,67 @@ jobs:
|
||||
"TAG_NAME=$tag" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
- name: Generate release notes from commits
|
||||
shell: pwsh
|
||||
run: |
|
||||
git fetch --force --tags
|
||||
|
||||
$tag = $env:TAG_NAME
|
||||
if ([string]::IsNullOrWhiteSpace($tag)) {
|
||||
throw "TAG_NAME is empty"
|
||||
}
|
||||
|
||||
$repo = "${{ github.repository }}"
|
||||
|
||||
$prev = ""
|
||||
try {
|
||||
$commit = (git rev-list -n 1 $tag).Trim()
|
||||
if (-not [string]::IsNullOrWhiteSpace($commit)) {
|
||||
$prev = (git describe --tags --abbrev=0 "$commit^" 2>$null).Trim()
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($prev)) {
|
||||
# Fallback: best-effort previous version tag by semver-ish sorting.
|
||||
$prev = (git tag --list "v*" --sort=-v:refname | Where-Object { $_ -ne $tag } | Select-Object -First 1)
|
||||
}
|
||||
|
||||
$range = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($prev)) {
|
||||
$range = "$prev..$tag"
|
||||
}
|
||||
|
||||
$lines = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($range)) {
|
||||
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse $range)
|
||||
} else {
|
||||
# First release tag / missing history: include a small recent window.
|
||||
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse -n 50)
|
||||
}
|
||||
|
||||
if (-not $lines -or $lines.Count -eq 0) {
|
||||
$lines = @("- 修复了一些已知问题,提升了稳定性。")
|
||||
}
|
||||
|
||||
$max = 60
|
||||
if ($lines.Count -gt $max) {
|
||||
$total = $lines.Count
|
||||
$lines = @($lines | Select-Object -First $max)
|
||||
$lines += "- ...(共 $total 条提交,更多请查看完整变更链接)"
|
||||
}
|
||||
|
||||
$body = @()
|
||||
$body += "## 更新内容 ($tag)"
|
||||
$body += ""
|
||||
$body += $lines
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($prev)) {
|
||||
$body += ""
|
||||
$body += "完整变更: https://github.com/$repo/compare/$prev...$tag"
|
||||
}
|
||||
|
||||
($body -join "`n") | Out-File -FilePath release-notes.md -Encoding utf8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -71,7 +134,7 @@ jobs:
|
||||
with:
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
name: ${{ env.TAG_NAME }}
|
||||
generate_release_notes: true
|
||||
body_path: release-notes.md
|
||||
files: |
|
||||
desktop/dist/*Setup*.exe
|
||||
desktop/dist/*Setup*.exe.blockmap
|
||||
|
||||
@@ -265,9 +265,108 @@ function setWindowProgressBar(value) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function looksLikeHtml(input) {
|
||||
if (!input) return false;
|
||||
const s = String(input);
|
||||
if (!s.includes("<") || !s.includes(">")) return false;
|
||||
// Be conservative: only treat the note as HTML if it contains common tags we expect from GitHub-rendered bodies.
|
||||
return /<(p|div|br|ul|ol|li|a|strong|em|tt|code|pre|h[1-6])\b/i.test(s);
|
||||
}
|
||||
|
||||
function htmlToPlainText(html) {
|
||||
if (!html) return "";
|
||||
|
||||
let text = String(html);
|
||||
|
||||
// Drop script/style blocks entirely.
|
||||
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
||||
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
||||
|
||||
// Keep links readable after stripping tags.
|
||||
text = text.replace(
|
||||
/<a\s+[^>]*href=(["'])([^"']+)\1[^>]*>([\s\S]*?)<\/a>/gi,
|
||||
(_m, _q, href, inner) => {
|
||||
const innerText = String(inner).replace(/<[^>]*>/g, "").trim();
|
||||
const url = String(href || "").trim();
|
||||
if (!url) return innerText;
|
||||
if (!innerText) return url;
|
||||
return `${innerText} (${url})`;
|
||||
}
|
||||
);
|
||||
|
||||
// Preserve line breaks / list structure before stripping remaining tags.
|
||||
text = text.replace(/<\s*br\s*\/?>/gi, "\n");
|
||||
text = text.replace(/<\/\s*(p|div|h1|h2|h3|h4|h5|h6)\s*>/gi, "\n");
|
||||
text = text.replace(/<\s*li[^>]*>/gi, "- ");
|
||||
text = text.replace(/<\/\s*li\s*>/gi, "\n");
|
||||
text = text.replace(/<\/\s*(ul|ol)\s*>/gi, "\n");
|
||||
|
||||
// Strip remaining tags.
|
||||
text = text.replace(/<[^>]*>/g, "");
|
||||
|
||||
// Decode the handful of entities we commonly see from GitHub-rendered HTML.
|
||||
const named = {
|
||||
nbsp: " ",
|
||||
amp: "&",
|
||||
lt: "<",
|
||||
gt: ">",
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
"#39": "'",
|
||||
};
|
||||
text = text.replace(/&([a-z0-9#]+);/gi, (m, name) => {
|
||||
const key = String(name || "").toLowerCase();
|
||||
if (named[key] != null) return named[key];
|
||||
|
||||
// Numeric entities (decimal / hex).
|
||||
const decMatch = key.match(/^#(\d+)$/);
|
||||
if (decMatch) {
|
||||
const n = Number(decMatch[1]);
|
||||
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
|
||||
try {
|
||||
return String.fromCodePoint(n);
|
||||
} catch {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
const hexMatch = key.match(/^#x([0-9a-f]+)$/i);
|
||||
if (hexMatch) {
|
||||
const n = Number.parseInt(hexMatch[1], 16);
|
||||
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
|
||||
try {
|
||||
return String.fromCodePoint(n);
|
||||
} catch {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
return m;
|
||||
});
|
||||
|
||||
// Normalize whitespace/newlines.
|
||||
text = text.replace(/\r\n/g, "\n");
|
||||
text = text.replace(/\n{3,}/g, "\n\n");
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
function normalizeReleaseNotes(releaseNotes) {
|
||||
if (!releaseNotes) return "";
|
||||
if (typeof releaseNotes === "string") return releaseNotes;
|
||||
|
||||
const normalizeText = (value) => {
|
||||
if (value == null) return "";
|
||||
const raw = typeof value === "string" ? value : String(value);
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return "";
|
||||
if (looksLikeHtml(trimmed)) return htmlToPlainText(trimmed);
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
if (typeof releaseNotes === "string") return normalizeText(releaseNotes);
|
||||
if (Array.isArray(releaseNotes)) {
|
||||
const parts = [];
|
||||
for (const item of releaseNotes) {
|
||||
@@ -275,15 +374,17 @@ function normalizeReleaseNotes(releaseNotes) {
|
||||
const note = item?.note;
|
||||
const noteText =
|
||||
typeof note === "string" ? note : note != null ? JSON.stringify(note, null, 2) : "";
|
||||
const block = [version ? `v${version}` : "", noteText].filter(Boolean).join("\n");
|
||||
const block = [version ? `v${version}` : "", normalizeText(noteText)]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
if (block) parts.push(block);
|
||||
}
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(releaseNotes, null, 2);
|
||||
return normalizeText(JSON.stringify(releaseNotes, null, 2));
|
||||
} catch {
|
||||
return String(releaseNotes);
|
||||
return normalizeText(releaseNotes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<span v-if="remainingText">剩余 {{ remainingText }}</span>
|
||||
</div>
|
||||
<div class="mt-2 h-2 w-full rounded bg-gray-200 overflow-hidden">
|
||||
<div class="h-2 bg-emerald-500" :style="{ width: `${percent}%` }" />
|
||||
<div class="h-2 bg-wechat-green" :style="{ width: `${percent}%` }" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<button
|
||||
v-if="readyToInstall"
|
||||
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
|
||||
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
|
||||
type="button"
|
||||
@click="emitInstall"
|
||||
>
|
||||
@@ -86,7 +86,7 @@
|
||||
忽略此版本
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
|
||||
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
|
||||
type="button"
|
||||
@click="emitUpdate"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user