pyRPC
← Back to Blog

The CI/CD publish pipeline: tag-triggered, chained, OIDC

·12 min read

The .github/workflows/publish.yml file is the entire release automation. One tag push publishes 5 Python packages, 6 npm packages, and creates a GitHub Release. This post explains how.

Trigger

on:
  push:
    tags:
      - "v*.*.*"
      - "v*.*.*-*"

Any tag matching v1.2.3 or v1.2.3-beta.1 triggers the workflow. Pre-release tags get a beta npm tag.

Job chain

The six jobs run in dependency order:

  1. publish-pypi — builds all Python wheels, uploads via OIDC
  2. publish-npm-types — publishes @pyrpc/types
  3. publish-npm-client — needs types, publishes @pyrpc/client
  4. publish-npm-react — needs client, publishes @pyrpc/react
  5. publish-npm-adapters — needs react, publishes @pyrpc/next, vue, svelte
  6. create-release — waits for all publish jobs, creates GitHub Release

PyPI via OIDC

permissions:
  id-token: write
  contents: write

steps:
  - uses: pypa/gh-action-pypi-publish@release/v1
    with:
      packages-dir: dist/
      skip-existing: true

No API token. The id-token: write permission lets GitHub create an OIDC token that PyPI trusts. skip-existing prevents errors if a version was already published.

npm chained publishes

Each npm job builds its package before publishing. The chain (needs:) ensures:

  • Types are published first (other packages depend on them)
  • Client is published before React (React imports from client)
  • React is published before Next/Vue/Svelte (adapters import from React)

Pre-release handling

VERSION=$(echo "$GITHUB_REF_NAME" | sed 's/^v//')
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
fi

If the tag is v0.9.0-beta.1, npm publishes with --tag beta. The latest tag stays on the stable release.

How we publish · Versioning guide