|
1 | 1 | # ============================================================================= |
2 | | -# coding-proxy: PyPI Publishing Pipeline (4-Stage Unified Pipeline) |
| 2 | +# coding-proxy: PyPI Publishing Pipeline (3-Stage Unified Pipeline) |
3 | 3 | # ============================================================================= |
4 | 4 | # Trigger: GitHub Release publication event (release.types: [published]) |
5 | 5 | # |
6 | | -# Architecture: 4-Stage Serial Pipeline (prerelease only — 所有生产发布均走预发布) |
| 6 | +# Architecture: 3-Stage Serial Pipeline (prerelease only — 所有生产发布均走预发布) |
7 | 7 | # |
8 | 8 | # Stage 1 (build): 矩阵构建 3.12/3.13/3.14,上传 artifacts |
9 | 9 | # Stage 2 (publish-testpypi): 发布到 TestPyPI 供验证 |
10 | 10 | # Stage 3 (publish-to-pypi): ⏸ Production Approval Gate (environment: pypi) |
11 | 11 | # 审批通过后自动:Promote Release + Publish to PyPI |
12 | | -# Stage 4 (update-changelog): 从 Release notes 生成 Changelog PR → master |
13 | 12 | # |
14 | 13 | # 设计决策 — Stage 3 合并审批与发布的理由: |
15 | 14 | # pypa/gh-action-pypi-publish@release/v1 始终优先尝试 OIDC Trusted Publishing。 |
|
21 | 20 | # Pre-requisites — 需要在 GitHub Settings 一次性手动配置: |
22 | 21 | # 1. repo → Settings → Environments → "pypi" |
23 | 22 | # → Required reviewers: 添加审批人员(Production Approval Gate 所需) |
24 | | -# 2. repo → Settings → Actions → General → Workflow permissions |
25 | | -# → "Read and write permissions"(Stage 4 推分支所需) |
26 | | -# → 勾选 "Allow GitHub Actions to create and approve pull requests" |
27 | 23 | # |
28 | 24 | # Authentication: |
29 | 25 | # - TEST_PYPI_API_TOKEN: TestPyPI API Token(repository secret) |
@@ -198,129 +194,3 @@ jobs: |
198 | 194 | skip-existing: true |
199 | 195 | verbose: true |
200 | 196 |
|
201 | | - # =========================================================================== |
202 | | - # Stage 5: UPDATE CHANGELOG -- 自动生成 Changelog PR → master |
203 | | - # =========================================================================== |
204 | | - update-changelog: |
205 | | - name: Update Changelog |
206 | | - runs-on: ubuntu-latest |
207 | | - needs: publish-to-pypi |
208 | | - if: github.event.release.prerelease == true |
209 | | - timeout-minutes: 10 |
210 | | - permissions: |
211 | | - contents: write # git push 新分支 |
212 | | - pull-requests: write # 创建 PR |
213 | | - |
214 | | - steps: |
215 | | - - name: Checkout repository |
216 | | - uses: actions/checkout@v4 |
217 | | - with: |
218 | | - fetch-depth: 0 |
219 | | - persist-credentials: true |
220 | | - |
221 | | - - name: Extract stable version from prerelease tag |
222 | | - id: version |
223 | | - run: | |
224 | | - PRERELEASE_TAG="${{ github.ref_name }}" |
225 | | - # v0.2.0a1 → v0.2.0(移除预发布后缀 a*/b*/rc*) |
226 | | - STABLE_VERSION=$(echo "$PRERELEASE_TAG" | sed 's/\(v[0-9]*\.[0-9]*\.[0-9]*\).*/\1/') |
227 | | - echo "prerelease_tag=$PRERELEASE_TAG" >> "$GITHUB_OUTPUT" |
228 | | - echo "stable_version=$STABLE_VERSION" >> "$GITHUB_OUTPUT" |
229 | | - echo "branch_name=chore/changelog-$STABLE_VERSION" >> "$GITHUB_OUTPUT" |
230 | | - echo "Extracted: $PRERELEASE_TAG → $STABLE_VERSION" |
231 | | -
|
232 | | - - name: Check if Changelog entry already exists |
233 | | - id: check |
234 | | - run: | |
235 | | - STABLE="${{ steps.version.outputs.stable_version }}" |
236 | | - if grep -qF "[$STABLE]" CHANGELOG.md; then |
237 | | - echo "exists=true" >> "$GITHUB_OUTPUT" |
238 | | - echo "ℹ️ Changelog entry for $STABLE already exists, skipping PR creation" |
239 | | - else |
240 | | - echo "exists=false" >> "$GITHUB_OUTPUT" |
241 | | - echo "✅ No existing entry for $STABLE, will create PR" |
242 | | - fi |
243 | | -
|
244 | | - - name: Fetch release body and update CHANGELOG.md |
245 | | - if: steps.check.outputs.exists == 'false' |
246 | | - uses: actions/github-script@v7 |
247 | | - with: |
248 | | - script: | |
249 | | - const fs = require('fs'); |
250 | | - const { owner, repo } = context.repo; |
251 | | -
|
252 | | - // 通过 API 获取 Release body,避免 shell 特殊字符注入问题 |
253 | | - const release = await github.rest.repos.getReleaseByTag({ |
254 | | - owner, |
255 | | - repo, |
256 | | - tag: '${{ steps.version.outputs.prerelease_tag }}', |
257 | | - }); |
258 | | -
|
259 | | - const stable = '${{ steps.version.outputs.stable_version }}'; |
260 | | - const date = new Date().toISOString().slice(0, 10); |
261 | | - const releaseUrl = `https://github.com/${owner}/${repo}/releases/tag/${{ steps.version.outputs.prerelease_tag }}`; |
262 | | - const body = release.data.body || ''; |
263 | | -
|
264 | | - // 构建新条目 |
265 | | - const newEntry = `\n## [${stable}](${releaseUrl}) — ${date}\n\n${body}\n`; |
266 | | -
|
267 | | - // 读取 CHANGELOG.md,在 ## [Unreleased] 行后插入新条目 |
268 | | - let content = fs.readFileSync('CHANGELOG.md', 'utf8'); |
269 | | - const marker = '## [Unreleased]'; |
270 | | - const idx = content.indexOf(marker); |
271 | | - if (idx === -1) { |
272 | | - core.setFailed('CHANGELOG.md 中未找到 ## [Unreleased] 标记,无法插入条目'); |
273 | | - return; |
274 | | - } |
275 | | - const insertPos = idx + marker.length; |
276 | | - content = content.slice(0, insertPos) + newEntry + content.slice(insertPos); |
277 | | - fs.writeFileSync('CHANGELOG.md', content, 'utf8'); |
278 | | - core.info(`✅ CHANGELOG.md updated with entry for ${stable}`); |
279 | | -
|
280 | | - - name: Create branch and commit |
281 | | - if: steps.check.outputs.exists == 'false' |
282 | | - run: | |
283 | | - BRANCH="${{ steps.version.outputs.branch_name }}" |
284 | | - STABLE="${{ steps.version.outputs.stable_version }}" |
285 | | -
|
286 | | - git config user.name "github-actions[bot]" |
287 | | - git config user.email "github-actions[bot]@users.noreply.github.com" |
288 | | -
|
289 | | - git checkout -b "$BRANCH" |
290 | | - git add CHANGELOG.md |
291 | | - git commit -m "docs(changelog): add entry for $STABLE" |
292 | | - git push origin "$BRANCH" |
293 | | -
|
294 | | - - name: Create Pull Request |
295 | | - if: steps.check.outputs.exists == 'false' |
296 | | - uses: actions/github-script@v7 |
297 | | - with: |
298 | | - script: | |
299 | | - const { owner, repo } = context.repo; |
300 | | - const stable = '${{ steps.version.outputs.stable_version }}'; |
301 | | - const branch = '${{ steps.version.outputs.branch_name }}'; |
302 | | - const prereleaseTag = '${{ steps.version.outputs.prerelease_tag }}'; |
303 | | - const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}`; |
304 | | -
|
305 | | - const pr = await github.rest.pulls.create({ |
306 | | - owner, |
307 | | - repo, |
308 | | - title: `docs(changelog): 补充 ${stable} 发版说明`, |
309 | | - body: [ |
310 | | - `## 摘要`, |
311 | | - ``, |
312 | | - `自动生成的 PR,为 **${stable}** 正式版补充 CHANGELOG 条目。`, |
313 | | - ``, |
314 | | - `- 来源:[\`${prereleaseTag}\`](https://github.com/${owner}/${repo}/releases/tag/${prereleaseTag}) Release notes`, |
315 | | - `- 目标分支:\`master\``, |
316 | | - ``, |
317 | | - `请审阅 CHANGELOG 条目后合并。`, |
318 | | - ``, |
319 | | - `> 🤖 由 [发布流水线](${runUrl}) 自动生成`, |
320 | | - ].join('\n'), |
321 | | - head: branch, |
322 | | - base: 'master', |
323 | | - }); |
324 | | -
|
325 | | - core.notice(`✅ PR created: ${pr.data.html_url}`); |
326 | | -
|
0 commit comments