The entire release pipeline (publish to PyPI, publish to npm, create the GitHub Release) is triggered by nothing more than a git tag matching a pattern. The tag is the release. Everything downstream is ceremony.
The trigger
on:
push:
tags:
- "v*.*.*"
- "v*.*.*-*"Two glob patterns cover stable and prerelease tags: v0.12.0 and v0.12.0-beta.1. A push of either fires the whole workflow. No manual "run release" button, no secret ritual, a tag push is the whole ceremony.
The tag encodes release metadata
The workflow never needs an input parameter for "is this a prerelease?", it derives it from the tag name:
prerelease: ${{ contains(github.ref_name, '-') }}
make_latest: ${{ !contains(github.ref_name, '-') }}A hyphen in the tag (v0.12.0-beta.1) marks the GitHub Release as a prerelease and excludes it from latest. A clean tag (v0.12.0) is a full release and becomes the latest. The versioning scheme and the workflow metadata are the same string.
npm's prerelease dist-tags mirror the same rule
The npm publish steps do the identical dance with npm's dist-tag system:
if [[ "$VERSION" == *"-"* ]]; then
TAG=$(echo "$VERSION" | awk -F'-' '{print $2}' | awk -F'.' '{print $1}')
npm publish --access public --tag "$TAG"
else
npm publish --access public
fiA prerelease version (0.12.0-beta.1) extracts the first hyphen segment (beta) and publishes under that dist-tag (so npm i @pyrpc/react@beta works without ever disturbing the latest tag. Stable versions publish plain, becoming latest. The prerelease signal travels from git tag, to GitHub Release, to npm dist-tag) one source of truth, three registries honoring it.
Why tag-based triggers win here
A tag is a git-native, immutable, auditable object. It names a specific commit, so a release is provably built from a known tree. It is also push-friendly: the release flow becomes git tag v0.12.0 && git push origin v0.12.0, two commands a human can reason about. The tag is the single point of entry for the entire pipeline, which keeps the "how do I release?" question answerable in one sentence.
The risk and the guard
The danger of tag-triggered publishing is a misfired tag. The workflow's countermeasure is the release PR flow upstream: the version bump lands on main first, CI runs, and the tag is created only after review. The tag is the last step of a reviewed process, not a footgun anyone can trip accidentally. That ordering (review, merge, then tag) is what makes a single-command trigger safe.

pyRPC