Files
Unlucky-Launcher/packaging/release_marker.py
T
AskaEth 1d2e84ed61 fix(m163): push tag 默认建稳定版(发布标记语义 + 0.2.3 bump)
发布标记:`release.yml` 的 dispatch `prerelease` 默认值 true → false;`release-publish.yml`
的 create 路径不再塞 `--prerelease`(空串 = push tag ⇒ 稳定版),edit 路径行为不变(空串不碰
标记)。三种触发各创建什么改由 `packaging/release_marker.py` 一张表定义,流水线
`--emit-bash` 出来两个数组直接用,自己不再写 case——v0.2.2 至今 `releases/latest` 404 的根因
就是「push 默认 prerelease」这层叠上「dispatch 默认 true」。

守门:新增 `packaging/check_release_semantics.py`(CI 在 `.github/workflows/release-guard.yml`,
guards.yml 已 398 行不能再挂 job):①标记表逐条对(push tag 的 create 不含 --prerelease);
②把 `--emit-bash` 真灌进 bash 读回两个数组;③publish 的可执行内容里没有 --prerelease 字面量、
必须调用 emit-bash、两个数组各喂 create/edit;④release.yml 的 dispatch 默认值 == false(读
YAML 结构)。`--self-mutation` 注入三项真实回归(默认值改回 true / 塞回字面量 / 表里 push
改成预发布),断言每项都红、基线绿。

版本:0.2.2 → 0.2.3(workspace `[workspace.package]` + 五处 path 依赖 pin + Cargo.lock)。

文档:README 中英两版版本行改 0.2.3,并把本阶段能力补进现有章节(Modrinth 匿名在线内容源、
自更新与稳定通道、整合包导入逐文件决策、实例级启动参数),删掉「没有自动更新通道」「导入只有
两份全局策略」两条过期说法;`release-notes.md` 写 0.2.3 的真实更新摘要(保留 {{VERSION}} /
{{SUMS}});`packaging/README.md` 与 `docs/build-and-ci.md` 里「push 默认仍 prerelease」的旧口径
全部改掉。
2026-11-07 18:56:01 +08:00

