Skip to content

Commit 5096889

Browse files
dmealingclaude
andcommitted
ci: retire a dist-tag across the lockstep set from CI, where the token that publishes lives
The npm `next` tag has pointed at `1.0.0-rc.8` since the 1.0 cut, so `npm i @metaobjectsdev/cli@next` has been serving a superseded prerelease. It stayed that way because removing it by hand is 14 registry calls and the maintainer's LOCAL granular token cannot make them. Diagnosed rather than assumed: that token 403s on `dist-tag rm` for every package in the set AND on `npm profile get`, and passing `--otp` does not change the error. The registry is not asking for a second factor — the token lacks the right. So this is not a 2FA problem and no amount of OTP juggling would have fixed it. The token that publishes the set is the CI `NPM_TOKEN` secret; it is therefore the one that can retag, and running the operation here means no publish-capable token is ever pasted onto a laptop. The workflow reads the set and its order from `scripts/publish-set.mjs` rather than listing packages, per the standing rule that a new publish path must read the same derivation — a hardcoded list here would drift the moment the set changes, and an omission sorts FIRST rather than failing. Two deliberate details: `ls` is the default action so a dispatch that is meant to report cannot write, and a tag that is already absent prints as nothing-to-do instead of failing, so a partial run is safely resumable. Failures accumulate and the step exits non-zero at the end, so one run names every package that could not be retagged instead of stopping at the first. Run: Actions -> "npm dist-tag" -> Run workflow -> tag `next`, action `rm`. Verify: `npm dist-tag ls @metaobjectsdev/cli` shows `latest: 1.0.0` and no `next` line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
1 parent 1812c86 commit 5096889

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

.github/workflows/npm-dist-tag.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Retire (or repoint) a dist-tag across the whole lockstep npm set.
2+
#
3+
# WHY THIS EXISTS. A dist-tag left pointing at a superseded prerelease is a live install
4+
# path: `npm i @metaobjectsdev/cli@next` served 1.0.0-rc.8 for as long as `next` outlived
5+
# the 1.0.0 cut. Removing it by hand is 14 registry calls, and the maintainer's LOCAL
6+
# granular token cannot make them — it 403s on `dist-tag rm` for every package and on
7+
# `npm profile get`, and passing `--otp` does not change the error, so the registry is not
8+
# asking for a second factor: that token simply lacks the right. The CI `NPM_TOKEN` is the
9+
# one that publishes, so it is the one that can retag. Running it here also means no token
10+
# ever has to be pasted onto a laptop.
11+
#
12+
# The set and its order come from scripts/publish-set.mjs — the single source of truth for
13+
# WHICH packages a release touches (CLAUDE.md: never hardcode the publish set; a new
14+
# publish path must read it too). Written to a file first so a non-zero exit fails the step
15+
# rather than yielding a silently short list.
16+
name: npm dist-tag
17+
18+
on:
19+
workflow_dispatch:
20+
inputs:
21+
tag:
22+
description: 'dist-tag to operate on (e.g. next)'
23+
required: true
24+
default: 'next'
25+
action:
26+
description: 'rm = remove the tag; ls = report only (no writes)'
27+
required: true
28+
default: 'ls'
29+
type: choice
30+
options: [ls, rm]
31+
32+
jobs:
33+
dist-tag:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
38+
- name: Set up Node
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: '22'
42+
registry-url: 'https://registry.npmjs.org'
43+
44+
- name: ${{ inputs.action }} '${{ inputs.tag }}' across the lockstep set
45+
env:
46+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
47+
TAG: ${{ inputs.tag }}
48+
ACTION: ${{ inputs.action }}
49+
run: |
50+
set -euo pipefail
51+
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
52+
trap 'rm -f ~/.npmrc' EXIT
53+
54+
node scripts/publish-set.mjs > "${RUNNER_TEMP}/set.txt"
55+
test -s "${RUNNER_TEMP}/set.txt"
56+
57+
fail=0
58+
while read -r dir; do
59+
name=$(node -p "require('./${dir}/package.json').name")
60+
if [ "$ACTION" = "ls" ]; then
61+
printf '%-42s %s\n' "$name" "$(npm dist-tag ls "$name" | tr '\n' ' ')"
62+
else
63+
# A tag that is already absent is the desired end state, not a failure.
64+
if npm dist-tag ls "$name" | grep -q "^${TAG}:"; then
65+
npm dist-tag rm "$name" "$TAG" && echo " ✓ $name — removed '$TAG'" || { echo " ✗ $name"; fail=1; }
66+
else
67+
echo " · $name — no '$TAG' tag, nothing to do"
68+
fi
69+
fi
70+
done < "${RUNNER_TEMP}/set.txt"
71+
72+
# Report every failure before exiting, so one run names them all.
73+
exit "$fail"

0 commit comments

Comments
 (0)