Skip to content

Commit 4ef4d7d

Browse files
authored
Merge pull request #4 from Kilo-Org/feat/oidc-trusted-publishing
feat(release): use OIDC trusted publishing for npm publish
2 parents 9477626 + 1bbf056 commit 4ef4d7d

4 files changed

Lines changed: 15 additions & 118 deletions

File tree

.github/workflows/format.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
pull_request:
88
workflow_dispatch:
99

10+
permissions:
11+
contents: read
12+
1013
jobs:
1114
format:
1215
runs-on: ubuntu-latest

.github/workflows/publish.yml

Lines changed: 6 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454

5555
- uses: actions/setup-node@v4
5656
with:
57-
node-version: "22"
57+
node-version: "24"
5858
registry-url: "https://registry.npmjs.org"
5959

6060
- name: Install dependencies
@@ -69,14 +69,6 @@ jobs:
6969
- name: Format check
7070
run: bun run format:check
7171

72-
# Fail fast on bad/missing NPM_TOKEN before any side effects
73-
# (version.ts writes to package.json, network calls to GH, etc.)
74-
# Surfaces auth issues in ~2s instead of mid-publish.
75-
- name: Verify npm auth
76-
run: npm whoami --registry=https://registry.npmjs.org/
77-
env:
78-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
79-
8072
- name: Resolve version
8173
id: version
8274
run: bun script/version.ts
@@ -107,11 +99,15 @@ jobs:
10799
# recovery handler prints the exact manual recovery commands.
108100
# ============================================================
109101

102+
# Authentication for npm publish uses OIDC trusted publishing —
103+
# no NODE_AUTH_TOKEN needed. npm CLI auto-detects the OIDC
104+
# environment when id-token: write is set and no token is present.
105+
# Configured on npmjs.com under package settings → Trusted Publishers.
106+
# Requires npm CLI v11.5.1+ and Node 22.14.0+.
110107
- name: Publish to npm
111108
id: publish
112109
run: bun script/publish.ts
113110
env:
114-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
115111
NPM_CONFIG_PROVENANCE: "true"
116112
KILO_CHANNEL: ${{ steps.version.outputs.channel }}
117113

