From 3501303fdbedbe65c900389b01fef5d20bf0ba3c Mon Sep 17 00:00:00 2001 From: tmoonlight Date: Tue, 20 Oct 2026 23:43:50 +0800 Subject: [PATCH] Automatically refresh public downloads after release --- .github/workflows/package.yml | 12 +++ .../workflows/refresh-public-downloads.yml | 96 +++++++++++++++++++ docs/public-download-publishing.md | 20 ++++ .../.openspec.yaml | 2 + .../auto-refresh-public-downloads/design.md | 39 ++++++++ .../auto-refresh-public-downloads/proposal.md | 27 ++++++ .../specs/public-release-catalog/spec.md | 43 +++++++++ .../auto-refresh-public-downloads/tasks.md | 15 +++ 8 files changed, 254 insertions(+) create mode 100644 .github/workflows/refresh-public-downloads.yml create mode 100644 docs/public-download-publishing.md create mode 100644 openspec/changes/auto-refresh-public-downloads/.openspec.yaml create mode 100644 openspec/changes/auto-refresh-public-downloads/design.md create mode 100644 openspec/changes/auto-refresh-public-downloads/proposal.md create mode 100644 openspec/changes/auto-refresh-public-downloads/specs/public-release-catalog/spec.md create mode 100644 openspec/changes/auto-refresh-public-downloads/tasks.md diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 9bc51d1c..fec6f1c7 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1333,6 +1333,18 @@ jobs: make_latest: ${{ contains(github.ref_name, '-') && 'false' || 'true' }} files: release-assets/* + refresh-public-downloads: + name: refresh public downloads + if: startsWith(github.ref, 'refs/tags/v') + needs: release + uses: ./.github/workflows/refresh-public-downloads.yml + permissions: + contents: read + with: + release_tag: ${{ github.ref_name }} + secrets: + PUBLIC_SITE_DEPLOY_KEY: ${{ secrets.PUBLIC_SITE_DEPLOY_KEY }} + publish-npm: name: publish npm # npm publication is paused for both tagged releases and manual backfills. diff --git a/.github/workflows/refresh-public-downloads.yml b/.github/workflows/refresh-public-downloads.yml new file mode 100644 index 00000000..3b0aec75 --- /dev/null +++ b/.github/workflows/refresh-public-downloads.yml @@ -0,0 +1,96 @@ +name: refresh-public-downloads + +on: + workflow_call: + inputs: + release_tag: + required: true + type: string + secrets: + PUBLIC_SITE_DEPLOY_KEY: + required: true + workflow_dispatch: + inputs: + release_tag: + description: Existing published release tag to verify and synchronize + required: true + type: string + +permissions: + contents: read + +concurrency: + group: public-download-catalog + cancel-in-progress: false + +jobs: + synchronize: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - name: Validate release tag and deployment credential + env: + RELEASE_TAG: ${{ inputs.release_tag }} + DEPLOY_KEY: ${{ secrets.PUBLIC_SITE_DEPLOY_KEY }} + shell: bash + run: | + set -euo pipefail + [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { + echo "::error::A published v tag is required" + exit 1 + } + test -n "$DEPLOY_KEY" || { + echo "::error::PUBLIC_SITE_DEPLOY_KEY is not configured" + exit 1 + } + + - name: Checkout public website + uses: actions/checkout@v4 + with: + repository: tmoonlight/acecode-site + ref: main + ssh-key: ${{ secrets.PUBLIC_SITE_DEPLOY_KEY }} + fetch-depth: 1 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Synchronize published downloads and trigger Pages + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.release_tag }} + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + for attempt in 1 2 3; do + # This is the disposable CI website checkout. Refresh against main + # after a concurrent website edit, then regenerate only public data. + git fetch origin main + git checkout -B catalog-sync origin/main + python scripts/sync_catalog.py --require-tag "$RELEASE_TAG" --releases-only + python -m unittest discover -s scripts -p "test_*.py" + node scripts/check_download.mjs + git add public/aceupdate.json public/data/release-source.json + git diff --cached --check + if git diff --cached --quiet; then + echo "Public catalog already matches GitHub Releases" + exit 0 + fi + git commit -m "Refresh download catalog after $RELEASE_TAG" + if git push origin HEAD:main; then + echo "Public website updated; Pages deployment is triggered by this push" + exit 0 + fi + echo "Website main changed during synchronization; retry $attempt/3" + done + echo "::error::Could not update the public website" + exit 1 + + - name: Verify public CDN catalog + run: python scripts/wait_for_catalog.py public/aceupdate.json --timeout 900 diff --git a/docs/public-download-publishing.md b/docs/public-download-publishing.md new file mode 100644 index 00000000..b59d9d58 --- /dev/null +++ b/docs/public-download-publishing.md @@ -0,0 +1,20 @@ +# 公开下载站自动更新 + +GitHub 标签发布成功后,`package.yml` 自动调用 +`refresh-public-downloads.yml`,更新 `tmoonlight/acecode-site` 的下载清单。 +网站仓库的 push 自动触发 Pages 部署,发布任务继续等待 CDN 清单内容一致。 +正常发布无需在本机执行网站刷新命令。 + +同步只接受已公开且核心安装资产及 SHA-256 完整的版本。普通分支构建、 +失败的 Release 发布不会触发网站更新。重试会读取全部公开 Releases, +保留最新版本;相同内容不产生空提交。 + +跨仓库写入使用 `PUBLIC_SITE_DEPLOY_KEY` Actions Secret,对应部署密钥 +仅授权 `acecode-site` 仓库。不要在代码、日志或普通变量中放置私钥。 + +流水线的网站同步失败时,已发布的安装包不回滚。检查同步任务和网站仓库 +`Deploy Pages` 的失败原因,修复后重跑失败任务。维护时也可在 ACECode +仓库 Actions 中选择 `refresh-public-downloads`,输入已有 `v` 标签。 +CDN 使用短缓存,工作流最多等待 15 分钟确认公开目录。 + +该步骤不替代原有的本机 aupdate 完整镜像与客户端升级验证。 diff --git a/openspec/changes/auto-refresh-public-downloads/.openspec.yaml b/openspec/changes/auto-refresh-public-downloads/.openspec.yaml new file mode 100644 index 00000000..9076a660 --- /dev/null +++ b/openspec/changes/auto-refresh-public-downloads/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-20 diff --git a/openspec/changes/auto-refresh-public-downloads/design.md b/openspec/changes/auto-refresh-public-downloads/design.md new file mode 100644 index 00000000..98075a9b --- /dev/null +++ b/openspec/changes/auto-refresh-public-downloads/design.md @@ -0,0 +1,39 @@ +## Context + +`package.yml` 通过工作流令牌调用 GitHub Release 发布;该令牌产生的普通事件 +不能用作另一个工作流的可靠触发器。下载站位于独立公开仓库 +`tmoonlight/acecode-site`,已有 push 驱动的 Pages 工作流。 + +## Goals / Non-Goals + +目标是将目录更新、Pages 部署与 CDN 验证串联为发布后续步骤。 +不改变安装包构建矩阵、版本生成、用户中心或本机 aupdate 镜像。 + +## Decisions + +1. 在 `package.yml` 添加 `needs: release` 的可复用工作流调用。 + 不依赖由工作流令牌生成的 `release.published` 事件,也不使用定时轮询。 +2. 新工作流支持 `workflow_call` 和维护用 `workflow_dispatch`,输入明确标签。 + 它检查独立网站仓库 main,生成公开目录、校验、精确暂存并 push。 +3. 网站仓库使用专用 SSH 写入部署密钥;私钥仅存 ACECode Actions Secret。 + 其权限局限于网站仓库,ACECode 的工作流令牌仅保留读取权限。 +4. 清单生成器提供只同步 Release 与必需标签校验参数,避免此步骤依赖价格服务。 + 记录目标版本公开来源,首次接入可用现有版本验证整条链路。 +5. 网站 push 自动部署 Pages。同步工作流比较 CDN `aceupdate.json` 的 SHA-256, + 最多等待 15 分钟,覆盖 Pages 构建时间与已有 5 分钟边缘缓存。 + 相同内容不创建空提交,但仍验证线上目录。 +6. 同步工作流串行执行,push 遇到外部并发更新重新拉取并生成,最多重试三次。 + 仅刷新清单及来源记录,不推送价格数据或其他页面内容。 + +## Risks / Trade-offs + +- Pages 或 GitHub 故障 → 同步步骤失败,保留已发布资产,支持重跑。 +- 网站清单已更新但 Pages 上一次部署失败 → 幂等同步不制造空提交;修复网站工作流后重跑 Pages,再重跑验证。 +- 两次发布快速连续完成 → 每次读取完整公开 Releases,避免覆盖最新版本。 +- 密钥失效 → Actions 显示认证失败,替换网站部署密钥及对应 Secret。 + +## Migration Plan + +先发布网站生成器与测试,再配置专用密钥,通过 PR 将 ACECode 工作流合入 master。 +用当前已发布版本调用同一可复用工作流,验证网站 push 自动启动 Pages 及 CDN 内容。 +回退时移除发布后的工作流调用并撤销该部署密钥;既有下载站继续工作。 diff --git a/openspec/changes/auto-refresh-public-downloads/proposal.md b/openspec/changes/auto-refresh-public-downloads/proposal.md new file mode 100644 index 00000000..0fc671da --- /dev/null +++ b/openspec/changes/auto-refresh-public-downloads/proposal.md @@ -0,0 +1,27 @@ +## Why + +ACECode 安装包发布后,公开下载站目前需要人工运行 Pages 工作流。 +将刷新接入发布成功后的流程,使下载目录在本机离线时也能自动更新。 + +## What Changes + +- `package.yml` 的 Release 发布成功后调用独立的网站同步工作流。 +- 同步工作流通过仅限 `acecode-site` 的部署密钥更新公开清单并触发 Pages。 +- 校验目标版本已发布且安装资产具有校验值;重复执行不创建空提交。 +- 同步后等待 CDN 公开清单与提交内容一致,失败在发布流水线中明确显示。 + +## Capabilities + +### New Capabilities + +- `public-release-catalog`: 公开下载清单随 GitHub Release 发布自动同步、部署和验证。 + +### Modified Capabilities + +无。 + +## Impact + +涉及 ACECode 的 GitHub Actions 和发布文档,以及独立仓库 +`tmoonlight/acecode-site` 的清单生成器、测试与部署文档。 +本机 aupdate 镜像流程、用户中心和安装包构建行为保持原有约定。 diff --git a/openspec/changes/auto-refresh-public-downloads/specs/public-release-catalog/spec.md b/openspec/changes/auto-refresh-public-downloads/specs/public-release-catalog/spec.md new file mode 100644 index 00000000..18b5f668 --- /dev/null +++ b/openspec/changes/auto-refresh-public-downloads/specs/public-release-catalog/spec.md @@ -0,0 +1,43 @@ +## Purpose + +让 ACECode 的公开下载目录随安装包发布自动更新,用户无需在本机执行刷新命令。 +目录只展示已公开的版本资产,并在部署后验证 CDN 实际内容,保证发布记录与用户看到的文件一致。 + +## ADDED Requirements + +### Requirement: 发布成功后自动更新下载站 + +系统 SHALL 在 GitHub Release 安装资产发布成功后自动刷新并部署公开下载清单。 + +#### Scenario: 标签发布成功 +- **WHEN** 标签对应的安装包发布步骤成功 +- **THEN** 自动执行公开目录同步和 Pages 部署 +- **AND** 全过程不依赖用户电脑开机 + +#### Scenario: 发布失败或非标签打包 +- **WHEN** 安装包发布失败或运行普通分支打包 +- **THEN** 不执行本次发布的网站同步 + +### Requirement: 验证公开版本与同步结果 + +系统 SHALL 拒绝缺失、草稿或没有完整下载校验值的目标版本,并在有限时间内确认 CDN 清单与生成结果一致。 + +#### Scenario: 无效目标 +- **WHEN** 请求同步的版本未公开或资产缺少有效 SHA-256 +- **THEN** 同步失败且不推送不完整目录 + +#### Scenario: CDN 尚未更新 +- **WHEN** Pages 部署失败或 CDN 未在等待期限内提供目标目录 +- **THEN** 发布流程的网站同步步骤明确失败,已有安装包与旧网页继续可用 + +### Requirement: 重试与授权边界 + +系统 SHALL 只使用公开站点仓库的专用写入授权,串行处理同步并允许同版本安全重试。 + +#### Scenario: 相同内容重复执行 +- **WHEN** 重新运行同一版本同步且目录内容未变 +- **THEN** 不创建空提交,并继续验证 CDN 目录 + +#### Scenario: 旧版本重新运行 +- **WHEN** 已有更新版本后重试旧版本同步 +- **THEN** 仍读取所有已公开 Release,不能把当前下载目录回退到旧版本 diff --git a/openspec/changes/auto-refresh-public-downloads/tasks.md b/openspec/changes/auto-refresh-public-downloads/tasks.md new file mode 100644 index 00000000..74cb9890 --- /dev/null +++ b/openspec/changes/auto-refresh-public-downloads/tasks.md @@ -0,0 +1,15 @@ +## 1. 网站同步能力 + +- [x] 1.1 增加必需标签校验与仅刷新 Release 模式,单元测试覆盖缺失、未完整发布及幂等内容。 +- [x] 1.2 更新网站部署说明并通过现有下载清单测试,发布网站仓库变更。 + +## 2. 发布衔接 + +- [x] 2.1 添加可复用工作流及 package 发布后的调用,完成工作流语法与依赖检查,补齐发布说明。 +- [x] 2.2 配置网站专用部署密钥和 ACECode Secret,验证授权限定目标仓库。 +- [ ] 2.3 通过 PR 合入 master,精确提交本任务文件并确认其他未提交内容保持。 + +## 3. 联调 + +- [ ] 3.1 用现有 Release 执行同步,确认自动 push 触发 Pages,并验证 CDN 清单内容。 +- [ ] 3.2 重复执行同版本确认没有空提交,记录成功运行链接及严格 OpenSpec 校验。