-
Notifications
You must be signed in to change notification settings - Fork 0
187 lines (166 loc) · 8.12 KB
/
Copy pathrc-tag-release.yml
File metadata and controls
187 lines (166 loc) · 8.12 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
# Release workflow for @loop-engine/* OSS packages
#
# Normal publish trigger:
# git tag v0.2.0 && git push --tags
# → workflow fires automatically, no manual dispatch needed
#
# Pipeline test (dry run):
# GitHub → Actions → RC tag release → Run workflow
# → leave dry_run CHECKED → run
# → exercises install, build, validate:publish, and pnpm publish --dry-run
#
# Manual dispatch with dry_run UNCHECKED:
# Only allowed from main or a version tag — workflow will hard-fail otherwise.
# Use only as an emergency escape hatch, not as a routine publish path.
#
# Tag pushes: inputs.dry_run is undefined; guard step uses if: ${{ !inputs.dry_run }} so it still
# runs, and startsWith(github.ref, 'refs/tags/') passes immediately — do not change that behavior.
#
# Trusted publishing (OIDC): do NOT set NODE_AUTH_TOKEN / NPM_TOKEN on the publish step — that forces token auth and
# yields EOTP in CI. npm + pnpm use the GitHub OIDC token when id-token: write is set and npm CLI is new enough.
# Requirements (npm docs): Node >= 22.14.0, npm CLI >= 11.5.1. Use Node 24 on the runner — it bundles npm 11.x. Do not run
# `npm install -g npm@...` here; it can break the toolcache install (e.g. MODULE_NOT_FOUND promise-retry).
# Each published package needs Trusted Publisher on npmjs (same repo + workflow filename rc-tag-release.yml).
#
# First-publish bootstrap (token_bootstrap input): npm trusted publishing CANNOT create a package
# that does not yet exist on the registry (npm/cli#8544) — the TP config UI requires an existing
# package record. For brand-new names, run this workflow manually with token_bootstrap CHECKED and
# dry_run UNCHECKED: the bootstrap step publishes ONLY the names listed in BOOTSTRAP_PACKAGES via a
# granular automation token (secrets.NPM_TOKEN — new-package create rights on @loop-engine,
# bypass-2FA), with provenance (id-token: write makes attestation work under token auth too).
# Then bind Trusted Publishers for the new names on npmjs and publish everything else via the tag.
# IMPORTANT: an empty NODE_AUTH_TOKEN is still a value — npm only falls back to OIDC when the var
# is completely unset. That is why token auth lives on a separate step, not a conditional env value.
#
# Optional: NPM_READ_TOKEN + NODE_AUTH_TOKEN only if you add private @scope deps during install (public monorepo: omit).
name: RC tag release
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
dry_run:
description: "Dry run only — does not publish to npm. Uncheck only from main with a version tag pushed."
type: boolean
default: true
required: true
token_bootstrap:
description: "First-publish bootstrap: token-auth publish of ONLY the BOOTSTRAP_PACKAGES list (names trusted publishing can't create yet). Requires secrets.NPM_TOKEN."
type: boolean
default: false
required: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
publish:
name: Publish to npm
runs-on: ubuntu-latest
permissions:
contents: write # required to create GitHub Releases
id-token: write # npm provenance (links tarball to this workflow run)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: pnpm
- name: Show Node and npm versions (OIDC needs npm >= 11.5.1)
run: node --version && npm --version
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm build
- name: Validate no workspace:* in publish targets
run: pnpm validate:publish
- name: Enforce main or tag for real publish
if: ${{ !inputs.dry_run }}
run: |
IS_TAG=${{ startsWith(github.ref, 'refs/tags/') }}
IS_MAIN=${{ github.ref_name == 'main' }}
if [ "$IS_TAG" != "true" ] && [ "$IS_MAIN" != "true" ]; then
echo "❌ Real publish is only allowed from main or a version tag."
echo " Current ref: ${{ github.ref }}"
echo " To test the pipeline safely, re-run with dry_run checked."
exit 1
fi
echo "✅ Ref check passed: ${{ github.ref }}"
- name: Bootstrap first publishes (token auth — names TP cannot create)
if: ${{ inputs.token_bootstrap }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: "true"
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "❌ token_bootstrap requires secrets.NPM_TOKEN (granular automation token with new-package create rights)."
exit 1
fi
# Names that do not exist on npmjs yet — trusted publishing cannot create them (npm/cli#8544).
# After a bootstrap run succeeds: bind Trusted Publishers for each, then DELETE entries from
# this list. The rc.1 bootstrap (2026-06-10) published the original 9; list is empty until
# the next first-publish name arrives.
BOOTSTRAP_PACKAGES=()
if [ ${#BOOTSTRAP_PACKAGES[@]} -eq 0 ]; then
echo "❌ BOOTSTRAP_PACKAGES is empty — nothing to bootstrap. Add the new package names"
echo " to the list in rc-tag-release.yml before dispatching with token_bootstrap."
echo " (Guard: an empty list must never fall through to an unfiltered token publish.)"
exit 1
fi
FLAGS="--access public --no-git-checks"
if [ "${{ inputs.dry_run }}" = "true" ]; then
FLAGS="$FLAGS --dry-run"
echo "ℹ️ Dry run mode — no packages will be published"
fi
FILTERS=""
for pkg in "${BOOTSTRAP_PACKAGES[@]}"; do
FILTERS="$FILTERS --filter $pkg"
done
pnpm publish -r $FILTERS $FLAGS
- name: Publish to npm
if: ${{ !inputs.token_bootstrap }}
run: |
FLAGS="--access public --no-git-checks"
if [ "${{ inputs.dry_run }}" = "true" ]; then
FLAGS="$FLAGS --dry-run"
echo "ℹ️ Dry run mode — no packages will be published"
fi
# Provenance is automatic with trusted publishing; do not pass NODE_AUTH_TOKEN (token auth → EOTP in CI).
pnpm publish -r $FLAGS
# Skip only on workflow_dispatch with dry_run (inputs undefined on tag push — do not use !inputs.dry_run alone).
# Also skip on bootstrap runs — they publish a subset from a branch ref, not a release tag.
- name: Create GitHub Release
if: ${{ github.event_name != 'workflow_dispatch' || (!inputs.dry_run && !inputs.token_bootstrap) }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body: |
## @loop-engine ${{ github.ref_name }}
Published packages:
- See [npm @loop-engine org](https://www.npmjs.com/org/loop-engine) for full package list and changelogs.
### Install
```bash
npm install @loop-engine/sdk@${{ github.ref_name }}
```
### What changed
See [CHANGELOG.md](https://github.com/loopengine/loop-engine/blob/main/CHANGELOG.md)
for detailed changes, or view the [npm release history](https://www.npmjs.com/package/@loop-engine/sdk?activeTab=versions).
draft: false
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify clean install
if: github.event_name == 'push'
run: |
VERSION="${GITHUB_REF_NAME#v}"
mkdir -p /tmp/install-test && cd /tmp/install-test
npm init -y
npm install "@loop-engine/sdk@${VERSION}"
node -e "const { createLoopSystem } = require('@loop-engine/sdk'); console.log('✅ @loop-engine/sdk installs cleanly')"