返回 last30days-skill
tag-release.yml
根目录 / .github / workflows / tag-release.yml
1 name: Tag release
2
3 # After a prepare-release PR merges to main, create the vX.Y.Z tag and
4 # dispatch release.yml (GITHUB_TOKEN tag pushes do not start other workflows).
5
6 on:
7 push:
8 branches:
9 - main
10
11 permissions: {}
12
13 jobs:
14 tag:
15 runs-on: ubuntu-latest
16 # Only act on the release-prep commit shape produced by prepare-release.yml
17 # (or an equivalent manual chore(release) commit). Quote the expression —
18 # a bare `chore(release):` colon is invalid YAML and fails the whole
19 # workflow before any job runs. Use contains (not startsWith) so merge
20 # commits whose subject is "Merge pull request #N …" still match when the
21 # PR title is in the body.
22 if: "contains(github.event.head_commit.message, 'chore(release): bump version to ')"
23 permissions:
24 contents: write
25 actions: write
26 pull-requests: read
27 steps:
28 - name: Checkout
29 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30 with:
31 fetch-depth: 0
32 persist-credentials: false
33
34 - name: Create annotated tag and dispatch Release
35 env:
36 GH_TOKEN: ${{ github.token }}
37 HEAD_MSG: ${{ github.event.head_commit.message }}
38 HEAD_SHA: ${{ github.sha }}
39 REPOSITORY: ${{ github.repository }}
40 DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
41 run: |
42 set -euo pipefail
43 # Scan every line — merge commits put the chore(release) title in the body.
44 VERSION="$(printf '%s\n' "${HEAD_MSG}" | sed -n 's/^chore(release): bump version to \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' | head -n1)"
45 if [ -z "${VERSION}" ]; then
46 echo "Could not parse version from commit message: ${HEAD_MSG}"
47 exit 1
48 fi
49 TAG="v${VERSION}"
50
51 PY_VERSION="$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
52 if [ "${PY_VERSION}" != "${VERSION}" ]; then
53 echo "Commit message version (${VERSION}) does not match pyproject.toml (${PY_VERSION})"
54 exit 1
55 fi
56
57 # Require the merged PR to carry the repository-controlled `release`
58 # label so a matching title alone cannot mint a tag.
59 PR_NUMBER="$(gh api "repos/${REPOSITORY}/commits/${HEAD_SHA}/pulls" \
60 --jq 'map(select(.base.ref == env.DEFAULT_BRANCH)) | .[0].number // empty')"
61 if [ -z "${PR_NUMBER}" ]; then
62 echo "No PR found for ${HEAD_SHA} into ${DEFAULT_BRANCH} — refusing to tag."
63 exit 1
64 fi
65 if ! gh api "repos/${REPOSITORY}/issues/${PR_NUMBER}/labels" \
66 --jq '.[].name' | grep -qx 'release'; then
67 echo "PR #${PR_NUMBER} lacks the 'release' label — refusing to tag."
68 exit 1
69 fi
70
71 if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
72 echo "Tag ${TAG} already exists locally — nothing to do."
73 exit 0
74 fi
75 if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
76 echo "Tag ${TAG} already exists on origin — nothing to do."
77 exit 0
78 fi
79
80 git config user.name "github-actions[bot]"
81 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
82 git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${REPOSITORY}.git"
83 git tag -a "${TAG}" -m "Release ${TAG}"
84 git push origin "refs/tags/${TAG}"
85 echo "Created and pushed ${TAG}"
86
87 # GITHUB_TOKEN tag pushes do not trigger release.yml; dispatch it.
88 gh workflow run release.yml \
89 --ref "${DEFAULT_BRANCH}" \
90 -f "tag=${TAG}"
91 echo "Dispatched release.yml for ${TAG}"
92
92 lines YAML