-
Notifications
You must be signed in to change notification settings - Fork 311
216 lines (196 loc) · 9.27 KB
/
Copy pathrelease.yml
File metadata and controls
216 lines (196 loc) · 9.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# Copyright (c) Meta Platforms, Inc. and affiliates.
# Single source of truth for ALL npm publishing of @astryxdesign/* packages to
# the public npm registry, via npm OIDC trusted publishing — no NPM_TOKEN.
#
# IMPORTANT: npm trusted publishing matches the OIDC token's workflow FILENAME
# against the trusted-publisher config. npm allows only ONE trusted publisher per
# package, so BOTH stable and canary publishing MUST live in this one file
# (release.yml) and the npm trust config MUST point at release.yml. If publishing
# were split across two workflow files, only one of them could be trusted and the
# other's publish would be rejected by npm at the OIDC check.
# Re-point trust with:
# node scripts/npm/setup-trusted-publishing.mjs --setup-trust --replace --workflow release.yml
#
# Two jobs, selected by trigger:
# - publish (stable): workflow_dispatch only. Publishes the `latest` dist-tag.
# The release flow is:
# 1. Land the changesets "Version Packages" PR on main (`pnpm
# version-packages` → PR → merge). This bumps versions; it never touches
# the registry.
# 2. Run this workflow to publish the bumped versions.
# Version-gated and idempotent: any version already on the registry is
# skipped, so re-running (or a `dry-run` first) is safe.
# - canary: push to main only. Publishes 0.x.y-canary.<short-sha> to the
# `canary` dist-tag on every push to main:
# npm install @astryxdesign/core@canary
#
# Why both jobs live here instead of in deploy.yml:
# - Single workflow file ⇒ single npm trusted-publisher config covers both.
# - deploy.yml uses the `pages-deploy` concurrency group, which serializes/
# cancels on a busy main — that repeatedly starved npm publishing. This
# workflow keeps publishing off that group entirely.
# - Each job checks out the exact ref and publishes per package, so the
# published tarball matches the released commit and carries provenance.
#
# Concurrency is per-JOB (deliberately NOT a single workflow-level group): a
# busy main firing many canary pushes must NEVER cancel or starve a pending
# stable dispatch. Stable and canary therefore use independent concurrency
# groups (see each job below).
#
# Provenance requires a PUBLIC repo (facebook/astryx is public).
name: Release
run-name: Release (${{ github.sha }})
on:
workflow_dispatch:
inputs:
dry-run:
description: 'Build and resolve what would publish, but do not publish'
type: boolean
default: false
push:
branches: [main]
permissions: {}
jobs:
# Stable publish — manual dispatch only. Publishes the `latest` dist-tag.
publish:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
# Dedicated, non-cancelling group. Independent of the canary group so a
# canary push can never cancel or queue ahead of a stable release.
concurrency:
group: npm-release-stable
cancel-in-progress: false
permissions:
contents: read
id-token: write # npm OIDC trusted publishing + provenance
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24 # bundles npm >= 11.5.1, required for OIDC publishing
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm build
- name: Publish changed packages
env:
DRY_RUN: ${{ inputs.dry-run }}
run: |
set -eu
published=0
for pkg_dir in packages/* packages/themes/*; do
[ -f "$pkg_dir/package.json" ] || continue
# Stable `latest` never includes canaryOnly packages (e.g. lab):
# skip them explicitly so even an accidental `private: false` can
# never leak them onto the `latest` dist-tag.
IS_PUBLISHABLE=$(node -p "const p=require('./$pkg_dir/package.json'); !p.private && !p.astryx?.canaryOnly && p.name?.startsWith('@astryxdesign/') ? 'yes' : 'no'")
[ "$IS_PUBLISHABLE" = "yes" ] || continue
NAME=$(node -p "require('./$pkg_dir/package.json').name")
VERSION=$(node -p "require('./$pkg_dir/package.json').version")
if npm view "$NAME@$VERSION" version --registry https://registry.npmjs.org >/dev/null 2>&1; then
echo "Skipping $NAME@$VERSION (already on registry)"
continue
fi
if [ "$DRY_RUN" = "true" ]; then
echo "[dry-run] Would publish $NAME@$VERSION"
continue
fi
echo "Publishing $NAME@$VERSION"
pnpm publish "./$pkg_dir" --tag latest --provenance --access public --no-git-checks
published=$((published + 1))
done
echo "Published $published package(s)."
# Canary publish — runs on every push to main. The @canary dist-tag always
# points to the latest main commit:
# npm install @astryxdesign/core@canary
canary:
name: Publish canary to npm
if: github.event_name == 'push'
runs-on: ubuntu-latest
# Dedicated group, cancel-in-progress so a newer main push supersedes an
# in-flight canary. Independent of the stable group, so this can never
# cancel or starve a stable dispatch.
concurrency:
group: npm-release-canary
cancel-in-progress: true
permissions:
contents: read
id-token: write # required for npm OIDC trusted publishing + provenance
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24 # bundles npm >= 11.5.1, required for OIDC publishing
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm build
- name: Publish canary versions
# Pure OIDC trusted publishing — no NPM_TOKEN.
run: |
set -eu
SHORT_SHA="${GITHUB_SHA:0:7}"
BASE_VERSION=$(node -p "require('./packages/core/package.json').version")
CANARY_VERSION="${BASE_VERSION}-canary.${SHORT_SHA}"
echo "Publishing canary ${CANARY_VERSION}"
# Stamp canary version into all publishable packages. canaryOnly
# packages (e.g. lab) stay `private: true` in git as npm's hard
# guarantee against a stable publish; here — in the ephemeral CI
# checkout ONLY — we strip `private` so they publish to @canary.
for pkg in packages/*/package.json packages/themes/*/package.json; do
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('$pkg','utf8'));
if (!p.name?.startsWith('@astryxdesign/')) process.exit(0);
if (p.private && !p.astryx?.canaryOnly) process.exit(0);
if (p.private && p.astryx?.canaryOnly) delete p.private;
p.version = '${CANARY_VERSION}';
for (const dt of ['dependencies','peerDependencies','devDependencies']) {
if (!p[dt]) continue;
for (const [k,v] of Object.entries(p[dt])) {
if (k.startsWith('@astryxdesign/')) p[dt][k] = '${CANARY_VERSION}';
}
}
fs.writeFileSync('$pkg', JSON.stringify(p, null, 2) + '\n');
console.log(' ' + p.name + ' → ${CANARY_VERSION}');
"
done
# Publish each package with --tag canary. Failures are collected and
# the job still fails at the end (never `|| true`-swallowed), but one
# rejected publish no longer aborts the loop: when lab's first OIDC
# publish was rejected (no npm package/trust bootstrapped yet), every
# package after it in the loop (all seven themes) stopped receiving
# canaries. Publishes are per-package and idempotent, so attempting
# the rest is always safe.
FAILED=""
for pkg_dir in packages/* packages/themes/*; do
[ -f "$pkg_dir/package.json" ] || continue
# canaryOnly packages are publishable here (the stamp step above
# already stripped their `private` flag in this ephemeral checkout).
IS_PUBLISHABLE=$(node -p "const p=require('./$pkg_dir/package.json'); (!p.private || p.astryx?.canaryOnly) && p.name?.startsWith('@astryxdesign/') ? 'yes' : 'no'")
if [ "$IS_PUBLISHABLE" = "yes" ]; then
NAME=$(node -p "require('./$pkg_dir/package.json').name")
echo "Publishing ${NAME}@${CANARY_VERSION}"
if ! pnpm publish "./$pkg_dir" --tag canary --provenance --access public --no-git-checks; then
echo "::error::Canary publish failed for ${NAME}@${CANARY_VERSION}"
FAILED="${FAILED} ${NAME}"
fi
fi
done
if [ -n "$FAILED" ]; then
echo "::error::Canary publish failed for:${FAILED}"
exit 1
fi
echo "Canary published: npm install @astryxdesign/core@${CANARY_VERSION}"