diff --git a/docs/release-notes/TEMPLATE-zh.md b/docs/release-notes/TEMPLATE-zh.md index 1497ce1..4e26d8d 100644 --- a/docs/release-notes/TEMPLATE-zh.md +++ b/docs/release-notes/TEMPLATE-zh.md @@ -25,20 +25,19 @@ 2. <说明是否需要配置、数据库或备份迁移。> 3. <说明升级后的首次检查步骤。> -## 🛡 安全保障 +## 🔒 数据处理方式 -- <说明本版本保持不变的认证和消息边界。> -- <说明备份、事务和恢复边界。> -- <说明平台或路径安全限制。> +- <用用户能理解的方式说明数据会如何处理、是否创建备份,以及重要的数据边界。> +- <只保留会影响用户操作的限制,不罗列内部检查、审计或流程。> ## ⚙️ 自动化接口(如适用) <先说明普通用户是否需要本节,再说明协议变化和兼容性。没有变化时可省略本节。> -## ⚠️ 重要说明 +## 🗂 使用提示 -- <列出会直接影响使用结果、但不能由工具自动解决的限制。> -- <不要把普通实现细节放在这里。> +- <列出会直接影响使用结果、但工具无法自动解决的限制。> +- <给出遇到问题时下一步该做什么。> ## 🔍 验证结果 diff --git a/scripts/read-release-metadata.js b/scripts/read-release-metadata.js index 52d28a8..23b1a20 100644 --- a/scripts/read-release-metadata.js +++ b/scripts/read-release-metadata.js @@ -7,19 +7,18 @@ import { fileURLToPath, pathToFileURL } from "node:url"; const RELEASE_TAG_PATTERN = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/; const TITLE_PATTERN = /^$/gm; -const REQUIRED_ANNOUNCEMENT_MARKERS = [ - "## 📦 下载", - "## ⬆️ 升级说明", - "## 🛡 安全保障", - "## ⚠️ 重要说明", - "## 🔍 验证结果", - "SmartScreen", - "SHA-256", - "`auth.json`", - "`updated_at`", - "`encrypted_content`", - "回滚", - "WSL UNC", +const REQUIRED_ANNOUNCEMENT_SECTION_GROUPS = [ + { label: "下载", markers: ["## 📦 下载"] }, + { label: "升级说明", markers: ["## ⬆️ 升级说明"] }, + { + label: "使用说明", + markers: ["## ⚠️ 重要说明", "## 🗂 会话同步怎么处理", "## 使用提示"], + }, + { + label: "数据处理方式", + markers: ["## 🛡 安全保障", "## 🔒 数据处理方式"], + }, + { label: "验证", markers: ["## 🔍 验证结果", "## 🔍 本版验证"] }, ]; function displayPath(rootDir, targetPath) { @@ -57,12 +56,14 @@ export function readReleaseMetadata({ rootDir, tag }) { if (/\b(?:TODO|TBD)\b/.test(body)) { throw new Error(`${relativeBodyPath} still contains a TODO or TBD placeholder.`); } - const missingMarkers = REQUIRED_ANNOUNCEMENT_MARKERS.filter( - (marker) => !body.includes(marker), + const missingSections = REQUIRED_ANNOUNCEMENT_SECTION_GROUPS.filter( + ({ markers }) => !markers.some((marker) => body.includes(marker)), + ).map( + ({ label }) => label, ); - if (missingMarkers.length > 0) { + if (missingSections.length > 0) { throw new Error( - `${relativeBodyPath} is missing required release or safety content: ${missingMarkers.join(", ")}`, + `${relativeBodyPath} is missing required user-facing sections: ${missingSections.join(", ")}`, ); } diff --git a/test/release-metadata.test.js b/test/release-metadata.test.js index cfbf62b..54c5a31 100644 --- a/test/release-metadata.test.js +++ b/test/release-metadata.test.js @@ -59,6 +59,15 @@ test("reads the current repository Chinese release metadata", () => { assert.equal(result.relativeBodyPath, "docs/release-notes/v0.5.0-zh.md"); }); +test("reads the current user-facing v1.0.3 announcement", () => { + const testDirectory = path.dirname(fileURLToPath(import.meta.url)); + const rootDir = path.resolve(testDirectory, ".."); + + const result = readReleaseMetadata({ rootDir, tag: "v1.0.3" }); + + assert.equal(result.title, "v1.0.3 - 会话同步与更新体验改进"); +}); + test("reads one validated release title and body path", () => withFixture((rootDir) => { writeAnnouncement( @@ -114,14 +123,42 @@ test("rejects duplicate, mismatched, and placeholder release metadata", () => ); })); -test("rejects a release announcement missing required safety content", () => +test("accepts user-facing sections without fixed internal boilerplate", () => + withFixture((rootDir) => { + writeAnnouncement( + rootDir, + "v1.2.3", + ` + +${"发布内容。".repeat(50)} + +## 📦 下载 +下载说明。 + +## ⬆️ 升级说明 +升级说明。 + +## 🗂 会话同步怎么处理 +使用说明。 + +## 🔒 数据处理方式 +数据说明。 + +## 🔍 本版验证 +验证说明。`, + ); + + assert.doesNotThrow(() => readReleaseMetadata({ rootDir, tag: "v1.2.3" })); + })); + +test("rejects a release announcement missing required user-facing sections", () => withFixture((rootDir) => { - const incomplete = completeAnnouncement("v1.2.3", "标题").replace("`auth.json`", "认证文件"); + const incomplete = completeAnnouncement("v1.2.3", "标题").replace("## 🛡 安全保障", "## 处理细节"); writeAnnouncement(rootDir, "v1.2.3", incomplete); assert.throws( () => readReleaseMetadata({ rootDir, tag: "v1.2.3" }), - /missing required release or safety content: `auth\.json`/, + /missing required user-facing sections: 数据处理方式/, ); }));