Skip to content

feat(cli): gate OpenCode by smoke-tested version (#2129) #87

feat(cli): gate OpenCode by smoke-tested version (#2129)

feat(cli): gate OpenCode by smoke-tested version (#2129) #87

Workflow file for this run

name: Publish npm packages
# Companion to package-version-guard.yml. That guard makes "source moved but
# version didn't" go red on the PR; this workflow closes the other half of the
# loop: "version moved but nobody published". As of 2026-08-29 main carried
# cli 0.1.22 / mcp 0.3.5 while npm served 0.1.18 / 0.3.4 — four CLI releases
# that never left the repo, so every `commonly` install and every seat on this
# laptop ran code the repo had already replaced.
#
# Trigger: a push to main that touches a published package. For each package,
# compare package.json's version with what the registry serves:
# equal → nothing to do (most pushes)
# repo > registry → publish, then read it back from the registry
# repo < registry → fail loudly; the repo walked backwards (see the guard)
#
# Auth: npm trusted publishing (OIDC). No token is stored anywhere. Each package
# on npmjs.com names this repo and this workflow file as its trusted publisher,
# and the job's id-token is exchanged for a short-lived publish credential.
# That needs npm >= 11.5.1, which the job installs, and `id-token: write`.
# Provenance is attached automatically.
#
# Why: until 2026-10-01 this used an NPM_TOKEN granular token (set 2026-08-29).
# It stopped being accepted, and the cli 0.1.82 publish failed with
# `E404 Not Found - PUT .../@commonlyai%2fcli` (run 36872618263): a token that
# can expire is a release outage on a timer.
#
# This file is deliberately NOT in its own path filter: merging it must not
# fire a publish before the trusted publisher is configured on npmjs.com.
# The first release after the switch goes through workflow_dispatch.
on:
push:
branches: [ main ]
paths:
- 'cli/**'
- 'commonly-mcp/**'
- 'docs/agents/skills/commonly/SKILL.md' # cli prepublishOnly copies it in
workflow_dispatch:
inputs:
package:
description: 'Package dir to publish (cli | commonly-mcp | all)'
default: 'all'
type: choice
options: [ all, cli, commonly-mcp ]
permissions:
contents: read
id-token: write # npm trusted publishing (OIDC) + provenance
concurrency:
group: npm-publish
cancel-in-progress: false # never abort a publish mid-flight
jobs:
publish:
name: ${{ matrix.pkg }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pkg: [ cli, commonly-mcp ]
# No job-level `if` on matrix: jobs.<id>.if cannot see the matrix context
# (only github/needs/vars/inputs), and a workflow that fails validation
# never appears in the check set — it is absent, not red. The package
# picker is applied in the compare step instead.
defaults:
run:
working-directory: ${{ matrix.pkg }}
env:
PKG: ${{ matrix.pkg }}
PICK: ${{ github.event.inputs.package || 'all' }}
steps:
- uses: actions/checkout@v4
# No `registry-url`: setup-node@v4 then writes
# `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` to the runner npmrc,
# and npm reads a present-but-empty token as "auth configured", skips the
# OIDC exchange and fails E404 (Vera, measured in setup-node's dist; fixed
# only in v7). npmjs.org is npm's default registry anyway.
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Compare repo version with the registry
id: cmp
shell: bash
run: |
set -euo pipefail
name=$(node -p "require('./package.json').name")
repo_v=$(node -p "require('./package.json').version")
echo "name=$name" >> "$GITHUB_OUTPUT"
echo "repo_v=$repo_v" >> "$GITHUB_OUTPUT"
if [ "$PICK" != "all" ] && [ "$PICK" != "$PKG" ]; then
echo "· $name: not selected (package=$PICK)"
echo "action=skip" >> "$GITHUB_OUTPUT"
exit 0
fi
# "Never published" (E404) is the first-release path and maps to
# 0.0.0. Anything else — outage, auth, rate limit — must stop the
# run: a registry we cannot read is not a registry at 0.0.0.
if reg_v=$(npm view "$name" version 2>/tmp/npm-view.err); then
:
elif grep -q 'E404' /tmp/npm-view.err; then
reg_v="0.0.0"
else
echo "::error::$name: npm view failed for a reason other than E404:"; cat /tmp/npm-view.err
exit 1
fi
echo "reg_v=$reg_v" >> "$GITHUB_OUTPUT"
if [ "$repo_v" = "$reg_v" ]; then
echo "· $name: registry already at $reg_v"
echo "action=skip" >> "$GITHUB_OUTPUT"
exit 0
fi
# Prerelease ordering is beyond `sort -V` (same limit the version
# guard documents). Checked AFTER equality so a repo and registry
# parked on the same prerelease is a no-op, not a failure.
case "$repo_v$reg_v" in *-*)
echo "::error::$name: prerelease in $reg_v → $repo_v; this workflow does not order prereleases. Publish by hand."
exit 1;;
esac
if [ "$(printf '%s\n%s\n' "$reg_v" "$repo_v" | sort -V | tail -1)" = "$repo_v" ]; then
echo "✓ $name: $reg_v → $repo_v, publishing"
echo "action=publish" >> "$GITHUB_OUTPUT"
else
echo "::error::$name: repo $repo_v is BELOW registry $reg_v. main walked the version backwards; fix the repo, do not republish."
exit 1
fi
# Trusted publishing needs npm >= 11.5.1; Node 22 ships npm 10.
- name: Use an npm that supports trusted publishing
if: steps.cmp.outputs.action == 'publish'
run: npm install -g npm@^11.5.1 && npm --version
# Turns the opaque registry E404 into a named failure (Vera 76338).
- name: Refuse to publish without a usable OIDC identity
if: steps.cmp.outputs.action == 'publish'
run: |
[ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ] || { echo "::error::id-token: write is not in effect; trusted publishing cannot run"; exit 1; }
for f in "${NPM_CONFIG_USERCONFIG:-}" "$HOME/.npmrc" ./.npmrc; do
[ -n "$f" ] && [ -f "$f" ] && grep -q '_authToken' "$f" && { echo "::error::$f carries _authToken; npm would skip the OIDC exchange and fail E404"; exit 1; }
done
true
- name: Install
if: steps.cmp.outputs.action == 'publish'
run: npm ci --ignore-scripts
- name: Publish
if: steps.cmp.outputs.action == 'publish'
# No NODE_AUTH_TOKEN: npm exchanges this job's OIDC id-token with the
# registry, because the package names this workflow as trusted publisher.
run: npm publish --provenance --access public
# The poll, its budget (READBACK_TIMEOUT_SECONDS; the script prints the
# value in force, which is the single place this number is stated) and the
# failure diagnostics live in the script, not inline here — a workflow
# body cannot be exercised by the suite, and this loop went red on two
# good publishes (cli 0.1.65 run 35498255344, 0.1.66 run 35804128707)
# before anyone could test it. TASK-100.
#
# $GITHUB_WORKSPACE, not a relative path: this job sets
# working-directory to the package dir, and the script is at the repo
# root. It is cwd-independent, so the workspace path is the only
# unambiguous way to reach it.
- name: Read it back from the registry
if: steps.cmp.outputs.action == 'publish'
shell: bash
env:
NAME: ${{ steps.cmp.outputs.name }}
WANT: ${{ steps.cmp.outputs.repo_v }}
run: bash "$GITHUB_WORKSPACE/scripts/verify-npm-publish-readback.sh"