Skip to content

Add scripts/release.sh for releasing to PyPI by hand - #50

Merged
manish-wekan merged 2 commits into
mainfrom
chore/release-script
Oct 16, 2026
Merged

manish-wekan merged 2 commits into
mainfrom
chore/release-script

Conversation

@manish-wekan

@manish-wekan manish-wekan commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

publish.yml only uploads through PyPI trusted publishing (never exercised: the upload job was skipped on every run, and no release on PyPI has a provenance record), and bump-version.yml can't open its release PR because the org doesn't allow GitHub Actions to create pull requests. This script does a release from a laptop.

Usage

scripts/release.sh --bump patch        # or: git switch release/X.Y.Z (pushed by the bump workflow)
                                       # then on that branch: scripts/release.sh
# merge the release PR it opens

scripts/release.sh --tag tags a version that's on PyPI but untagged (e.g. 0.4.1: it picks the head of origin/release/0.4.1, 57e8d35).
Options: --skip-tests, --yes (don't ask before uploading), --dry-run (no push, upload, PR or tag). Needs uv, gh, and a PyPI token in ~/.pypirc or TWINE_USERNAME/TWINE_PASSWORD.

What it checks before uploading

  • clean tree, branch identical to origin/<branch> (so PyPI gets exactly what the PR merges), not on main
  • uv.lock's editable nitrostack version matches pyproject.toml
  • version not on PyPI and not tagged
  • test suite (.[dev]), build, twine check --strict; asks y/N before twine upload
  • after upload: waits for PyPI, installs nitrostack==X into a fresh venv and imports it
  • tags the uploaded commit vX.Y.Z and pushes the tag
  • opens the release PR (label release, so the bump workflow skips it; publish.yml sees the version on PyPI and skips the upload)

uv is found even when the pyenv shim fails inside this repo (the pinned .python-version 3.12 isn't in pyenv).

Testing (throwaway clone, nothing pushed or uploaded)

  • --bump patch --dry-run: made release/0.4.2 from origin/main (only pyproject.toml/uv.lock changed), tests passed, built 0.4.2 wheel + sdist, twine check passed, stopped before upload.
  • --tag --dry-run: "would tag origin/release/0.4.1 (57e8d35) as v0.4.1".
  • --bump with release/0.4.2 already present: fails and stays on main, tree untouched.
  • Offline (fake curl): "Couldn't check PyPI for 0.4.1 (HTTP 000)" instead of treating it as unpublished.
  • Refuses: on main; uncommitted changes; branch not pushed; version already on PyPI (0.4.1); bad --bump.
  • PyPI's project JSON is cached up to 15 min after a release; --bump checks the exact-version URL first and says so instead of failing oddly.

Review fixes (2nd commit): runs from a temp copy (so --bump's branch switch can't rewrite the running script), plans --bump in a scratch folder, 404-only "not on PyPI", no-tty prompt means no, tag the uploaded commit instead of origin/main, non-fatal PR creation.

Not run for real: the upload, tag push and PR-creation steps (they'd publish).

🤖 Generated with Claude Code

manish-wekan and others added 2 commits October 16, 2026 05:55
publish.yml only uploads through PyPI trusted publishing, which has never
run here, and the Bump version workflow can't open its release PR (the org
doesn't allow GitHub Actions to create pull requests). The script covers
both: optionally makes release/<next> from origin/main (--bump), checks the
branch matches origin, the lockfile matches and the version is new, runs the
tests, builds, twine-checks, uploads after asking, verifies a clean install
from PyPI, and opens the release PR. `--tag` tags origin/main after the merge.
`--dry-run` stops before anything leaves the machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Run from a temp copy: --bump switches branches, which can rewrite the
  script while bash is still reading it.
- --bump plans from origin/main's files in a scratch folder and checks
  everything before switching, so a failed check no longer leaves you on a
  detached origin/main.
- Only a 404 counts as "not on PyPI"; offline or PyPI errors stop the run
  instead of passing the "version is new" check.
- No terminal and no --yes means "no" instead of a silent exit.
- Tag the uploaded commit right after the upload (no separate step that
  could tag later, unreleased merges on main). --tag stays for catching up
  untagged releases and prefers the release branch head.
- A failed PR creation after the upload prints the manual step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@manish-wekan
manish-wekan merged commit 1454263 into main Oct 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant