pyRPC
← Back to Blog

How we publish: from git tag to npm and PyPI

·10 min read

pyRPC ships on two registries: npm (@pyrpc/*) and PyPI (pyrpc-*). Publishing is triggered by a single git tag. Here is the full flow.

Step 1: release.mjs

node scripts/release.mjs 0.9.0

This script bumps all package.json and pyproject.toml versions, builds JS workspaces, and commits the changes.

Step 2: tag and push

git commit -m "chore: release v0.9.0"
git tag v0.9.0
git push origin HEAD && git push origin v0.9.0

The tag push triggers .github/workflows/publish.yml.

Step 3: CI publishes everything

The workflow has a chain of jobs that depend on each other:

publish-pypi          (Python packages, OIDC)
    ↓
publish-npm-types    (@pyrpc/types)
    ↓
publish-npm-client   (@pyrpc/client)
    ↓
publish-npm-react    (@pyrpc/react)
    ↓
publish-npm-adapters (@pyrpc/next, vue, svelte)
    ↓
create-release       (GitHub Release)

Each npm job builds and publishes one package. The chain ensures dependencies are published first. The PyPI job builds all Python wheels and uploads them via OIDC (no token needed in CI).

Why OIDC for PyPI

OpenID Connect publishing means GitHub Actions proves its identity to PyPI directly. No API token stored as a secret. PyPI trusts the GitHub workflow's identity. This is the recommended approach for open-source projects.

Manual publishing (fallback)

If CI fails or you need to publish locally:

# PyPI
$env:TWINE_USERNAME = "__token__"
$env:TWINE_PASSWORD = "pypi-AgE..."
twine upload dist/*

# npm
npm publish --access public  # in each package directory

The --skip-existing flag on twine prevents errors if packages are already published.

The build order matters

Python packages must be built in dependency order: codegen first, then core, then adapters. pyrpc-core depends on pyrpc-codegen. Adapters depend on pyrpc-core. The dist/ directory ends up with all wheels and sdists.

Publishing guide · Versioning guide