Skip to content

Commit 34cb98d

Browse files
committed
ci: add release automation
1 parent 4bd1f38 commit 34cb98d

2 files changed

Lines changed: 234 additions & 4 deletions

File tree

.github/workflows/release.yml

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
---
2+
name: Release
3+
4+
on:
5+
push:
6+
tags:
7+
- "v*"
8+
release:
9+
types: [created, edited, published]
10+
11+
concurrency:
12+
group: release-${{ github.ref }}
13+
cancel-in-progress: false
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
# Build distribution packages and generate SBOM
20+
build:
21+
name: Build distribution
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 10
24+
outputs:
25+
version: "${{ steps.version.outputs.version }}"
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
30+
31+
- name: Install uv
32+
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
33+
with:
34+
python-version: "3.10"
35+
enable-cache: true
36+
37+
- name: Install just
38+
uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
39+
40+
- name: Add dev version if not a PyPi build
41+
if: "github.event_name == 'push' || (github.event_name == 'release' && github.event.action != 'published')"
42+
run: |
43+
just set-dev-version ${{ github.run_number}}
44+
45+
- name: Capture version
46+
id: version
47+
run: |
48+
echo "version=$(just version)" >> "$GITHUB_OUTPUT"
49+
50+
- name: Build package with SBOM
51+
run: just build-release
52+
53+
- name: Upload distributions
54+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
55+
with:
56+
name: dist
57+
path: dist/
58+
retention-days: 7
59+
60+
# Test built package installs correctly
61+
test:
62+
name: "Test package (${{ matrix.os }}, Python ${{ matrix.python-version }})"
63+
needs: build
64+
runs-on: "${{ matrix.os }}"
65+
timeout-minutes: 10
66+
strategy:
67+
fail-fast: false
68+
matrix:
69+
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
70+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
71+
72+
steps:
73+
- name: Download distributions
74+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
75+
with:
76+
name: dist
77+
path: dist/
78+
79+
- name: Install uv
80+
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
81+
with:
82+
python-version: ${{ matrix.python-version }}
83+
enable-cache: false # No checkout, so no dependency files to hash
84+
85+
- name: Install from wheel
86+
run: |
87+
uv venv
88+
uv pip install dist/*.whl
89+
90+
- name: Smoke test
91+
run: |
92+
uv run python -c "import jsonlt; print(f'Version: {jsonlt.__version__}')"
93+
94+
# Publish to TestPyPI (triggered by tag push or draft release/unpublished release edit)
95+
publish-testpypi:
96+
name: Publish to TestPyPI
97+
needs:
98+
- build
99+
- test
100+
if: "github.event_name == 'push' || (github.event_name == 'release' && github.event.action != 'published')"
101+
runs-on: ubuntu-latest
102+
timeout-minutes: 10
103+
environment:
104+
name: testpypi
105+
url: https://test.pypi.org/p/jsonlt-python
106+
107+
permissions:
108+
id-token: write # Trusted Publishing OIDC
109+
attestations: write # Artifact attestations
110+
111+
steps:
112+
- name: Checkout code
113+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
114+
115+
- name: Download distributions
116+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
117+
with:
118+
name: dist
119+
path: dist/
120+
121+
- name: Generate build attestation
122+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
123+
with:
124+
subject-path: "dist/*.tar.gz,dist/*.whl"
125+
126+
- name: Generate SBOM attestation
127+
uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0
128+
with:
129+
subject-path: "dist/*.tar.gz,dist/*.whl"
130+
sbom-path: dist/sbom.cdx.json
131+
132+
- name: Install uv
133+
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
134+
with:
135+
python-version: "3.10"
136+
137+
- name: Install just
138+
uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
139+
140+
- name: Publish to TestPyPI
141+
run: just publish-testpypi
142+
143+
- name: Verify TestPyPI release
144+
run: just verify-testpypi ${{ needs.build.outputs.version }}
145+
146+
# Publish to PyPI (triggered by publishing the GitHub Release)
147+
publish-pypi:
148+
name: Publish to PyPI
149+
needs:
150+
- build
151+
- test
152+
if: github.event_name == 'release' && github.event.action == 'published'
153+
runs-on: ubuntu-latest
154+
timeout-minutes: 10
155+
environment:
156+
name: pypi
157+
url: https://pypi.org/p/jsonlt-python
158+
159+
permissions:
160+
id-token: write # Trusted Publishing OIDC
161+
attestations: write # Artifact attestations
162+
163+
steps:
164+
- name: Checkout code
165+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
166+
167+
- name: Download distributions
168+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
169+
with:
170+
name: dist
171+
path: dist/
172+
173+
- name: Generate build attestation
174+
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
175+
with:
176+
subject-path: "dist/*.tar.gz,dist/*.whl"
177+
178+
- name: Generate SBOM attestation
179+
uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0
180+
with:
181+
subject-path: "dist/*.tar.gz,dist/*.whl"
182+
sbom-path: dist/sbom.cdx.json
183+
184+
- name: Install uv
185+
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
186+
with:
187+
python-version: "3.10"
188+
189+
- name: Install just
190+
uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
191+
192+
- name: Publish to PyPI
193+
run: just publish-pypi
194+
195+
- name: Verify PyPi release
196+
run: just verify-testpypi ${{ needs.build.outputs.version }}

Justfile

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ set unstable
33
set positional-arguments
44

55
project := "jsonlt"
6-
package := "jsonlt"
6+
package := "jsonlt-python"
7+
module := "jsonlt"
78
pnpm := "pnpm exec"
9+
test_pypi_index := "https://test.pypi.org/legacy/"
810

911
# List available recipes
1012
default:
@@ -114,11 +116,11 @@ prek-all:
114116
prek run --all-files
115117

116118
# Publish to TestPyPI (requires OIDC token in CI or UV_PUBLISH_TOKEN)
117-
publish-testpypi: build-release
118-
uv publish --publish-url https://test.pypi.org/legacy/
119+
publish-testpypi:
120+
uv publish --publish-url {{test_pypi_index}}
119121

120122
# Publish to PyPI (requires OIDC token in CI or UV_PUBLISH_TOKEN)
121-
publish-pypi: build-release
123+
publish-pypi:
122124
uv publish
123125

124126
# Run command
@@ -137,6 +139,13 @@ run-python *args:
137139
sbom output="sbom.cdx.json":
138140
uv run --isolated --group release cyclonedx-py environment --of json -o {{output}}
139141

142+
# Set development version (appends .devN)
143+
[script]
144+
set-dev-version number:
145+
base_version="$(uv version --package {{package}} | awk '{print $2}')"
146+
version="${base_version}.dev{{number}}"
147+
uv version --package {{package}} "${version}"
148+
140149
# Run tests (excludes benchmarks and slow tests by default)
141150
test *args:
142151
pytest "$@"
@@ -171,3 +180,28 @@ update-examples *args:
171180
# Sync Vale styles and dictionaries
172181
vale-sync:
173182
vale sync
183+
184+
# Show the current version
185+
[script]
186+
version:
187+
uv version --package {{package}} | awk '{print $2}'
188+
189+
# Verify a PyPi release
190+
[script]
191+
verify-pypi version:
192+
tmp_dir="/tmp/{{package}}-verify-pypi/venv"
193+
rm -fr "${tmp_dir}"
194+
mkdir -p "${tmp_dir}"
195+
uv venv --directory "${tmp_dir}" --python 3.10 --no-project --no-cache
196+
uv pip install --directory "${tmp_dir}" --no-cache --strict "{{package}}=={{version}}"
197+
uv run --directory "${tmp_dir}" --no-project python -c "import {{module}}; print({{module}}.__version__)"
198+
199+
# Verify a TestPyPi release
200+
[script]
201+
verify-testpypi version:
202+
tmp_dir="/tmp/{{package}}-verify-testpypi/venv"
203+
rm -fr "${tmp_dir}"
204+
mkdir -p "${tmp_dir}"
205+
uv venv --directory "${tmp_dir}" --python 3.10 --no-project --no-cache --default-index "https://test.pypi.org/simple/" --extra-index-url "https://pypi.org/simple/"
206+
uv pip install --directory "${tmp_dir}" --no-cache --strict --default-index "https://test.pypi.org/simple/" --extra-index-url "https://pypi.org/simple/" "{{package}}=={{version}}"
207+
uv run --directory "${tmp_dir}" --no-project python -c "import {{module}}; print({{module}}.__version__)"

0 commit comments

Comments
 (0)