ci: don't wait for CI workflow when publishing preview releases, replace --dry-run with real publication (#1113)

This commit is contained in:
Andrey Bragin
2026-09-09 13:08:08 +02:00
committed by GitHub
parent cc528fb5e9
commit 45f8b34020
2 changed files with 18 additions and 38 deletions

View File

@@ -3,13 +3,6 @@ on:
push: push:
branches: branches:
- main - main
# Previews are gated on CI, so they hang off the CI workflow finishing rather than
# off the push itself. `branches` filters on the *validated* branch, which keeps
# every PR CI run from spawning a Publish and Release run with all jobs skipped.
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
channel: channel:
@@ -92,21 +85,16 @@ jobs:
publish-npm-preview: publish-npm-preview:
name: Publish preview to npm name: Publish preview to npm
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Every CI-green push to main gets a preview, except release-please's own release # Every push to main gets a preview without waiting for CI or release-please,
# merge: merging that PR already published this exact tree as a stable version, so # except release-please's own release merge, which publishes a stable version.
# a preview of it would be a double release. release-please's `releases_created` # Check the commit author and the generated release subject to avoid publishing
# output would be the sharper signal, but it belongs to the push-triggered run and # the same tree twice; either match is enough to skip the preview.
# this job runs in the workflow_run one, so the commit is checked instead — by
# author and by the subject release-please generates, either of which is enough.
if: >- if: >-
${{ ${{
(github.event_name == 'workflow_dispatch' && inputs.channel == 'preview') || (github.event_name == 'workflow_dispatch' && inputs.channel == 'preview') ||
(github.event_name == 'workflow_run' (github.event_name == 'push'
&& github.event.workflow_run.conclusion == 'success' && github.event.head_commit.author.name != 'acp-release-bot[bot]'
&& github.event.workflow_run.event == 'push' && !startsWith(github.event.head_commit.message, 'chore(main): release '))
&& github.event.workflow_run.head_repository.full_name == github.repository
&& github.event.workflow_run.head_commit.author.name != 'acp-release-bot[bot]'
&& !startsWith(github.event.workflow_run.head_commit.message, 'chore(main): release '))
}} }}
# Same environment as the stable job, so the npm trusted-publisher binding holds. # Same environment as the stable job, so the npm trusted-publisher binding holds.
# It carries no required reviewers, so previews never wait for an approval. # It carries no required reviewers, so previews never wait for an approval.
@@ -128,9 +116,8 @@ jobs:
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
# workflow_run runs default to the tip of the default branch, so the commit # Publish the exact pushed commit, or the explicitly requested manual ref.
# CI actually validated has to be asked for explicitly. ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.event.workflow_run.head_sha }}
# Brings the tags the version calculation reads as its floor. The repo is a # Brings the tags the version calculation reads as its floor. The repo is a
# few megabytes packed, so a full fetch is cheap and sidesteps every # few megabytes packed, so a full fetch is cheap and sidesteps every
# shallow-clone tag caveat. # shallow-clone tag caveat.
@@ -156,18 +143,13 @@ jobs:
# --no-git-tag-version skips every git operation. # --no-git-tag-version skips every git operation.
run: npm version "${{ steps.preview.outputs.version }}" --no-git-tag-version run: npm version "${{ steps.preview.outputs.version }}" --no-git-tag-version
- run: npm run build - run: npm run build
# TODO(preview-releases): once one run has proved the trigger, the gate and the
# version calculation on main, drop --dry-run and add
# `echo "published=true" >> "$GITHUB_OUTPUT"` to this step. Nothing else needs
# touching — leaving `published` unset is what keeps the tag and the registry
# dispatch dormant while the publish is only a rehearsal. The workflow_run
# trigger and the `release` environment's protected-branch policy mean this job
# cannot be exercised from a feature branch at all.
- name: Publish - name: Publish
id: publish id: publish
# --tag is mandatory: npm publish defaults to `latest` even for a semver # --tag is mandatory: npm publish defaults to `latest` even for a semver
# prerelease, which would point every plain `npm install` at a preview. # prerelease, which would point every plain `npm install` at a preview.
run: npm publish --dry-run --tag preview run: |
npm publish --tag preview
echo "published=true" >> "$GITHUB_OUTPUT"
outputs: outputs:
published: ${{ steps.publish.outputs.published }} published: ${{ steps.publish.outputs.published }}
version: ${{ steps.preview.outputs.version }} version: ${{ steps.preview.outputs.version }}

View File

@@ -61,9 +61,8 @@ job so that a tag failure can be retried on its own with **Re-run failed jobs**
publishing the same one twice fails outright. publishing the same one twice fails outright.
Both downstream jobs are gated on a `published` output that the publish step Both downstream jobs are gated on a `published` output that the publish step
sets, not on whether the publish job went green. That keeps the two concerns sets only after `npm publish --tag preview` succeeds. This ensures tagging and
apart — `published` means "npm has this version" and nothing else — and it means the registry update run only after the version has been published to npm.
a rehearsal run that only passes `--dry-run` neither tags nor dispatches.
A stable and a preview dispatch can never collide — a release merge publishes A stable and a preview dispatch can never collide — a release merge publishes
stable and skips the preview, every other push does the reverse — so the registry stable and skips the preview, every other push does the reverse — so the registry
@@ -111,12 +110,11 @@ version _below_ `latest` until the next push lands; that is cosmetic.
Release merges are excluded by checking the head commit's author and the subject Release merges are excluded by checking the head commit's author and the subject
release-please generates. Both are checked, either is enough, and the cost of a release-please generates. Both are checked, either is enough, and the cost of a
miss is one wasted version number plus a `preview` tag briefly pointing at miss is one wasted version number plus a `preview` tag briefly pointing at
already-released code — `latest` is untouched. release-please's own already-released code — `latest` is untouched. Previews start directly on pushes
`releases_created` output would be a sharper signal, but previews hang off the to `main`, without waiting for CI or the `release-please` job. The commit checks
`CI` workflow finishing rather than off the push, so they run in a different let previews run independently of release-please's outputs.
workflow run from the `release-please` job and cannot read its outputs.
To publish a preview by hand — from any commit, bypassing the CI gate: To publish a preview by hand from any commit:
```sh ```sh
gh workflow run publish.yml --ref main \ gh workflow run publish.yml --ref main \