Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
name: Canary
name: _publish-canary

# Per-PR snapshot publish for early feedback. Publishes to the
# npm dist-tag 'canary' without touching the official version or
# the branch's history. Comment on the PR with the install
# command when a snapshot is published.
# Reusable workflow that performs the actual npm publish for the
# canary snapshot path (per-PR). Called from publish.yml (the
# Trusted Publisher entrypoint registered on npmjs.com).
#
# Note: this file is NOT the Trusted Publisher. npm validates the
# entrypoint (publish.yml), not the reusable workflow. Permissions
# for OIDC are declared at the entrypoint level.

on:
pull_request:
types: [opened, synchronize, reopened]
branches: [staging]
workflow_call:

permissions:
contents: read
pull-requests: write
permissions: {}

concurrency:
group: canary-${{ github.workflow }}-${{ github.event.pull_request.number }}
group: canary-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
canary:
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
pull-requests: write
id-token: write

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
Expand Down Expand Up @@ -99,4 +98,4 @@ jobs:
'',
'_This is a snapshot build. Do not depend on it in production._',
].join('\n'),
});
});
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
name: Hotfix
name: _publish-hotfix

# Tag-driven publish path for urgent fixes. The hotfix branch is
# merged into main through a regular PR (the only case where a PR
# targets main directly, justified by urgency). Once the PR is
# merged, a maintainer pushes a vX.Y.Z tag to main, which triggers
# this workflow.
# Reusable workflow that performs the actual npm publish for the
# hotfix path (tag-driven). Called from publish.yml (the Trusted
# Publisher entrypoint registered on npmjs.com).
#
# This workflow is intentionally minimal: no Changesets run
# (the fix's changeset was already merged on staging via a back-merge
# or follow-up PR). It uses the 'hotfix' environment, distinct
# from 'release', with a smaller reviewer pool.
# Note: this file is NOT the Trusted Publisher. npm validates the
# entrypoint (publish.yml), not the reusable workflow. Permissions
# for OIDC are declared at the entrypoint level.

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
branches: [main]
workflow_call:

permissions: {}

concurrency:
group: hotfix-${{ github.ref }}
group: hotfix-${{ inputs.ref || github.ref }}
cancel-in-progress: false

jobs:
hotfix:
runs-on: ubuntu-latest
environment: hotfix
environment: release
permissions:
id-token: write
contents: read

steps:
Expand All @@ -51,7 +44,7 @@ jobs:
# The hotfix branch is responsible for having bumped the
# version in packages/fp/package.json before merging. If the
# version was not bumped, this guard catches it.
- name: Verify version was bumped
- name: Verify version matches tag
run: |
PKG=$(node -p "require('./packages/fp/package.json').name")
VER=$(node -p "require('./packages/fp/package.json').version")
Expand Down Expand Up @@ -93,4 +86,4 @@ jobs:
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228
with:
tag_name: ${{ github.ref_name }}
generate_release_notes: true
generate_release_notes: true
Original file line number Diff line number Diff line change
@@ -1,41 +1,28 @@
name: Release
name: _publish-release

# Triggers on push to main (the canonical path: a "Version Packages"
# PR is merged into main, which produces a new package version and
# pushes here). workflow_dispatch is reserved for hotfix recovery and
# requires an explicit reason for audit trail.
# Reusable workflow that performs the actual npm publish for the
# stable release path. Called from publish.yml (the Trusted
# Publisher entrypoint registered on npmjs.com).
#
# Triggering on pull_request.closed is intentionally avoided — the
# version bump is its own PR, and its merge to main is the natural
# push event.
# Note: this file is NOT the Trusted Publisher. npm validates the
# entrypoint (publish.yml), not the reusable workflow. Permissions
# for OIDC are declared at the entrypoint level.

on:
push:
branches: [main]
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual publish (hotfix recovery only)'
required: true
workflow_call:

permissions: {}

concurrency:
group: release-${{ github.ref }}
group: release-${{ inputs.ref || github.ref }}
cancel-in-progress: false

jobs:
release:
if: >
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch'
&& contains(github.event.inputs.reason, 'hotfix'))
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: read
pull-requests: read

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
Expand All @@ -57,11 +44,9 @@ jobs:
- run: pnpm install --frozen-lockfile

- name: Anti-republish guard
env:
PACKAGE_JSON: packages/fp/package.json
run: |
PKG=$(node -p "require('./${PACKAGE_JSON}').name")
VER=$(node -p "require('./${PACKAGE_JSON}').version")
PKG=$(node -p "require('./packages/fp/package.json').name")
VER=$(node -p "require('./packages/fp/package.json').version")
if npm view "${PKG}@${VER}" version >/dev/null 2>&1; then
echo "::error::${PKG}@${VER} is already published"
exit 1
Expand Down Expand Up @@ -100,4 +85,4 @@ jobs:
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228
with:
tag_name: v$(node -p "require('./packages/fp/package.json').version")
generate_release_notes: true
generate_release_notes: true
65 changes: 65 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Publish

# Single Trusted Publisher entrypoint. Registered on npmjs.com
# under "Trusted Publisher" with workflow filename = "publish.yml"
# and environment = "release".
#
# npm Trusted Publishing allows only one configuration per
# package. To support three publish paths (release / hotfix /
# canary) under a single trusted publisher, this workflow
# dispatches to reusable workflows. npm validates THIS file, not
# the reusable ones.
#
# Triggers covered here:
# - push to main -> release path
# - push tag vX.Y.Z on main -> hotfix path
# - pull_request to staging -> canary path
#
# Each downstream reusable workflow inherits OIDC from this
# entrypoint. Reusable workflows declare permissions explicitly
# but do not request id-token themselves.

on:
push:
branches: [main]
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
pull_request:
types: [opened, synchronize, reopened]
branches: [staging]

permissions: {}

concurrency:
group: publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
# Stable release: triggered by push to main (the canonical
# "Version Packages" PR merge path).
release:
if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')
uses: ./.github/workflows/_publish-release.yml
permissions:
id-token: write
contents: read

# Hotfix: triggered by a tag push on main. The tag is created by
# a maintainer as part of the hotfix procedure (see release
# pipeline docs).
hotfix:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: ./.github/workflows/_publish-hotfix.yml
permissions:
id-token: write
contents: read

# Canary: triggered by a PR targeting staging. Skips cleanly when
# the PR has no changeset.
canary:
if: github.event_name == 'pull_request'
uses: ./.github/workflows/_publish-canary.yml
permissions:
id-token: write
contents: read
pull-requests: write
Loading