@@ -140,114 +136,6 @@ jobs:
140136
echo "::warning::Could not verify $VERSION on the registry after 60s of polling. The publish step itself reported success; verification is informational only and the workflow will continue to the tag/release steps."
141137
exit 0
142138
143-
# Reconcile npm dist-tags.latest after a dev publish. On the very
144-
# first publish of a new package, npm auto-assigns `latest` to
145-
# whatever version was published, regardless of `--tag dev`. That
146-
# leaves end users running plain `npm install <pkg>` getting a
147-
# prerelease, which trips OpenClaw's prerelease guard with a
148-
# confusing error.
149-
#
150-
# This step runs ONLY for dev-channel publishes, and ONLY when
151-
# `latest` currently points at a prerelease version. It tries to
152-
# repoint `latest` to the highest existing stable. If no stable
153-
# exists yet (the pre-stable phase, i.e. before the first
154-
# `channel=latest` release), it emits a warning and exits 0.
155-
#
156-
# Like the verify step above, this is INFORMATIONAL only —
157-
# it never fails the workflow and never blocks tag/release.
158-
- name: Reconcile latest dist-tag (dev publishes)
159-
if: steps.publish.outcome == 'success' && steps.version.outputs.channel == 'dev'
160-
env:
161-
VERSION: ${{ steps.version.outputs.version }}
162-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
163-
run: |
164-
set -euo pipefail
165-
PKG="@kilocode/openclaw-security-advisor"
166-
167-
# Read dist-tags via the registry HTTP endpoint (faster
168-
# propagation than `npm view` which has a separate cache layer
169-
# and can return stale data for 30-90s after a publish).
170-
# Retry up to 3x with 5s backoff in case the dist-tags entry
171-
# itself hasn't propagated yet.
172-
fetch_latest_dist_tag() {
173-
curl -s "https://registry.npmjs.org/-/package/$PKG/dist-tags" 2>/dev/null | node -e '
174-
let s = "";
175-
process.stdin.on("data", d => s += d);
176-
process.stdin.on("end", () => {
177-
try { console.log(JSON.parse(s).latest || ""); }
178-
catch { console.log(""); }
179-
});
180-
' || echo ""
181-
}
182-
183-
LATEST=""
184-
for attempt in 1 2 3; do
185-
LATEST=$(fetch_latest_dist_tag)
186-
if [ -n "$LATEST" ]; then
187-
break
188-
fi
189-
if [ "$attempt" -lt 3 ]; then
190-
echo " dist-tags query attempt $attempt/3 returned empty, retrying in 5s..."
191-
sleep 5
192-
fi
193-
done
194-
echo "Current dist-tags.latest: ${LATEST:-<unset>}"
195-
196-
# If `latest` is empty or already a stable version (no `-`),
197-
# there's nothing to reconcile.
198-
case "$LATEST" in
199-
"")
200-
echo "::notice::dist-tags.latest is unset; nothing to reconcile"
201-
exit 0
202-
;;
203-
*-*)
204-
: # prerelease — fall through to reconciliation
205-
;;
206-
*)
207-
echo "::notice::dist-tags.latest is already a stable version ($LATEST); nothing to reconcile"
208-
exit 0
209-
;;
210-
esac
211-
212-
# Find the highest stable version on the registry. Handles
213-
# both shapes of `npm view ... versions --json`: a string for
214-
# single-version packages, an array for multi-version.
215-
HIGHEST_STABLE=$(npm view "$PKG" versions --json 2>/dev/null | node -e '
216-
let s = "";
217-
process.stdin.on("data", d => s += d);
218-
process.stdin.on("end", () => {
219-
try {
220-
const data = JSON.parse(s);
221-
const arr = Array.isArray(data) ? data : [data];
222-
const stable = arr.filter(x => typeof x === "string" && !x.includes("-"));
223-
if (!stable.length) process.exit(42);
224-
stable.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
225-
console.log(stable[stable.length - 1]);
226-
} catch {
227-
process.exit(43);
228-
}
229-
});
230-
') || HIGHEST_STABLE=""
231-
232-
if [ -z "$HIGHEST_STABLE" ]; then
233-
echo "::warning::No stable version of $PKG exists on the registry yet. npm auto-assigned dist-tags.latest to the just-published dev version ($LATEST) because --tag dev alone cannot prevent it on a first publish. Users must opt in to the dev channel explicitly: 'openclaw plugins install $PKG@dev' or 'npm install $PKG@dev'. This is expected and non-fatal until the first stable (channel=latest) release ships, at which point this step will repoint latest automatically."
234-
exit 0
235-
fi
236-
237-
echo "Highest stable on registry: $HIGHEST_STABLE — repointing latest..."
238-
for i in 1 2 3; do
239-
if npm dist-tag add "$PKG@$HIGHEST_STABLE" latest; then
240-
echo "::notice::Repointed dist-tags.latest from $LATEST to $HIGHEST_STABLE"
241-
exit 0
242-
fi
243-
if [ "$i" -lt 3 ]; then
244-
echo " attempt $i/3 failed, retrying in 5s..."
245-
sleep 5
246-
fi
247-
done
248-
echo "::warning::Failed to repoint dist-tags.latest to $HIGHEST_STABLE after 3 attempts. Manual fix: npm dist-tag add $PKG@$HIGHEST_STABLE latest"
249-
exit 0
250-
251139
- name: Configure git identity
252140
if: steps.publish.outcome == 'success'
253141
run: |

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
pull_request:
88
workflow_dispatch:
99

10+
permissions:
11+
contents: read
12+
1013
jobs:
1114
test:
1215
runs-on: ubuntu-latest

.github/workflows/typecheck.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
pull_request:
88
workflow_dispatch:
99

10+
permissions:
11+
contents: read
12+
1013
jobs:
1114
typecheck:
1215
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)