172 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""发布标记(`gh release ... --prerelease`)的**唯一定义处**(M163)。
# 三种触发方式各创建什么
| 触发 | `github.event.inputs.prerelease` | 新建 Release(create) | 已存在 Release(edit) |
| --- | --- | --- | --- |
| push tag `v*`(以及它的 Re-run) | 空串(该字段不存在) | **稳定版**:不带任何标记 | 不传标记(**不碰**) |
| `workflow_dispatch` + `prerelease=true` | `"true"` | 预发布 | 显式改成预发布 |
| `workflow_dispatch` + `prerelease=false` | `"false"` | 稳定版 | 显式改成稳定版 |
空串必须是稳定版:GitHub 的 `releases/latest` **只指向非 prerelease 的最新 Release**,而自更新
客户端把清单地址钉在 `releases/latest/download/manifest.json`(D50)。v0.2.2 之前 push tag 默认打
`--prerelease`,于是 latest 一直 404、更新通道取不到清单——M163 修的就是这条语义。
edit 那一列的空串是「不碰」而不是「改成稳定版」:同 tag 重跑会落到 edit 分支,而用户手工把某个
Release 提成稳定版之后,重跑**不应该**把它翻回预发布。
# 流水线里唯一的走法
python3 packaging/release_marker.py --emit-bash --prerelease "${PRERELEASE:-}" > "$marks"
source "$marks" # 得到 create_mark=(...) / edit_mark=(...)
先落文件再 `source`,而不是 `source <(...)`:非法的 `prerelease` 取值必须让这一步**当场红**
(`set -e` 只认前一条命令的退出码,进程替换里的失败会被吞掉)。
守门在 `packaging/check_release_semantics.py`:它调本文件的 `marks()` 逐条对表,再真的把
`--emit-bash` 灌进 bash 看展开出来的两个数组。本文件的 `--self-test` 只查自己这一层。
"""
from __future__ import annotations
import argparse
import sys
#: 打预发布标记。
PRERELEASE = "--prerelease"
#: 显式摘掉预发布标记(`gh release edit` 用;`create` 不带标记就已经是稳定版)。
STABLE = "--prerelease=false"
#: (触发方式,输入原始值,create 标记,edit 标记)——本仓对外承诺的那张表,逐行可对。
CASES: tuple[tuple[str, str, tuple[str, ...], tuple[str, ...]], ...] = (
("push tag v*(含其 Re-run)", "", (), ()),
("workflow_dispatch + prerelease=true", "true", (PRERELEASE,), (PRERELEASE,)),
("workflow_dispatch + prerelease=false", "false", (), (STABLE,)),
)
def marks(raw: str) -> tuple[tuple[str, ...], tuple[str, ...]]:
"""`github.event.inputs.prerelease` 的原始字符串 → (create 标记, edit 标记)。
只认三种取值(空串 / `"true"` / `"false"`),其它一律抛 :class:`ValueError`——发布流水线
宁可红在一次错误的 dispatch 输入上,也不要把一个自己没预期的值当成「稳定版」发出去。
"""
value = (raw or "").strip()
if value == "":
return (), ()
if value == "true":
return (PRERELEASE,), (PRERELEASE,)
if value == "false":
return (), (STABLE,)
raise ValueError(
f"非预期的 github.event.inputs.prerelease 取值:{raw!r}(只认 空串 / true / false)"
)
def bash_snippet(raw: str) -> str:
"""把两个数组渲染成可直接 `source` 的 bash 片段。"""
create, edit = marks(raw)
return (
"# 由 packaging/release_marker.py 生成,别手改:标记的全部判据都在那个脚本里。\n"
f"create_mark=({' '.join(create)})\n"
f"edit_mark=({' '.join(edit)})\n"
)
def table() -> str:
"""把 :data:`CASES` 渲染成 markdown 表(`--table`;报告与文档直接抄它)。"""
def cell(flags: tuple[str, ...], empty: str) -> str:
return "、".join(f"`{flag}`" for flag in flags) if flags else empty
rows = ["| 触发 | `prerelease` 输入 | create | edit |", "| --- | --- | --- | --- |"]
for trigger, raw, create, edit in CASES:
shown = "空串" if raw == "" else f'`"{raw}"`'
rows.append(f"| {trigger} | {shown} | {cell(create, '(不带标记)')} | {cell(edit, '不碰标记')} |")
return "\n".join(rows)
def self_test() -> int:
"""本文件自己这一层的断言(完整守门见 check_release_semantics.py)。"""
failures: list[str] = []
for trigger, raw, create, edit in CASES:
got = marks(raw)
if got != (create, edit):
failures.append(f"{trigger}:表里写 {(create, edit)},marks({raw!r}) 给的是 {got}")
# 最关键的一条:push tag(空串)的新建路径**不许**带预发布标记。它是 v0.2.2 至今
# `releases/latest` 404 的根因,任何一次重构都不许把它带回来。
push_create, push_edit = marks("")
if PRERELEASE in push_create:
failures.append(f"push tag 的 create 标记里出现了 {PRERELEASE}:稳定通道会一直是 404")
if push_edit:
failures.append(f"push tag 的 edit 标记应为「不碰」,实际是 {push_edit}")
# dispatch 显式 false 时,新建同样是稳定版(create 不需要 `--prerelease=false`:不带就已经是了)。
false_create, false_edit = marks("false")
if PRERELEASE in false_create:
failures.append(f"dispatch prerelease=false 的 create 标记里出现了 {PRERELEASE}")
# 非法值必须炸,不许静默当稳定版(`True` 是 Python 的写法,Actions 的 boolean 输入给的是小写)。
for bad in ("yes", "1", "TRUE", "False", "0", "prerelease"):
try:
marks(bad)
except ValueError:
continue
failures.append(f"非法取值 {bad!r} 被当成了合法输入:{marks(bad)}")
if failures:
for item in failures:
print(f"::error::release_marker 自检失败:{item}", file=sys.stderr)
return 1
print("OK: release_marker 标记表自检通过(push tag → 稳定版)")
print(table())
return 0
def main(argv: list[str]) -> int:
# Windows 控制台是 GBK:本文件的输出里有中文与箭头,缺了这行会在打印时抛
# UnicodeEncodeError(与判据无关的崩溃)。CI 是 UTF-8,不受影响。
for stream in (sys.stdout, sys.stderr):
if hasattr(stream, "reconfigure"):
stream.reconfigure(errors="backslashreplace")
parser = argparse.ArgumentParser(
description="发布标记(--prerelease)的唯一定义处:打印 gh release 要带的参数。"
)
parser.add_argument(
"--prerelease",
default="",
metavar="VALUE",
help='github.event.inputs.prerelease 的原值:空串 / "true" / "false"',
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--emit-bash",
action="store_true",
help="打印可直接 source 的 bash 片段(create_mark / edit_mark 两个数组)",
)
group.add_argument("--table", action="store_true", help="打印触发方式 ↔ 标记的对照表")
group.add_argument("--self-test", action="store_true", help="只查本文件这一层的断言")
args = parser.parse_args(argv)
if args.self_test:
return self_test()
if args.table:
print(table())
return 0
try:
sys.stdout.write(bash_snippet(args.prerelease))
except ValueError as error:
# 走 stderr:stdout 在这种情况下会被重定向进 marks 文件,注解会丢。
print(f"::error::{error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))