-
Notifications
You must be signed in to change notification settings - Fork 1
153 lines (137 loc) · 6.71 KB
/
Copy pathpublish.yml
File metadata and controls
153 lines (137 loc) · 6.71 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
name: Publish to npm
# Why this exists: this repo had NO publish workflow, so every release depended on
# someone running `npm publish` twice, in the right order, from a laptop. Twice that
# did not happen — `@blockrun/cli` 0.1.1 (2026-07-17) was tagged and changelogged but
# never published, and `@blockrun/core` 0.1.0 sat built-but-unpublished carrying a
# payment-signing security fix while every installed CLI kept resolving the defective
# `@blockrun/core@^0.0.3`. A missing release step is not a small process gap here; it
# is how a real-money fix stayed off npm.
on:
release:
types: [published]
workflow_dispatch: # manual trigger, e.g. to unstick a failed publish
concurrency:
group: publish
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # required for npm --provenance attestation
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Build
run: |
./node_modules/.bin/tsc -p packages/core/tsconfig.json
./node_modules/.bin/tsc -p packages/cli/tsconfig.json
- name: Tests
run: node --import tsx --test packages/*/test/*.test.ts
# core FIRST and as its own step. The CLI depends on it by range, so a CLI
# published ahead of core would be uninstallable until core landed.
- name: Resolve core version
id: core
run: |
V=$(node -p "require('./packages/core/package.json').version")
echo "version=$V" >> "$GITHUB_OUTPUT"
if npm view "@blockrun/core@$V" version >/dev/null 2>&1; then
echo "published=true" >> "$GITHUB_OUTPUT"
echo "⚠️ @blockrun/core@$V already on npm, skipping"
else
echo "published=false" >> "$GITHUB_OUTPUT"
fi
# pnpm PACK + npm PUBLISH, deliberately split. Each tool is here for one reason:
#
# pnpm pack — only pnpm rewrites the `workspace:^` protocol into a real
# semver range. `npm pack` would ship a literal
# "@blockrun/core": "workspace:^" that no consumer can resolve.
# npm publish — matches how ClawRouter ships, and gives a clearer error
# surface. `pnpm publish` additionally attempts an OIDC token
# exchange first; with trusted publishing unconfigured for
# these packages that 404s and it reports the failure as
# ERR_PNPM_OTP_NON_INTERACTIVE, which reads like a pnpm bug.
# npm reports the same underlying condition as plain EOTP.
#
# REQUIRED: secrets.NPM_TOKEN must be a **granular access token with
# "Bypass 2FA" enabled**, AND each package's npmjs.com "Publishing access"
# must be set to "Require two-factor authentication OR a granular access
# token with bypass 2fa enabled".
#
# Not an Automation token, and not a classic token of any kind. The default
# package setting is "Require 2FA and DISALLOW bypass 2fa tokens", under
# which every token is rejected at the write with EOTP no matter its type —
# including `npm access set mfa=...`, so you cannot even relax the setting
# from CI. That killed four runs on 2026-08-07/08. The tell is that
# `npm whoami` and `npm access list` both succeed while the publish fails:
# the token authenticates fine, the package policy just refuses tokens.
#
# BETTER, and what this repo should move to: configure a **Trusted
# Publisher** (OIDC) for both packages at
# https://www.npmjs.com/package/<pkg>/access. Trusted publishers work
# regardless of the Publishing access setting, so the strict "disallow
# bypass 2fa tokens" option can stay selected and no long-lived credential
# exists to leak or rotate. @blockrun/clawrouter already ships this way —
# its published versions carry provenance attestations.
#
# Verified: blockrun-cli-0.2.0.tgz produced by pnpm pack declares
# "@blockrun/core": "^0.1.0", not the workspace protocol.
- name: Publish @blockrun/core
if: steps.core.outputs.published != 'true'
run: |
cd packages/core
TGZ=$(pnpm pack --pack-destination "$RUNNER_TEMP" | tail -1)
npm publish "$TGZ" --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Wait for core to be resolvable
if: steps.core.outputs.published != 'true'
run: |
for i in $(seq 1 30); do
npm view "@blockrun/core@${{ steps.core.outputs.version }}" version >/dev/null 2>&1 && exit 0
sleep 5
done
echo "::error::@blockrun/core@${{ steps.core.outputs.version }} never became resolvable"
exit 1
- name: Resolve cli version
id: cli
run: |
V=$(node -p "require('./packages/cli/package.json').version")
echo "version=$V" >> "$GITHUB_OUTPUT"
if npm view "@blockrun/cli@$V" version >/dev/null 2>&1; then
echo "published=true" >> "$GITHUB_OUTPUT"
echo "⚠️ @blockrun/cli@$V already on npm, skipping"
else
echo "published=false" >> "$GITHUB_OUTPUT"
fi
# Same pnpm-pack / npm-publish split as core above. This is the package that
# actually carries `workspace:^`, so the pack half is load-bearing here.
- name: Publish @blockrun/cli
if: steps.cli.outputs.published != 'true'
run: |
cd packages/cli
TGZ=$(pnpm pack --pack-destination "$RUNNER_TEMP" | tail -1)
node -e "
const { execSync } = require('child_process');
execSync('tar xzf $TGZ -C $RUNNER_TEMP package/package.json');
const dep = require('$RUNNER_TEMP/package/package.json').dependencies['@blockrun/core'];
if (!dep || dep.startsWith('workspace:')) {
throw new Error('workspace protocol survived pack: ' + dep);
}
console.log('packed @blockrun/core range:', dep);
"
npm publish "$TGZ" --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Summary
run: |
echo "core ${{ steps.core.outputs.version }} — published=${{ steps.core.outputs.published == 'false' }}" >> "$GITHUB_STEP_SUMMARY"
echo "cli ${{ steps.cli.outputs.version }} — published=${{ steps.cli.outputs.published == 'false' }}" >> "$GITHUB_STEP_SUMMARY"