-
Notifications
You must be signed in to change notification settings - Fork 3
174 lines (157 loc) · 6.49 KB
/
Copy pathrelease.yml
File metadata and controls
174 lines (157 loc) · 6.49 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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
name: Release
# Manual release: bump VERSION, build the sdist + wheel, publish to PyPI via
# Trusted Publishing (OIDC — no stored token), then commit + tag + create a
# GitHub Release.
on:
workflow_dispatch:
inputs:
bump:
description: "Semver bump applied to VERSION file."
required: true
type: choice
default: patch
options: [patch, minor, major]
override:
description: "Optional explicit version (overrides bump). Leave blank to auto-bump."
required: false
type: string
default: ""
dry_run:
description: "Skip PyPI publish + tag; only build + show the new version."
required: false
type: boolean
default: false
permissions:
contents: write # commit + tag + create release
id-token: write # PyPI Trusted Publishing (OIDC)
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
env:
BUMP: ${{ inputs.bump }}
OVERRIDE: ${{ inputs.override }}
PYTHONHASHSEED: "0" # deterministic gate runtime (match gate.yml)
steps:
# Org GitHub App token (no PAT — never expires). Authorizes BOTH the
# release commit/tag push to protected main (the App is a bypass actor on
# the org ruleset) AND the downstream hub dispatch. Scoped to this repo +
# the hub. Minted first so checkout can push with it.
- name: Mint org App token
id: app
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PINEFORGE_APP_ID }}
private-key: ${{ secrets.PINEFORGE_APP_PRIVATE_KEY }}
owner: pineforge-4pass
repositories: pineforge-codegen-oss,pineforge-release
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
# App token bypasses the main ruleset for the release commit/tag push.
token: ${{ steps.app.outputs.token }}
- name: Compute new version
id: ver
run: |
set -euo pipefail
cur=$(tr -d '[:space:]' < VERSION)
if [ -n "$OVERRIDE" ]; then
new="$OVERRIDE"
else
IFS='.' read -r maj min pat <<< "$cur"
case "$BUMP" in
patch) pat=$((pat+1)) ;;
minor) min=$((min+1)); pat=0 ;;
major) maj=$((maj+1)); min=0; pat=0 ;;
*) echo "::error::bad bump '$BUMP'"; exit 1 ;;
esac
new="${maj}.${min}.${pat}"
fi
if ! [[ "$new" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-+].+)?$ ]]; then
echo "::error::computed '$new' not semver"; exit 1
fi
if [ "$new" = "$cur" ] && [ -z "$OVERRIDE" ]; then
echo "::error::version unchanged ($cur)"; exit 1
fi
echo "cur=$cur" >> "$GITHUB_OUTPUT"
echo "new=$new" >> "$GITHUB_OUTPUT"
echo "Release $cur -> $new"
- name: Write VERSION
env:
NEW_VERSION: ${{ steps.ver.outputs.new }}
run: printf '%s\n' "$NEW_VERSION" > VERSION
- name: Show diff
run: git --no-pager diff
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14" # match the gate's runtime
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
# Conformance gate (BLOCKING): the differential parity gate must pass
# before anything is built, published, or tagged. A gate-broken codegen
# fails the release here — nothing reaches PyPI, no tag is pushed (so the
# tag->npm trigger never fires), and no GitHub Release is created.
- name: Run conformance gate (blocking)
run: |
python -m pip install -e .
npm ci
npm run gate:selftest
npm run gate:full
- name: Build sdist + wheel
run: |
python -m pip install --upgrade build
python -m build
ls -l dist/
# Publish to PyPI BEFORE the tag is pushed. The tag push triggers
# publish-pyodide.yml (npm), so PyPI must land first — otherwise a PyPI
# failure would leave npm shipping a version PyPI never got.
- name: Publish to PyPI
if: ${{ !inputs.dry_run }}
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
# Commit + tag + push AFTER PyPI succeeds. The `git push --tags` triggers
# publish-pyodide.yml for npm (which re-runs the gate — defense in depth),
# so the tag only exists once the gate passed and PyPI published.
- name: Commit + tag + push
if: ${{ !inputs.dry_run }}
env:
NEW_VERSION: ${{ steps.ver.outputs.new }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add VERSION
git commit -m "release: v${NEW_VERSION}"
git tag "v${NEW_VERSION}"
git push origin HEAD --tags
- name: Create GitHub Release
if: ${{ !inputs.dry_run }}
env:
# Org disables write for the default Actions token; use the App token.
GH_TOKEN: ${{ steps.app.outputs.token }}
NEW_VERSION: ${{ steps.ver.outputs.new }}
run: |
gh release create "v${NEW_VERSION}" \
--title "v${NEW_VERSION}" \
--generate-notes \
--notes "Install: \`pip install pineforge-codegen==${NEW_VERSION}\`" \
dist/*
# Notify the pineforge-release hub: it bumps its CODEGEN_VERSION pin,
# rebuilds the combined image, and fans out to the MCPs. FAIL LOUD (no
# continue-on-error) — a dropped notify means downstream never picks up the
# new codegen. Uses the App token minted above (no PAT). Runs AFTER the
# PyPI publish so the version is installable.
- name: Dispatch codegen-release -> pineforge-release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ steps.app.outputs.token }}
NEW_VERSION: ${{ steps.ver.outputs.new }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
gh api repos/pineforge-4pass/pineforge-release/dispatches \
-f event_type=codegen-release \
-F "client_payload[version]=${NEW_VERSION}" \
-F "client_payload[run_id]=${RUN_ID}"
echo "dispatched codegen-release -> pineforge-release (${NEW_VERSION})"