-
Notifications
You must be signed in to change notification settings - Fork 0
148 lines (136 loc) · 4.98 KB
/
Copy pathpublish-pypi.yml
File metadata and controls
148 lines (136 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
name: Publish to PyPI
on:
push:
tags:
- "v*.*.*"
jobs:
# GATE — block build/publish/release until the required CI workflows pass
# on this exact tagged commit. Polls the Actions API; a workflow that
# concludes in any non-success terminal state aborts the whole pipeline.
# (CI_DOES_THE_RELEASE, 2026-07-16 — matches the same fix applied to the
# Python CLI repo after v10.12.2 shipped to PyPI with broken metadata
# while its own CI test job was still failing on that commit.)
require_ci_green:
name: Require CI to pass on this commit
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Poll required workflows
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ github.sha }}
REQUIRED: "ci.yml codeql.yml secret-scan.yml"
run: |
deadline=$(( $(date +%s) + 2400 )) # 40 min ceiling
while :; do
pending=""
for wf in $REQUIRED; do
out=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$wf" \
--commit "$SHA" --limit 1 \
--json conclusion,status \
--jq '.[0] | (.conclusion // "none") + " " + (.status // "none")' \
2>/dev/null || true)
[ -z "$out" ] && out="none none"
concl=${out%% *}
echo " $wf -> conclusion=$concl"
case "$concl" in
success) ;;
failure|cancelled|timed_out|startup_failure|action_required)
echo "::error::$wf concluded '$concl' on $SHA — refusing to publish"
exit 1 ;;
*) pending="$pending $wf" ;;
esac
done
if [ -z "$pending" ]; then
echo "All required CI workflows passed on $SHA."
break
fi
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "::error::Timed out waiting for required CI:$pending"
exit 1
fi
echo "Waiting for:$pending"
sleep 25
done
build:
name: Build distribution
needs: require_ci_green
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install build
run: python -m pip install --upgrade build
- name: Build wheel and sdist
run: python -m build
- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: dist/
publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write # required for OIDC trusted-publisher auth
steps:
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
name: python-package-distributions
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
release:
name: Publish GitHub Release
needs: publish
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Extract release notes from CHANGELOG.md
id: notes
run: |
VERSION="${GITHUB_REF_NAME#v}"
# VERSION passed to awk via -v, matched with plain string ops (no
# regex on it) — never interpolated into the program text.
# GITHUB_REF_NAME is attacker-influenceable via the tag name, so
# string interpolation there would be an awk/command-injection
# vector. Preserves the original "## VERSION" + (space|end-of-line)
# boundary check without needing a regex over VERSION itself.
awk -v ver="$VERSION" '
{
prefix = "## " ver
if (index($0, prefix) == 1) {
rest = substr($0, length(prefix) + 1)
if (rest == "" || substr(rest, 1, 1) == " ") { flag=1; next }
}
}
/^## / { flag=0 }
flag { print }
' CHANGELOG.md > /tmp/release-notes.md
if ! grep -q '[^[:space:]]' /tmp/release-notes.md; then
echo "::error::No CHANGELOG.md entry found for ${VERSION} — add a '## ${VERSION}' section before tagging."
exit 1
fi
- name: Create or update GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${GITHUB_REF_NAME}"
TITLE="${TAG#v}"
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TITLE" --notes-file /tmp/release-notes.md
else
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TITLE" --notes-file /tmp/release-notes.md
fi