ci: gate v3 release on version value changes (#7052)

Co-authored-by: DDSRem <1448139087@qq.com>
This commit is contained in:
DDSRem
2026-10-17 19:57:48 +08:00
committed by GitHub
parent 85b367527e
commit e33ae01aad
3 changed files with 90 additions and 0 deletions

View File

@@ -12,7 +12,49 @@ permissions:
packages: write
jobs:
release-gate:
runs-on: ubuntu-latest
outputs:
publish: ${{ steps.detect.outputs.publish }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Detect version change
id: detect
env:
EVENT_NAME: ${{ github.event_name }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = workflow_dispatch ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
exit 0
fi
versions() {
sed -nE "s/^(APP_VERSION|FRONTEND_VERSION)[[:space:]]*=[[:space:]]*'([^']+)'[[:space:]]*$/\1=\2/p" | sort
}
previous=$(git show "$BEFORE_SHA:version.py" | versions)
current=$(versions < version.py)
for value in APP_VERSION FRONTEND_VERSION; do
if ! printf '%s\n' "$previous" | grep -q "^${value}=" ||
! printf '%s\n' "$current" | grep -q "^${value}="; then
echo "版本声明缺失: $value" >&2
exit 1
fi
done
if [ "$previous" = "$current" ]; then
echo "publish=false" >> "$GITHUB_OUTPUT"
else
echo "publish=true" >> "$GITHUB_OUTPUT"
fi
Docker-build:
needs: release-gate
if: needs.release-gate.outputs.publish == 'true'
runs-on: ubuntu-latest
name: Build Docker Image
steps:

View File

@@ -215,6 +215,8 @@ uvx --from pip-audit pip-audit \
Docker 镜像直接基于官方基础镜像构建,只安装应用运行所需的系统依赖;不运行镜像漏洞扫描,
也不为扫描结果刷新系统包、删除基础镜像自带工具或维护漏洞忽略清单。正式版和 Beta 均直接构建
并发布标准版与 free-threaded 版的双架构镜像。应用锁定依赖仍按上述流程审计。
`v3` 分支自动发布仅在 `version.py` 的 `APP_VERSION` 或 `FRONTEND_VERSION` 值发生变化时运行;
只修改该文件的说明文字不会重建正式镜像。维护者仍可通过手动触发正式发布工作流重建当前版本。
### 6. Contributor 提交准备

View File

@@ -403,6 +403,52 @@ def test_publish_refreshes_base_and_preserves_build_cache() -> None:
assert "scope=moviepilot-v3-standard-docker," in publish["cache-to"]
def test_release_gate_only_publishes_changed_versions(tmp_path: Path) -> None:
"""
自动发布只接受版本值变化,手动重建保留入口。
"""
workflow = _load_workflow()
gate = workflow["jobs"]["release-gate"]
build = workflow["jobs"]["Docker-build"]
assert build["needs"] == "release-gate"
assert build["if"] == "needs.release-gate.outputs.publish == 'true'"
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init", "-q", str(repo)], check=True)
version_file = repo / "version.py"
version_file.write_text(
"APP_VERSION = 'v3.1.6'\nFRONTEND_VERSION = 'v3.1.6'\n",
encoding="utf-8",
)
subprocess.run(["git", "-C", str(repo), "add", "version.py"], check=True)
subprocess.run(
["git", "-C", str(repo), "-c", "user.name=Test", "-c", "user.email=test@example.com",
"commit", "-qm", "base"],
check=True,
)
before_sha = subprocess.check_output(
["git", "-C", str(repo), "rev-parse", "HEAD"], text=True
).strip()
command = next(step for step in gate["steps"] if step.get("id") == "detect")["run"]
output_file = tmp_path / "output"
def detect(event_name: str, content: str) -> str:
version_file.write_text(content, encoding="utf-8")
output_file.write_text("", encoding="utf-8")
subprocess.run(
["bash", "-e", "-c", command], cwd=repo, check=True,
env={**os.environ, "EVENT_NAME": event_name, "BEFORE_SHA": before_sha,
"GITHUB_OUTPUT": str(output_file)},
)
return output_file.read_text(encoding="utf-8").strip()
assert detect("push", "\"\"\"说明文字\"\"\"\nAPP_VERSION = 'v3.1.6'\nFRONTEND_VERSION = 'v3.1.6'\n") == "publish=false"
assert detect("push", "APP_VERSION = 'v3.1.7'\nFRONTEND_VERSION = 'v3.1.6'\n") == "publish=true"
assert detect("push", "APP_VERSION = 'v3.1.6'\nFRONTEND_VERSION = 'v3.1.7'\n") == "publish=true"
assert detect("workflow_dispatch", "APP_VERSION = 'v3.1.6'\nFRONTEND_VERSION = 'v3.1.6'\n") == "publish=true"
def test_release_publishes_free_threaded_image_with_separate_metadata_and_cache() -> None:
"""
free-threaded 发布必须使用 v3t 命名、参数和独立缓存。