Merge pull request #294 from GeWuYou/feat/semantic-release-versioning

ci(release): 修复 semantic-release 预览鉴权
This commit is contained in:
gewuyou 2026-04-26 12:17:15 +08:00 committed by GitHub
commit 617e0bffd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 195 additions and 104 deletions

66
.github/actions/validate-pat/action.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Validate PAT
description: Validate that the release PAT can access the repository and push tags.
inputs:
pat-token:
description: Personal access token used by semantic-release.
required: true
repo-api-url:
description: GitHub repository API URL, for example https://api.github.com/repos/owner/repo.
required: true
repository:
description: Repository slug used in error messages.
required: true
missing-token-message:
description: Error message emitted when the PAT is absent.
required: true
runs:
using: composite
steps:
- name: Validate PAT can push
shell: bash
env:
PAT_TOKEN: ${{ inputs.pat-token }}
REPO_API_URL: ${{ inputs.repo-api-url }}
REPOSITORY: ${{ inputs.repository }}
MISSING_TOKEN_MESSAGE: ${{ inputs.missing-token-message }}
run: |
if [ -z "${PAT_TOKEN}" ]; then
echo "::error::${MISSING_TOKEN_MESSAGE}"
exit 1
fi
response_file="$(mktemp)"
trap 'rm -f "${response_file}"' EXIT
status_code="$(
curl -sS -o "${response_file}" -w "%{http_code}" \
-H "Authorization: Bearer ${PAT_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${REPO_API_URL}"
)"
case "${status_code}" in
200)
# The repository endpoint returns 200 for read-only tokens as well.
# semantic-release still performs a remote push probe, so require push permission here.
push_ok="$(jq -r '.permissions.push // false' "${response_file}")"
if [ "${push_ok}" != "true" ]; then
echo "::error::PAT_TOKEN can read ${REPOSITORY} but lacks push permission. semantic-release requires contents:write."
cat "${response_file}"
exit 1
fi
;;
401|403)
echo "::error::PAT_TOKEN is invalid or lacks access to ${REPOSITORY} (HTTP ${status_code})."
cat "${response_file}"
exit 1
;;
*)
echo "::error::Failed to validate PAT_TOKEN against ${REPO_API_URL} (HTTP ${status_code})."
cat "${response_file}"
exit 1
;;
esac

View File

@ -27,6 +27,16 @@ jobs:
persist-credentials: false
ref: ${{ github.sha }}
# semantic-release 在 dry-run 中仍会执行一次 git push --dry-run 权限探测。
# 这里提前要求与正式 release 相同的 PAT避免 github-actions[bot] 因只读上下文触发误导性的 403。
- name: Validate PAT token
uses: ./.github/actions/validate-pat
with:
pat-token: ${{ secrets.PAT_TOKEN }}
repo-api-url: ${{ github.api_url }}/repos/${{ github.repository }}
repository: ${{ github.repository }}
missing-token-message: PAT_TOKEN is required because semantic-release preview performs a git push --dry-run permission check.
# preview 始终先运行,用于给当前 SHA 生成待发布版本预览。
- name: Semantic release preview
id: semantic_release
@ -37,7 +47,7 @@ jobs:
extra_plugins: |
conventional-changelog-conventionalcommits@9.1.0
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
- name: Show preview result
run: |
@ -59,6 +69,7 @@ jobs:
echo "- Last tag: \`${{ steps.semantic_release.outputs.last_release_git_tag }}\`"
echo "- Next version: \`${{ steps.semantic_release.outputs.new_release_version }}\`"
echo "- Next tag: \`${{ steps.semantic_release.outputs.new_release_git_tag }}\`"
echo "- Preview auth: uses \`PAT_TOKEN\` because semantic-release dry-run still performs a remote push permission probe."
echo "- Snapshot semantics: this preview is pinned to dispatch SHA \`${{ github.sha }}\`; commits added to \`main\` after the run starts are not included."
if [ "${RELEASE_PUBLISHED}" = "true" ] && [ -n "${RELEASE_NOTES}" ]; then
echo
@ -93,38 +104,12 @@ jobs:
ref: ${{ github.sha }}
- name: Validate PAT token
env:
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
REPO_API_URL: ${{ github.api_url }}/repos/${{ github.repository }}
run: |
if [ -z "${PAT_TOKEN}" ]; then
echo "::error::PAT_TOKEN is required because a tag created with GITHUB_TOKEN will not trigger publish.yml."
exit 1
fi
response_file="$(mktemp)"
status_code="$(
curl -sS -o "${response_file}" -w "%{http_code}" \
-H "Authorization: Bearer ${PAT_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${REPO_API_URL}"
)"
case "${status_code}" in
200)
;;
401|403)
echo "::error::PAT_TOKEN is invalid or lacks access to ${GITHUB_REPOSITORY} (HTTP ${status_code})."
cat "${response_file}"
exit 1
;;
*)
echo "::error::Failed to validate PAT_TOKEN against ${REPO_API_URL} (HTTP ${status_code})."
cat "${response_file}"
exit 1
;;
esac
uses: ./.github/actions/validate-pat
with:
pat-token: ${{ secrets.PAT_TOKEN }}
repo-api-url: ${{ github.api_url }}/repos/${{ github.repository }}
repository: ${{ github.repository }}
missing-token-message: PAT_TOKEN is required because a tag created with GITHUB_TOKEN will not trigger publish.yml.
- name: Semantic release
id: semantic_release

View File

@ -0,0 +1,77 @@
# Semantic Release 版本迁移归档2026-04-26
## 归档范围
- Phase 1 初始迁移结论
- `SEMREL-RP-001``SEMREL-RP-003` 期间已稳定的 PR review 修复
- active tracking 中已不需要继续占据默认恢复入口的历史验证明细
## 历史完成项
- 已确认当前版本入口为 `.github/workflows/auto-tag.yml`,现状始终执行 `PATCH + 1`
- 已确认当前 `.github/workflows/publish.yml` 由 tag 触发,并负责 `.nupkg` 打包、发布和 GitHub Release
- 已确认最新 tag 为 `v0.0.222`
- 已确认 `v0.0.222..HEAD` 之间存在 `feat(...)` 提交,按目标规则首次 dry-run 预期版本应为 `v0.1.0`
- 已新增 `.releaserc.json`,仅保留 `@semantic-release/commit-analyzer`
`@semantic-release/release-notes-generator`,避免 `semantic-release` 直接创建 GitHub Release
- 已将 `.releaserc.json``commit-analyzer` / `release-notes-generator` 同步切换到 `conventionalcommits`
preset并显式声明
- `breaking -> major`
- `revert -> patch`
- `feat -> minor`
- `fix/perf/refactor -> patch`
- `docs/test/chore/build/ci/style -> no release`
- 已将 `.github/workflows/auto-tag.yml` 重写为:
- `workflow_dispatch` 启动后总是先跑 `preview`
- `preview` 只执行 dry-run输出 `last_tag``next_version``next_tag`
- `release` job 依赖 `preview` 输出,并通过 `release-approval` environment 暂停等待人工确认
- 人工批准后,`release` 在同一 SHA 上执行真实打 tag并把 preview / release 结果都写入 job summary
- 已按上一轮 PR review 修复 `auto-tag.yml`
- 删除 preview job 中与 job 级 `if` 重复的运行时分支校验
- 为 release job 增加 `needs.preview.result == 'success'` 守卫
- 为 preview / release 的 semantic-release action 显式安装 `conventional-changelog-conventionalcommits@9.1.0`
- 在 release 前通过 GitHub API 校验 `PAT_TOKEN` 是否真实可访问当前仓库
- 在 preview / release summary 中补充 snapshot 语义与生成的 release notes
- 已修复 preview 链路的第一轮鉴权前提:
- 在 preview 开始前通过 GitHub API 校验 `PAT_TOKEN`
- 将 preview 的 `semantic-release` 令牌从 `${{ github.token }}` 切换为 `${{ secrets.PAT_TOKEN }}`
- 在 preview summary 中明确说明 dry-run 仍会执行远端 push 权限探测,避免将 403 误判为版本计算失败
- 已明确真实打 tag 仍使用 `PAT_TOKEN`,因为 `GITHUB_TOKEN` 推送的 tag 不会继续触发 `publish.yml`
- 已更新 `AGENTS.md` 的 Conventional Commit 规则,显式补充:
- `fix/perf/refactor -> patch`
- `docs/test/chore/build/ci/style -> no release`
- `BREAKING CHANGE``!` header -> major
- 已移除基于 `workflow_run``[release ci]` 的自动发版门闸,后续版本预览与真实发版都由维护者手动触发
- 已将 release 流程从“两次独立 workflow_dispatch”收敛为“同一次 run 里 preview + 审批 + release”的链路
- 已精简 active trace移除已废弃的 `release_mode=preview|release` 中间方案,保留当前有效恢复点
## 历史验证
- `git describe --tags --abbrev=0`
- 结果:通过
- 备注:当前最新 tag 为 `v0.0.222`
- `git log --pretty=format:%h%x09%s v0.0.222..HEAD`
- 结果:通过
- 备注:最近版本窗口内存在多条 `feat(...)`,后续 dry-run 预期应提升 `minor`
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`
- 结果:通过
- 备注:`GFramework.Core.Abstractions``GFramework.Cqrs.Abstractions` Release 构建通过,`0 warning / 0 error`
- `npx --yes semantic-release --dry-run --no-ci`
- 结果:受阻
- 备注:当前工作树的本地 tag 历史在 `git fetch --tags` 阶段出现 `would clobber existing tag` 冲突,不能直接作为 dry-run 环境
- `git clone --branch main --single-branch git@github.com:GeWuYou/GFramework.git /tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注:已建立干净临时克隆用于 dry-run 验证
- `npx --yes semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注dry-run 成功识别 `v0.0.222` 为最新 release并分析 `269` 个提交;按当前规则会提升到下一次 `minor` 发布,预期 tag 为 `v0.1.0`
- `npx --yes -p semantic-release -p conventional-changelog-conventionalcommits@9.1.0 semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注:成功加载 `@semantic-release/commit-analyzer``@semantic-release/release-notes-generator`,证明
`conventionalcommits` preset 包可被解析;本次 dry-run 未继续出版本,是因为干净克隆的 `main` 已落后远端
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`(手动发版入口调整后复验)
- 结果:通过
- 备注:`0 warning / 0 error`
- `dotnet build GFramework.sln -c Release`
- 结果:通过
- 备注Release 构建通过,`639 warning / 0 error`warning 为仓库既有基线preview 鉴权修复后复验结果与基线一致

View File

@ -13,16 +13,18 @@
## 当前恢复点
- 恢复点编号:`SEMREL-RP-002`
- 恢复点编号:`SEMREL-RP-004`
- 当前阶段:`Phase 2`
- 当前焦点:
- `.releaserc.json``BREAKING CHANGE``feat!:` / `feat(scope)!:``major` 语义与文档保持一致
- `auto-tag.yml` 的 preview / release 输出与 PR review 建议对齐,避免 release notes 被静默丢弃
- 提前校验 `PAT_TOKEN` 的真实可用性,并把当前 PR review 修复结果同步回 `AGENTS.md` 与 active trace
- 将 preview / release 的 PAT 校验收敛为同一个复用入口,避免后续修复在两段脚本间漂移
- 在 PAT 校验阶段提前识别“仅有 read、没有 push”的 token真正覆盖 `git push --dry-run` 的权限前提
- 将 active tracking 中已稳定的历史完成项归档,恢复默认入口的可读性
### 已知风险
- `GITHUB_TOKEN` 推送 tag 不会再触发另一个 workflow真实发布仍需要 `PAT_TOKEN`
- `semantic-release` preview 虽然不会真实推送 tag但仍会执行远端 `git push --dry-run` 权限探测;如果 PAT 仅具备
read 权限、没有 `contents:write`,仍然会先于版本分析阶段失败
- `semantic-release` 的版本判断完全依赖 Conventional Commits不规范提交会直接影响版本计算
- `cycjimmy/semantic-release-action@v6` 需要在 preview / release 两端都安装 `conventional-changelog-conventionalcommits`
以保证 `conventionalcommits` preset 在 GitHub Actions 中可解析
@ -30,72 +32,24 @@
## 已完成
- 已确认当前版本入口为 `.github/workflows/auto-tag.yml`,现状始终执行 `PATCH + 1`
- 已确认当前 `.github/workflows/publish.yml` 由 tag 触发,并负责 `.nupkg` 打包、发布和 GitHub Release
- 已确认最新 tag 为 `v0.0.222`
- 已确认 `v0.0.222..HEAD` 之间存在 `feat(...)` 提交,按目标规则首次 dry-run 预期版本应为 `v0.1.0`
- 已新增 `.releaserc.json`,仅保留 `@semantic-release/commit-analyzer`
`@semantic-release/release-notes-generator`,避免 `semantic-release` 直接创建 GitHub Release
- 已将 `.releaserc.json``commit-analyzer` / `release-notes-generator` 同步切换到 `conventionalcommits`
preset并显式声明
- `breaking -> major`
- `revert -> patch`
- `feat -> minor`
- `fix/perf/refactor -> patch`
- `docs/test/chore/build/ci/style -> no release`
- 已将 `.github/workflows/auto-tag.yml` 重写为:
- `workflow_dispatch` 启动后总是先跑 `preview`
- `preview` 只执行 dry-run输出 `last_tag``next_version``next_tag`
- `release` job 依赖 `preview` 输出,并通过 `release-approval` environment 暂停等待人工确认
- 人工批准后,`release` 在同一 SHA 上执行真实打 tag并把 preview / release 结果都写入 job summary
- 已按 PR review 修复 `auto-tag.yml`
- 删除 preview job 中与 job 级 `if` 重复的运行时分支校验
- 为 release job 增加 `needs.preview.result == 'success'` 守卫
- 为 preview / release 的 semantic-release action 显式安装 `conventional-changelog-conventionalcommits@9.1.0`
- 在 release 前通过 GitHub API 校验 `PAT_TOKEN` 是否真实可访问当前仓库
- 在 preview / release summary 中补充 snapshot 语义与生成的 release notes
- 已明确真实打 tag 仍使用 `PAT_TOKEN`,因为 `GITHUB_TOKEN` 推送的 tag 不会继续触发 `publish.yml`
- 已更新 `AGENTS.md` 的 Conventional Commit 规则,显式补充:
- `fix/perf/refactor -> patch`
- `docs/test/chore/build/ci/style -> no release`
- `BREAKING CHANGE``!` header -> major
- 已移除基于 `workflow_run``[release ci]` 的自动发版门闸,后续版本预览与真实发版都由维护者手动触发
- 已将 release 流程从“两次独立 workflow_dispatch”收敛为“同一次 run 里 preview + 审批 + release”的链路
- 已精简 active trace移除已废弃的 `release_mode=preview|release` 中间方案,保留当前有效恢复点
- 历史迁移结论与 `SEMREL-RP-001``SEMREL-RP-003` 的稳定完成项已归档到
`ai-plan/public/semantic-release-versioning/archive/todos/semantic-release-versioning-2026-04-26.md`
- 已将 preview / release 两段重复的 PAT 校验提取到 `.github/actions/validate-pat/action.yml`
- 已在 PAT 校验中补充 `permissions.push` 断言,避免 read-only token 通过 API 探活却在
`semantic-release``git push --dry-run` 阶段才失败
- 已为 PAT 校验的 `mktemp` 文件补充 `trap` 清理,避免异常退出时遗留临时文件路径干扰日志
- 已同步更新 active trace 到 `SEMREL-RP-004`,记录本轮 PR review 收敛结果
## 验证
- `git describe --tags --abbrev=0`
- 结果:通过
- 备注:当前最新 tag 为 `v0.0.222`
- `git log --pretty=format:%h%x09%s v0.0.222..HEAD`
- 结果:通过
- 备注:最近版本窗口内存在多条 `feat(...)`,后续 dry-run 预期应提升 `minor`
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`
- 结果:通过
- 备注:`GFramework.Core.Abstractions``GFramework.Cqrs.Abstractions` Release 构建通过,`0 warning / 0 error`
- `npx --yes semantic-release --dry-run --no-ci`
- 结果:受阻
- 备注:当前工作树的本地 tag 历史在 `git fetch --tags` 阶段出现 `would clobber existing tag` 冲突,不能直接作为 dry-run 环境
- `git clone --branch main --single-branch git@github.com:GeWuYou/GFramework.git /tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注:已建立干净临时克隆用于 dry-run 验证
- `npx --yes semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注dry-run 成功识别 `v0.0.222` 为最新 release并分析 `269` 个提交;按当前规则会提升到下一次 `minor` 发布,预期 tag 为 `v0.1.0`
- `npx --yes -p semantic-release -p conventional-changelog-conventionalcommits@9.1.0 semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`
- 结果:通过
- 备注:成功加载 `@semantic-release/commit-analyzer``@semantic-release/release-notes-generator`,证明
`conventionalcommits` preset 包可被解析;本次 dry-run 未继续出版本,是因为干净克隆的 `main` 已落后远端
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`(手动发版入口调整后复验)
- 结果:通过
- 备注:`0 warning / 0 error`
- `dotnet build GFramework.sln -c Release`
- 结果:通过
- 备注Release 构建完成,`639 warning / 0 error`warning 为仓库既有基线,与本轮 workflow / doc 改动无新增关联项
- 备注Release 构建通过,`639 warning / 0 error`warning 为仓库既有基线preview 鉴权修复后与本轮 PAT 校验收敛后复验结果一致
- 更早阶段的 dry-run / tag /抽象项目验证已归档到
`ai-plan/public/semantic-release-versioning/archive/todos/semantic-release-versioning-2026-04-26.md`
## 下一步
1. 复核当前 PR review 的 open threads 是否只剩等待 push 的已修复项
2. 将本轮修复提交到当前分支,等待 GitHub reviewer 重新评估
3. 若后续需要,再在真实仓库主线最新快照上复验一次 `semantic-release` dry-run 结果展示
1. 手动重跑 `Semantic Release Version and Tag` 的 preview job确认 read-only PAT 会在校验步骤提前失败、可写 PAT 不再进入 `git push --dry-run ... 403`
2. 推送本轮修复后重新抓取 PR review确认 CodeRabbit / Greptile 的 open threads 已转为过时或可关闭
3. 如 CI 仍报告权限边界问题,再决定是否将 PAT 校验升级为更贴近真实链路的远端 git 探测

View File

@ -2,7 +2,7 @@
## 2026-04-26
### 当前恢复点SEMREL-RP-002
### 当前恢复点SEMREL-RP-004
- 当前链路:
- `workflow_dispatch` 手动启动
@ -17,17 +17,25 @@
- `breaking -> major`
- 当前 workflow 加固:
- `release` 额外要求 `needs.preview.result == 'success'`
- `PAT_TOKEN` 在真实 release 前通过 GitHub API 做存活性校验
- `PAT_TOKEN` 通过复用的 composite action 统一校验
- GitHub API 校验额外断言 `.permissions.push == true`,避免 read-only PAT 混过 preview
- preview / release summary 会展示 snapshot 语义与生成的 release notes
- `preview` 改为先校验并使用 `PAT_TOKEN`,避免 `github-actions[bot]` 在 dry-run 的远端 push 权限探测中触发 403
### 本轮关键决策
- 保留 `@semantic-release/release-notes-generator`,但不再让它白跑:
- 继续生成 notes
- 将 notes 写入 GitHub Actions summary
- preview 与 release 共用 `PAT_TOKEN`
- `semantic-release` dry-run 仍会执行 `git push --dry-run`
- preview 如果继续使用 `${{ github.token }}`,会先被 `github-actions[bot]` 的仓库写权限拦住,日志不再具有可读性
- API 探活必须覆盖 push 权限:
- 单纯 `GET /repos/{owner}/{repo}``200` 只能证明 read access
- 本轮直接读取响应体里的 `permissions.push`,让 preview 在更接近真实失败原因的位置终止
- 不保留已废弃的 `release_mode=preview|release` 中间方案:
- active trace 只保留当前有效链路
- 历史演进以 tracking 文档的已完成项为准
- 历史演进以 tracking 归档文件为准active tracking 仅保留当前恢复入口
### 验证结论
@ -36,9 +44,10 @@
- 本次 dry-run 未继续出版本,因为干净克隆的 `main` 已落后远端
2. `dotnet build GFramework.sln -c Release`
- 通过,`639 warning / 0 error`
- warning 为仓库既有基线,本轮未新增关联 warning
- warning 为仓库既有基线,本轮 workflow / ai-plan 调整未新增关联 warning
### 下一步
1. 复查当前 PR 的 open review threads 是否只剩等待 push 的已修复项
2. 创建提交并推送当前分支
1. 重跑 `auto-tag.yml` 的 preview确认 read-only PAT 会在校验步骤提前失败、可写 PAT 不再落到 `EGITNOPERMISSION`
2. 复查当前 PR 的 open review threads 是否已与本地修复对齐
3. 创建提交并推送当前分支