From 4b0a543b42151fef73629548a959598a20b6506f Mon Sep 17 00:00:00 2001 From: treehey <2472336312@qq.com> Date: Sat, 29 Aug 2026 22:54:33 +0800 Subject: [PATCH] docs: define semantic versioning policy --- CONTRIBUTING.md | 1 + docs/releasing.md | 36 ++++++++++++++++++++++++++++++----- scripts/verify-repository.mjs | 28 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8caa52..a09017c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,7 @@ - 提交信息使用简短祈使句,可采用 Conventional Commits,例如 `fix: stop retries after manual auth`。 - 每个提交应能独立解释,避免同时包含功能、数据迁移和全仓格式化。 - 不重写他人的公开历史,不移动已发布标签。 +- 版本号从 `v6.4.0` 起遵循 SemVer;Bug 修复升 `PATCH`,向后兼容的新功能升 `MINOR`,不兼容变化升 `MAJOR`。完整判定见[发布流程](docs/releasing.md#版本号规则)。 ## Pull Request 要求 diff --git a/docs/releasing.md b/docs/releasing.md index 043250d..6788e87 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -2,6 +2,27 @@ 发布由维护者执行。正式包必须来自 `scripts/build-package.ps1`,不得直接压缩仓库根目录。 +## 版本号规则 + +项目从 `v6.4.0` 起严格采用 [Semantic Versioning](https://semver.org/),格式固定为 `MAJOR.MINOR.PATCH`。更早的公开版本保留原编号,不删除 Release、不重打标签,也不修改历史版本号。 + +| 变化类型 | 升级方式 | 例子 | +| --- | --- | --- | +| 只修复既有行为,且不增加用户功能 | `PATCH` | `6.4.0 → 6.4.1` | +| 增加向后兼容的新功能或新交互 | `MINOR` | `6.4.1 → 6.5.0` | +| 引入不兼容变化 | `MAJOR` | `6.x → 7.0.0` | + +以下情况通常必须升级 `MAJOR`: + +- 旧配置或会话数据无法自动迁移,需要用户重新配置; +- 删除或改变已有核心行为,旧使用方式不再成立; +- 扩大权限、数据用途或信任边界,并因此需要用户重新授权或重新确认; +- 更改支持范围或任务模型,导致现有自动化配置无法保持原语义。 + +更新内容很多并不自动构成大版本。只要旧配置可迁移、旧操作仍有效且权限边界没有破坏性变化,就继续使用 `MINOR`。`6.10.0` 高于 `6.9.0`,属于正常的语义化版本号。 + +版本确定后不得为了“看起来更大”临时调整。若同一批改动同时包含新功能和 Bug 修复,按其中最高级别升级:`MAJOR > MINOR > PATCH`。 + ## 1. 确认范围 - 工作区只包含本次发布相关改动; @@ -10,11 +31,14 @@ ## 2. 更新版本和文档 -1. 按语义化版本规则同步更新 `manifest.json` 和 `package.json` 的 `version`。 +1. 按上述规则确定版本,并同步更新 `manifest.json` 和 `package.json` 的 `version`。 2. 将 `CHANGELOG.md` 中已完成内容从 `Unreleased` 移到新版本标题,并写入发布日期。 -3. 权限、存储或网络行为变化时更新 `PRIVACY.md` 和 `STORE_DESCRIPTION.md`。 -4. 用户操作或支持范围变化时更新 `README.md`、使用指南和问题排查文档。 -5. README 的版本徽章读取最新 GitHub Release,不手工写死开发版本。 +3. 新增 `docs/RELEASE_NOTES_v.md`,并加入 `docs/README.md` 的发布说明索引。 +4. 权限、存储或网络行为变化时更新 `PRIVACY.md` 和 `STORE_DESCRIPTION.md`。 +5. 用户操作或支持范围变化时更新 `README.md`、使用指南和问题排查文档。 +6. README 的版本徽章读取最新 GitHub Release,不手工写死开发版本。 + +`npm test` 会检查版本格式、两个 JSON 文件的版本一致性、CHANGELOG 当前版本标题、发布说明文件及文档索引。标签和 GitHub Release 尚未创建时无法由 PR 检查验证,发布者必须在推送前人工核对。 ## 3. 执行验证 @@ -40,7 +64,7 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-package.ps1 ```powershell git status --short -git tag v +git tag -a v -m "NJU Login Pro v" git push origin git push origin v ``` @@ -54,6 +78,8 @@ git push origin v 推送标签前再次确认版本号和提交对象。标签发布后的修正使用新补丁版本,不移动已公开标签。 +GitHub Release 发布后,将 `main` 合并回 `dev`,确保发布文档和版本号不会只停留在主分支。远程仓库长期只保留 `main`、`dev` 和仍在评审的短生命周期分支。 + ## 6. 商店发布 使用同一个已验收 ZIP 提交 Edge Add-ons,保持商店短描述、权限说明和隐私政策链接一致。商店审核完成后检查公开页面版本和下载行为。 diff --git a/scripts/verify-repository.mjs b/scripts/verify-repository.mjs index c61c04f..ecb1c10 100644 --- a/scripts/verify-repository.mjs +++ b/scripts/verify-repository.mjs @@ -133,6 +133,34 @@ assert.match(manifest.version, /^\d+\.\d+\.\d+$/, 'Manifest version must use x.y assert.equal(packageJson.version, manifest.version, 'package.json and manifest.json versions must match'); assert.equal(packageJson.private, true, 'package.json must remain private'); +const escapedVersion = manifest.version.replaceAll('.', '\\.'); +const changelog = await readFile(path.join(repoRoot, 'CHANGELOG.md'), 'utf8'); +assert.match( + changelog, + new RegExp(`^## v${escapedVersion} - \\d{4}-\\d{2}-\\d{2}$`, 'm'), + `CHANGELOG.md must contain a dated v${manifest.version} heading` +); +const releaseNotesRelativePath = `docs/RELEASE_NOTES_v${manifest.version}.md`; +const releaseNotesPath = path.join(repoRoot, releaseNotesRelativePath); +assert.equal(await exists(releaseNotesPath), true, `Missing current release notes: ${releaseNotesRelativePath}`); +const releaseNotes = await readFile(releaseNotesPath, 'utf8'); +assert.match( + releaseNotes, + new RegExp(`^# NJU Login Pro v${escapedVersion}$`, 'm'), + `Current release notes must be titled NJU Login Pro v${manifest.version}` +); +assert.match( + releaseNotes, + new RegExp(`NJU-Login-Pro-v${escapedVersion}\\.zip`), + `Current release notes must name NJU-Login-Pro-v${manifest.version}.zip` +); +const docsIndex = await readFile(path.join(repoRoot, 'docs', 'README.md'), 'utf8'); +assert.match( + docsIndex, + new RegExp(`\\[v${escapedVersion}\\]\\(RELEASE_NOTES_v${escapedVersion}\\.md\\)`), + `docs/README.md must link the v${manifest.version} release notes` +); + const declaredPermissions = new Set(manifest.permissions || []); for (const permission of ['cookies', 'history', 'offscreen', 'tabs', 'unlimitedStorage']) { assert(!declaredPermissions.has(permission), `Unexpected sensitive permission: ${permission}`);