The npm side of the publish workflow is not one job, it is four jobs connected by needs:, forming a strict dependency chain. The chain exists because every package's publish depends on the one below it already being on the registry.
The graph
publish-npm-types ──► publish-npm-client ──► publish-npm-react ──► publish-npm-adapters
(leaf) (depends on types) (depends on client) (next, vue, svelte)Each arrow is a needs: declaration, and each job also lists its on: push: tags trigger. The Python side is a single job building all five packages; the npm side serializes because registry resolution is per-package.
Why ordering is a hard requirement
Consider the react job. It runs npm install and npm run build --workspace=@pyrpc/client then npm run build --workspace=@pyrpc/react. React's package.json declares @pyrpc/types@^0.12.0 as a dependency. During the workspace build, npm resolves that range locally, but at publish time, the registry metadata must already contain 0.12.0, because consumers will resolve it there. Publishing react before types would create a package whose dependency does not exist publicly yet.
The needs: chain guarantees the ordering. Types must be live before client publishes, client before react, react before the remaining adapters. GitHub Actions runs the chain serially, each link completing the previous.
Why each link rebuilds the whole base
The react job does not just build react, it rebuilds client first, and the adapters job rebuilds client, react, and then next/vue/svelte:
npm run build --workspace=@pyrpc/client npm run build --workspace=@pyrpc/react npm run build --workspace=@pyrpc/next npm run build --workspace=@pyrpc/vue npm run build --workspace=@pyrpc/svelte
Repetition is deliberate. Each job is a hermetic unit: it checks out the tag, installs, builds its dependency chain from source, and publishes only its own package. There is no shared artifact cache to get stale, and no job depends on another job's files, only on its registry outcome. The cost is redundant builds; the benefit is that any job can be re-run independently.
The terminal node: create-release
At the end, create-release lists all five jobs in its needs: array, both the Python job and the four npm jobs. The GitHub Release is created only after everything is published. This turns the release into an all-or-nothing commit: a half-published matrix never gets a release page attached to it, and a failed publish never pretends it succeeded.
The takeaway
Publishing a dependency chain is a serialization problem wearing a CI costume. The needs: graph is the dependency graph turned upside down, each package waits for its dependencies, builds hermetically, publishes idempotently, and only then do its dependents proceed.

pyRPC