Skip to content
Merged
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
32 changes: 25 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@ name: release
# `pypi` job fails and everything before it still succeeds, so a tag never
# leaves you with half a release and no artifacts.
#
# `workflow_dispatch` runs the same pipeline against TestPyPI, which is how to
# validate a change to this file without spending a real version number. PyPI
# releases are effectively permanent: a version can be yanked but never
# replaced, so the dry run is worth the two minutes.
# A release can also be cut entirely from the Actions tab, without anyone
# pushing a tag from a laptop:
#
# Actions -> release -> Run workflow -> target: pypi
#
# That path creates the tag itself, at the commit it built and tested, so the
# tag can never point at something that was never verified. Choosing
# `testpypi` instead runs the identical pipeline against TestPyPI and creates
# no tag, which is how to validate a change to this file without spending a
# real version number. PyPI releases are effectively permanent: a version can
# be yanked but never replaced, so the dry run is worth the two minutes.

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
target:
description: "Where to publish"
description: "Where to publish (pypi also tags and cuts the release)"
required: true
default: testpypi
type: choice
Expand Down Expand Up @@ -168,10 +175,15 @@ jobs:
github-release:
name: cut the GitHub release
needs: [build, pypi]
if: startsWith(github.ref, 'refs/tags/')
# Either a tag was pushed, or someone asked for a real release from the
# Actions tab. A TestPyPI dry run deliberately creates neither tag nor
# release -- it exists to rehearse, not to leave traces.
if: >-
startsWith(github.ref, 'refs/tags/')
|| github.event.inputs.target == 'pypi'
runs-on: ubuntu-latest
permissions:
contents: write # create the release and attach the distributions
contents: write # create the tag, the release, and attach the artifacts
steps:
- uses: actions/download-artifact@v4
with:
Expand All @@ -180,6 +192,12 @@ jobs:
- name: Create the release
uses: softprops/action-gh-release@v2
with:
# Named rather than inherited from the ref, so the dispatch path
# creates `v<version>` at this commit. The release API creates the
# tag when it does not exist, and the build job has already refused
# to get here if the version were inconsistent anywhere.
tag_name: v${{ needs.build.outputs.version }}
target_commitish: ${{ github.sha }}
name: jupyddl ${{ needs.build.outputs.version }}
body_path: release-notes.md
files: dist/*
Expand Down
24 changes: 20 additions & 4 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
# Releasing jupyddl

Releases are cut by pushing a tag. `.github/workflows/release.yml` builds,
verifies, publishes to PyPI and creates the GitHub Release from the changelog.
`.github/workflows/release.yml` builds, verifies, publishes to PyPI and
creates the GitHub Release from the changelog. There are two ways to start it.

**From the Actions tab** — no local git, nothing depending on one person's
laptop:

> **Actions → release → Run workflow → target: `pypi`**

That path creates the tag itself, at the commit it just built and tested, so
the tag cannot end up pointing at something that was never verified.

**Or by pushing a tag**, if you prefer:

```bash
git tag -a v2.3.0 -m "jupyddl 2.3.0"
git push origin v2.3.0
```

Both run the same checks and produce the same artifacts. Choosing `testpypi`
from the Actions tab rehearses everything and deliberately creates neither a
tag nor a release.

## One-time setup on PyPI (a human has to do this)

The workflow authenticates with **Trusted Publishing** (OIDC), so there is no
Expand Down Expand Up @@ -55,7 +69,8 @@ number is spent either way. Two minutes on TestPyPI is cheap next to that.

## Cutting a release

1. **Land everything on `main`** and confirm CI is green.
1. **Land everything on `main`** and confirm CI is green. Steps 2-5 prepare the
commit; step 6 is either the Actions button above or a tag push.
2. **Bump the version in two places** — they are checked against each other and
against the tag, and a mismatch fails the build rather than publishing a
surprise:
Expand All @@ -69,7 +84,8 @@ number is spent either way. Two minutes on TestPyPI is cheap next to that.
python tools/build_web.py
```
5. Commit, push, merge.
6. **Tag the merge commit** and push the tag.
6. **Cut it**: *Actions → release → Run workflow → `pypi`*, or tag the merge
commit and push the tag.

## What the workflow refuses to do

Expand Down
Loading