Skip to content

Commit 14de1a4

Browse files
committed
add python-semantic-release github action
sets up build deps, lints, prevents releases from racing, automatically updates release variables, publishes releases to github and tags them. TODO: pypi distribution
1 parent db8cc3e commit 14de1a4

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

.github/workflows/release.yml

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: Semantic Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
id-token: write
13+
14+
jobs:
15+
release:
16+
runs-on: ubuntu-latest
17+
concurrency:
18+
group: ${{ github.workflow }}-release-${{ github.ref_name }}
19+
cancel-in-progress: false
20+
21+
permissions:
22+
contents: write
23+
24+
steps:
25+
# Note: We checkout the repository at the branch that triggered the workflow
26+
# with the entire history to ensure to match PSR's release branch detection
27+
# and history evaluation.
28+
# However, we forcefully reset the branch to the workflow sha because it is
29+
# possible that the branch was updated while the workflow was running. This
30+
# prevents accidentally releasing un-evaluated changes.
31+
- name: Setup | Checkout Repository on Release Branch
32+
uses: actions/checkout@v4
33+
with:
34+
ref: ${{ github.ref_name }}
35+
fetch-depth: 0
36+
37+
- name: Setup | Force release branch to be at workflow sha
38+
run: |
39+
git reset --hard ${{ github.sha }}
40+
41+
- name: Set up Python
42+
uses: actions/setup-python@v4
43+
with:
44+
python-version: "3.11"
45+
46+
- name: Install dependencies
47+
run: |
48+
python -m pip install --upgrade pip
49+
pip install build twine wheel setuptools ruff black
50+
pip install -r requirements.txt
51+
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
52+
# TODO add playwright install for CI pytest
53+
# TODO update to use uv or move into build_command in pyproject.toml
54+
55+
- name: Run linting and formatting
56+
run: |
57+
# Run linter
58+
black --check --diff stagehand
59+
60+
# Run Ruff formatter check (without modifying files)
61+
ruff check stagehand
62+
63+
# TODO: add back as soon as CI is passing
64+
# - name: Run tests
65+
# run: |
66+
# pytest
67+
68+
- name: Evaluate | Verify upstream has NOT changed
69+
# Last chance to abort before causing an error as another PR/push was applied to
70+
# the upstream branch while this workflow was running. This is important
71+
# because we are committing a version change (--commit). You may omit this step
72+
# if you have 'commit: false' in your configuration.
73+
#
74+
# You may consider moving this to a repo script and call it from this step instead
75+
# of writing it in-line.
76+
shell: bash
77+
run: |
78+
set +o pipefail
79+
80+
UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)"
81+
printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME"
82+
83+
set -o pipefail
84+
85+
if [ -z "$UPSTREAM_BRANCH_NAME" ]; then
86+
printf >&2 '%s\n' "::error::Unable to determine upstream branch name!"
87+
exit 1
88+
fi
89+
90+
git fetch "${UPSTREAM_BRANCH_NAME%%/*}"
91+
92+
if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then
93+
printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!"
94+
exit 1
95+
fi
96+
97+
HEAD_SHA="$(git rev-parse HEAD)"
98+
99+
if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then
100+
printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]"
101+
printf >&2 '%s\n' "::error::Upstream has changed, aborting release..."
102+
exit 1
103+
fi
104+
105+
printf '%s\n' "Verified upstream branch has not changed, continuing with release..."
106+
107+
- name: Action | Semantic Version Release
108+
id: release
109+
uses: python-semantic-release/[email protected]
110+
with:
111+
github_token: ${{ secrets.GITHUB_TOKEN }}
112+
git_committer_name: "github-actions"
113+
git_committer_email: "[email protected]"
114+
115+
- name: Publish | Upload to GitHub Release Assets
116+
uses: python-semantic-release/[email protected]
117+
if: steps.release.outputs.released == 'true'
118+
with:
119+
github_token: ${{ secrets.GITHUB_TOKEN }}
120+
tag: ${{ steps.release.outputs.tag }}
121+
122+
- name: Upload | Distribution Artifacts
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: distribution-artifacts
126+
path: dist
127+
if-no-files-found: error
128+
129+
deploy:
130+
# 1. Separate out the deploy step from the publish step to run each step at
131+
# the least amount of token privilege
132+
# 2. Also, deployments can fail, and its better to have a separate job if you need to retry
133+
# and it won't require reversing the release.
134+
runs-on: ubuntu-latest
135+
needs: release
136+
if: ${{ needs.release.outputs.released == 'true' }}
137+
138+
permissions:
139+
contents: read
140+
id-token: write
141+
142+
steps:
143+
- name: Setup | Download Build Artifacts
144+
uses: actions/download-artifact@v4
145+
id: artifact-download
146+
with:
147+
name: distribution-artifacts
148+
path: dist
149+
150+
# TODO set up trusted publisher
151+
# see https://docs.pypi.org/trusted-publishers/
152+
- name: Publish package distributions to PyPI
153+
uses: pypa/gh-action-pypi-publish@release/v1
154+
with:
155+
packages-dir: dist
156+
print-hash: true
157+
verbose: true

0 commit comments

Comments
 (0)