From f361a6dcbee4d3ebbd46a0769aac8964e69f26ed Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Tue, 22 Sep 2026 15:21:08 +0800 Subject: [PATCH] =?UTF-8?q?ci(release):=20=E7=BB=99=20release-please=20?= =?UTF-8?q?=E9=85=8D=20extra-files=EF=BC=8C=E5=B9=B6=E6=8A=8A"=E5=93=AA?= =?UTF-8?q?=E5=87=A0=E5=A4=84=E5=AE=83=E6=94=B9=E4=B8=8D=E5=88=B0"?= =?UTF-8?q?=E5=86=99=E6=88=90=E6=9C=BA=E5=99=A8=E6=A0=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #114 让 release-please 恢复工作后,它自动开的 release PR(#115→#120)只改 3 个文件 (pyproject / CHANGELOG / manifest),而本仓有 10 处跟踪中的版本位 —— 那条 PR 一旦合进 main, `test_all_version_sites_agree` 会把 main 判红。我在 #115 上写过"要放行就得填 extra-files", 这条就是去填;但先查清了它到底能填哪些,不是照抄文档。 RP 没有 regex 类型:`schemas/config.json` 的 `extra-files[].type` 只接受 json | toml | yaml | xml | pom | generic,且 `generic` 要求行尾跟 `# x-release-please-version` 这种井号注释标记。于是: - 能自动(本条写进 extra-files 的 5 处 + pyproject):version.json、config.yaml(yaml/$.version)、 desktop/package.json、desktop/src-tauri/tauri.conf.json、Cargo.toml(toml/$.package.version); - 只能人改:Cargo.lock(归 cargo 生成,手写标记会被重写掉)、 setup.nsi 的 OutFile/APP_VERSION/VIProductVersion(NSIS 注释符是 `;`,用不了 `#` 标记)、 deploy/kubernetes/deployment.yaml 的镜像 tag(要跟 ghcr 上真存在的标签走); - 不列:scripts/installer/version.json(.gitignore:436 的装配中间物,仓库里没有)。 剩下的不装作能自动化,但把"哪几处要人补"变成失败信息的一部分: - `_RP_MANAGED` 常量 + 失败时逐条标「← RP 自动 / ← 手工同步」并写明原因; - 新增 `test_rp_managed_annotation_matches_the_actual_config`:标注必须与 config 里的 extra-files 一致 —— 指路信息自己漂掉,比没有注释更坏; - `docs/release-governance.md` §1 落同一张表。 验证:`test_version_consistency.py` + `test_release_robustness.py` 12 passed; 变异自证(把 tauri.conf.json 改成 2.2.99 → 红并逐条标注); `check_spec_refs.py` new=0;`verify_cloud_native.py` 全绿。 合掉之后 RP 会 groom 下一条 release PR(#120 / 2.2.4),那时就能在**真实 PR** 上看见 yaml/toml 更新器到底怎么写 config.yaml 与 Cargo.toml(保留引号还是裸值)。写歪了就回退对应条目, 而不是留着一个"看起来配好了"的配置。 Signed-off-by: ReSerendipity --- docs/release-governance.md | 10 +++++++++ release-please-config.json | 8 ++++++- tests/test_version_consistency.py | 36 ++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/docs/release-governance.md b/docs/release-governance.md index 7039199..0856113 100644 --- a/docs/release-governance.md +++ b/docs/release-governance.md @@ -14,6 +14,16 @@ > 其中 `desktop/package-lock.json` 在 `.gitignore` 里(不是仓库内的版本位);仓库内跟踪的 9 处 > 由 `tests/test_version_consistency.py::test_all_version_sites_agree` 逐条比对,漂一处就红 > (v2.2.2 发版前 `deploy/kubernetes/deployment.yaml` 的镜像 tag 就停在 2.2.1,红得下来)。 + > + > **哪几处 release-please 会自动改、哪几处必须人改**(它只支持 `json|toml|yaml|xml|pom|generic`, + > 没有 regex):`pyproject.toml` 与 `release-please-config.json` 的 `extra-files` + > (`version.json`/`config.yaml`/`desktop/package.json`/`tauri.conf.json`/`Cargo.toml`)是自动的; + > **必须人补**的是 `desktop/src-tauri/Cargo.lock`(归 cargo 生成)、 + > `scripts/installer/setup.nsi` 的 `OutFile`/`APP_VERSION`/`VIProductVersion` + > (NSIS 注释符是 `;`,用不了 `generic` 要求的 `# x-release-please-version` 标记)、 + > `deploy/kubernetes/deployment.yaml` 的镜像 tag(要跟 ghcr 上真存在的标签走)。 + > 所以下一条 release PR 上,红在这三处是**预期行为**,补一个 commit 即可 —— + > 失败信息里会逐条标注哪处是『RP 自动』、哪处是『手工同步』。 - 版本位:`pyproject.toml` + `config.yaml`(release-please 驱动前端缓存参数需人工补齐,见本地 AGENTS.md #9(AGENTS.md 为本地维护、不随仓库分发))+ `CHANGELOG.md`。 - 发布**目前**由人工 `git tag -a` + `gh release create` 完成:**`release-please.yml` 是结构性空转**, 不会替你发版 —— 它设了 `skip-github-pull-request: true` 而仓库从未产生过 release PR, diff --git a/release-please-config.json b/release-please-config.json index c130049..78ba566 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,7 +5,13 @@ "bump-patch-for-minor-pre-major": false, "release-type": "python", "include-component-in-tag": false, - "extra-files": [], + "extra-files": [ + { "type": "json", "path": "version.json", "jsonpath": "$.version" }, + { "type": "json", "path": "desktop/package.json", "jsonpath": "$.version" }, + { "type": "json", "path": "desktop/src-tauri/tauri.conf.json", "jsonpath": "$.version" }, + { "type": "toml", "path": "desktop/src-tauri/Cargo.toml", "jsonpath": "$.package.version" }, + { "type": "yaml", "path": "config.yaml", "jsonpath": "$.version" } + ], "packages": { ".": {} } diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py index 0ab7df6..bdc50be 100644 --- a/tests/test_version_consistency.py +++ b/tests/test_version_consistency.py @@ -83,6 +83,17 @@ def test_no_hardcoded_old_version(): _SEMVER = re.compile(r"\d+\.\d+\.\d+") +#: release-please 通过 `release-please-config.json` 的 extra-files 会自动抬的版本位。 +#: 键与本文件 `_site_versions()` 的键一致;改 config 时同步改这里,否则失败信息会指错方向。 +_RP_MANAGED = { + "pyproject.toml", + "version.json", + "config.yaml", + "desktop/package.json", + "desktop/src-tauri/tauri.conf.json", + "desktop/src-tauri/Cargo.toml", +} + def _ver_tuple(s: str) -> tuple[int, ...]: """x.y.z → 可比较的整数元组(避免字典序把 2.9 判成比 2.10 大)。""" @@ -151,9 +162,14 @@ def test_all_version_sites_agree() -> None: assert not bad, f"这些版本位不是 x.y.z 形态:{bad}" distinct = set(sites.values()) assert len(distinct) == 1, ( - "版本位互相矛盾(release-governance §5 的『版本位全部同步』没做到):" - + "\n " - + "\n ".join(f"{k} = {v}" for k, v in sorted(sites.items())) + "版本位互相矛盾(release-governance §5 的『版本位全部同步』没做到):\n " + + "\n ".join( + f"{k} = {v}{' ← RP 自动' if k in _RP_MANAGED else ' ← 手工同步'}" for k, v in sorted(sites.items()) + ) + + "\n release-please 只会改上面标『RP 自动』的那些(它只支持 json/toml/yaml/xml/pom/generic," + "没有 regex);标『手工同步』的必须在 release PR 上补一个 commit —— " + "Cargo.lock 归 cargo 生成、setup.nsi 的注释符是 `;` 用不了 generic 的 `# x-release-please-version` 标记、" + "k8s 镜像 tag 要跟 ghcr 上真存在的标签走、安装器那份是 gitignore 的装配中间物。" ) @@ -173,3 +189,17 @@ def test_installer_artifact_names_track_the_version_site() -> None: # updater.rs 只在 AppVersion 结构里解析它,shell-update.json 契约里没有这个字段), # 所以这里只钉住唯一站得住的关系:下界不得高于本次版本。 assert _ver_tuple(minimum) <= _ver_tuple(ver), f"minimum_shell_version={minimum} 高于本次版本 {ver}" + + +def test_rp_managed_annotation_matches_the_actual_config() -> None: + """`_RP_MANAGED` 只是给失败信息指路用的,它自己不能漂:必须与 + release-please-config.json 的 extra-files + pyproject(python release-type 自带)一致。""" + import re as _re + + cfg = (PROJECT_ROOT / "release-please-config.json").read_text(encoding="utf-8") + listed = set(_re.findall(r'"path":\s*"([^"]+)"', cfg)) + assert listed, "config 里一个 extra-files 都没有,那这条闸就没意义了" + assert listed | {"pyproject.toml"} == _RP_MANAGED, ( + f"RP 自动位与测试里的标注不一致:config 有 {sorted(listed)},标注多/少的部分是" + f" {sorted((listed | {'pyproject.toml'}) ^ _RP_MANAGED)}" + )