Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Every release uses `./scripts/release.sh`. Do not bump versions, tag, or create
## One-time setup

- Install [GitHub CLI](https://cli.github.com/) (`gh`) and authenticate.
- Install [uv](https://docs.astral.sh/uv/). `prepare` relocks `uv.lock` with it, since the lock file records this project's own version and CI installs with `uv sync --locked`.
- Ensure PyPI [trusted publishing](https://docs.pypi.org/trusted-publishers/) is configured for this repo (`publish.yml` uses the `pypi` GitHub environment).

## Release steps
Expand Down
75 changes: 75 additions & 0 deletions scripts/publish-workflow.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Generate publish.yml for a package. Usage: publish-workflow.sh hotdata-framework

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: this makes the generator a second source of truth for .github/workflows/publish.yml with nothing keeping the two in sync — a hand-edit to the workflow (e.g. bumping a pinned action SHA) would be silently reverted the next time someone regenerates. A one-line CI check (./scripts/publish-workflow.sh <pkg> | diff - .github/workflows/publish.yml) would catch drift. (not blocking)

set -euo pipefail
pkg="${1:?package name}"
cat <<EOF
name: Publish to PyPI

on:
push:
tags:
- 'v[0-9]*'

concurrency:
group: pypi-publish-\${{ github.ref_name }}
cancel-in-progress: false

permissions:
contents: read

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.12'

- name: Install build tooling
run: python -m pip install --upgrade build twine

- name: Verify tag matches pyproject version
run: |
if [[ ! "\$GITHUB_REF_NAME" =~ ^v[0-9] ]]; then
echo "Release tag '\$GITHUB_REF_NAME' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2
exit 1
fi
tag="\${GITHUB_REF_NAME#v}"
pkg_version=\$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
if [ "\$tag" != "\$pkg_version" ]; then
echo "Release tag (\$tag) does not match pyproject.toml version (\$pkg_version)" >&2
exit 1
fi

- name: Build sdist and wheel
run: python -m build

- name: Check distribution metadata
run: python -m twine check --strict dist/*

- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/${pkg}
permissions:
id-token: write
steps:
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: dist
path: dist/

- name: Publish via Trusted Publishing
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
EOF
20 changes: 16 additions & 4 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,18 @@ PY

default_branch() {
local remote="${1:-origin}"
git symbolic-ref --quiet "refs/remotes/${remote}/HEAD" 2>/dev/null | sed "s|refs/remotes/${remote}/||" \
|| { git branch -r | sed -n "s|^ ${remote}/\\(main\\|master\\)$|\\1|p" | head -1; } \
|| echo main
local branch
branch="$(git symbolic-ref --quiet "refs/remotes/${remote}/HEAD" 2>/dev/null | sed "s|refs/remotes/${remote}/||")"
if [[ -n "$branch" ]]; then
echo "$branch"
return
fi
branch="$(git branch -r --list "${remote}/main" "${remote}/master" | sed "s|^[[:space:]]*${remote}/||" | head -1)"
if [[ -n "$branch" ]]; then
echo "$branch"
return
fi
echo main
}

ensure_clean() {
Expand All @@ -103,6 +112,7 @@ cmd_prepare() {
[[ -n "$bump" ]] || { usage; die "missing bump kind or explicit version"; }
need gh
need python3
need uv

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: prepare now hard-requires uv, but RELEASING.md "One-time setup" only lists gh. Worth adding a line there so someone hitting error: uv is required knows it's expected. (not blocking)

ensure_clean

local current new base branch pkg
Expand All @@ -122,10 +132,12 @@ cmd_prepare() {

set_version "$new"
update_changelog "$new"
# uv.lock records this project's own version, and CI installs with --locked.
uv lock

branch="release/v${new}"
git checkout -b "$branch"
git add pyproject.toml CHANGELOG.md
git add pyproject.toml CHANGELOG.md uv.lock
git commit -m "chore: release v${new}"

pkg="$(get_pkg_name)"
Expand Down
Loading