Skip to content

Automate prerelease publishing with release branches, as drei does #3844

Description

@DennisSmolek

Publishing 10.0.0-alpha.3 took four failed attempts and about an hour, none of it caused by the code being wrong. The manual path is the problem, and patching it is not the fix — v10 has beta and stable still to come, on a September deadline.

What actually happened

  1. pnpm release failed — expired npm token.
  2. Re-authed. Failed again — the token had no write access to @react-three/* yet.
  3. Access was granted. Failed again — npm's authz cache had not propagated, and it reports that as a 404 on PUT, which reads identically to "you do not have permission". Three of us concluded the wrong thing from it.
  4. Eventually worked, publishing the three packages by hand.

The debug log shows 2FA completing cleanly and the PUT failing after it, so the browser challenge was never the issue:

40 http fetch GET 200 .../done?authId=***      ← 2FA completed
41 verbose web auth done-check finished
42 http fetch PUT 404 .../@react-three%2ffiber ← rejected here

None of this is reproducible or reviewable. The alphas so far were published from two different people's laptops (krispyaa for alpha.0–2, dennissmolek for alpha.3), with no provenance attestation on any of them.

Meanwhile, the canary job just works

canary.yml has published cleanly on every push to v10 — via GitHub OIDC, no personal token, with provenance:

10.0.0-canary.611635d   by GitHub Actions   provenance: YES
10.0.0-alpha.2          by krispyaa         provenance: none

So the mechanism is already proven in this repo. It is simply not wired to anything but canary.

What drei does

pmndrs/drei has alpha, beta and rc branches, and one release.yml:

on:
  push:
    branches: ['master', 'beta', 'alpha', 'canary-*', 'rc']

jobs:
  build-and-release:
    needs: test          # release cannot run unless tests pass
    permissions:
      id-token: write    # provenance

with release.config.js mapping branch → dist-tag:

branches: [
  'master',
  { name: 'alpha', prerelease: true },
  { name: 'beta', prerelease: true },
  { name: 'rc', prerelease: true },
  { name: 'canary-*', prerelease: true, channel: 'canary' },
]

Push to alpha → published under the alpha tag. No tokens, no OTP, no laptop, and needs: test makes it impossible to publish something that did not pass CI.

Proposed

Adopt the same shape. The pieces already exist here:

  • Trigger: extend a release workflow to alpha / beta / rc branches, modelled on canary.yml, which already has the OIDC trust working.
  • Gate: needs: test, so a publish cannot outrun CI. Worth noting test: stop shadowing React's act with a frame-wait helper #3823 and fix: keep interaction state continuous across instance reconstruction #3824 both merged a commit short of what CI had verified — a release gated on a post-merge run would have caught that.
  • Tag derivation: scripts/release.js already derives the dist-tag from the version and refuses to touch latest without --allow-latest. Either keep it as the publish step or replace it with semantic-release; the guard rails matter more than the tool.

Open question worth deciding early: semantic-release or the existing script. semantic-release also handles version bumping and changelog generation from commits, which is a larger change to how this repo works. The smaller step is branch-triggered publishing with the current script, keeping hand-written changelogs.

Also worth fixing regardless

scripts/release.js spawns pnpm publish through execFileSync with stdio: 'inherit'. npm's 2FA browser prompt printed but did not appear to block, and publishing by hand is what eventually worked. If any manual path survives, that spawn should hand the TTY through properly — but automating the publish removes the interactive prompt entirely, which is the better answer.

Acceptance

Pushing to a release branch publishes every package under the matching dist-tag, from CI, with provenance, and only after tests pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions