Skip to content

Launch Runbook — PyPI + npm

How kew reaches its two registries in one motion: what is automatic, the one-time manual steps, and the single-flag launch.

Distribution names: kew-cli on PyPI (pip install kew-cli; the installed command is kew — the PyPI name kew is an unrelated project) and create-kew on npm (npx create-kew, the recommended onboarding path). The release pipeline lives in .github/workflows/release.yml; a single publish gate — the repository variable PUBLISH_RELEASE — governs both the PyPI and npm publishes, so they flip together.

One-time setup (now, alongside the packaging PR)

1. Create the GitHub environments

gh api --method PUT repos/thossullivan/kew/environments/pypi
gh api --method PUT repos/thossullivan/kew/environments/testpypi
gh api --method PUT repos/thossullivan/kew/environments/npm

2. Register the pending publishers (web UI — the only manual steps)

On pypi.org: log in → Account settings → Publishing (https://pypi.org/manage/account/publishing/) → "Add a new pending publisher" → GitHub tab:

Field Value
PyPI Project Name kew-cli
Owner thossullivan
Repository name kew
Workflow name release.yml
Environment name pypi

On test.pypi.org: same form at https://test.pypi.org/manage/account/publishing/:

Field Value
PyPI Project Name kew-cli
Owner thossullivan
Repository name kew
Workflow name release.yml
Environment name testpypi

On npmjs.com: no access token is used — trusted publishing is OIDC. Log in → the create-kew package's Settings → Trusted Publishing (Account → Publishing before the package exists) → add a GitHub Actions publisher:

Field Value
Package name create-kew
Organization/User thossullivan
Repository kew
Workflow filename release.yml
Environment npm

3. Claim the names (stub publish — PyPI + npm in one dispatch)

A single dispatch claims both names and exercises both OIDC auth paths (the only real pre-launch test of the npm auth round-trip — the standing npm publish --dry-run cannot check auth):

gh workflow run release.yml -f stub_claim=true
gh run watch

Verify PyPI: https://pypi.org/project/kew-cli/ exists and shows only 0.0.1.dev0, and a plain install still fails for the public (pip ignores dev releases without --pre):

pip install kew-cli
# expected: ERROR: No matching distribution found for kew-cli

Verify npm:

npm view create-kew version   # expected: 0.0.1

The create-kew@0.0.1 stub is a functional-but-old wizard (npm has no dev-release equivalent to hide it). Optionally deprecate it so it never resolves as @latest at launch — needs an interactive npm login, so it is not run in CI:

npm deprecate create-kew@0.0.1 "pre-launch name claim"

Standing rehearsal (automatic)

Every release on main publishes kew-cli to TestPyPI (skip-existing: true absorbs workflow re-runs) and attaches the sdist + wheel to the GitHub release, and runs npm publish --dry-run for create-kew (proving the tarball packs; it does not check npm auth — that is what the §3 stub claim is for). Spot-check monthly, and once more just before launch:

pipx install --index-url https://test.pypi.org/simple/ \
  --pip-args="--extra-index-url https://pypi.org/simple/" kew-cli
kew --version
pipx uninstall kew-cli

Launch day

One flag flips both registries:

gh variable set PUBLISH_RELEASE --body true

Then produce a release. Re-running an old workflow run will not publish — semantic-release finds no new commits and releases nothing. Either merge any pending fix:/feat: PR, or:

git commit --allow-empty -m "fix: launch release" && git push origin main

That single release run publishes kew-cli to PyPI, then (ordered after it) create-kew to npm at the same version.

Verify:

  1. Both the publish-pypi and publish-npm jobs ran (not skipped) in the release workflow.
  2. https://pypi.org/project/kew-cli/ renders the landing page and https://www.npmjs.com/package/create-kew shows the new version; all links and badges resolve.
  3. On a clean machine: pipx install kew-cli && kew --version && kew --help, and npx create-kew@latest scaffolds cleanly.

Rollback

  • Stop future publishes: gh variable set PUBLISH_RELEASE --body false (TestPyPI + npm dry-run rehearsals continue; nothing already published is affected).
  • Bad PyPI release: yank it, never delete it. There is no official CLI for yanking — use the web UI: https://pypi.org/manage/project/kew-cli/releases/ → the release → Options → Yank. Yanked versions remain installable for users who pin kew-cli==X.Y.Z exactly, but resolvers skip them otherwise.
  • Bad npm release: npm deprecate create-kew@X.Y.Z "reason" (warns on install but keeps it resolvable). npm unpublish create-kew@X.Y.Z only within 72h and only if nothing depends on it — never rely on unpublish for a version users may already have pinned.