Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/actions/validate-pat/action.yml
Original file line number Diff line number Diff line change
@@ -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
51 changes: 18 additions & 33 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 鉴权修复后复验结果与基线一致
Original file line number Diff line number Diff line change
Expand Up @@ -13,89 +13,43 @@

## 当前恢复点

- 恢复点编号:`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 / releasePAT 校验收敛为同一个复用入口,避免后续修复在两段脚本间漂移
- 在 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 中可解析
- 当前仓库本地 `dotnet clean/build` 会带出既有 analyzer warnings;本轮仅修正发版配置与文档,不额外处理这些历史 warning

## 已完成

- 已确认当前版本入口为 `.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 探测
Loading
Loading