Skip to content

Merge pull request #16 from Anuj7411/here-everywhere #58

Merge pull request #16 from Anuj7411/here-everywhere

Merge pull request #16 from Anuj7411/here-everywhere #58

Workflow file for this run

name: release
# Publishes sipcode to npm via OIDC (Trusted Publishers).
#
# Structural rule (read before editing): the early-skip guard is the FIRST
# substantive step. If sipcode@<this-version> already exists on npm, the
# entire pipeline short-circuits to success. This means:
# - re-pushing an old tag never causes a failure email
# - re-running a historical workflow_dispatch never causes a failure email
# - the only way THIS workflow can fail is when publishing a genuinely new
# version, in which case the failure is a real signal worth investigating
#
# When you see a failure on this workflow, it is ALWAYS a real bug. Never
# add an exception, retry, or "|| true" — fix the underlying issue.
#
# Auth flow (Trusted Publishers / OIDC):
# 1. Push a git tag v*.*.* (or workflow_dispatch with a tag input).
# 2. Workflow checks out the tag.
# 3. Early-skip guard: if the version is already published, exit clean.
# 4. setup-node, upgrade npm to >=11.5.1 (required for OIDC publish).
# 5. Verify package.json version matches the tag.
# 6. npm ci, build, test.
# 7. npm publish — uses the OIDC token (no NPM_TOKEN needed).
# 8. npm.org verifies the token against the Trusted Publisher config
# at https://www.npmjs.com/package/sipcode/access
# (Organization: Anuj7411, Repository: sipcode, Workflow: release.yml).
# 9. Smoke-test by reading the version back from the registry.
#
# Provenance attestation is automatic once OIDC is in effect; no flag.
on:
push:
tags:
- "v*.*.*"
# Allow manual re-runs from the Actions tab if something needs replaying.
workflow_dispatch:
inputs:
tag:
description: "Tag to publish (must already exist, e.g. v1.0.0-rc.1)"
required: true
type: string
# Required so GitHub can mint the OIDC token that npm exchanges for publish auth.
permissions:
contents: read
id-token: write
jobs:
publish:
name: npm publish
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: checkout (tag)
uses: actions/checkout@v4
with:
# On tag pushes, the ref IS the tag — no extra step needed.
# On workflow_dispatch, take the user-supplied tag.
ref: ${{ inputs.tag || github.ref }}
- name: setup node
uses: actions/setup-node@v4
with:
# Setup-node's "22" channel ships npm 10.x at the time of this
# workflow; OIDC trusted publishing needs npm >=11.5.1. We
# upgrade npm explicitly below — but the early-skip guard runs
# first so we don't waste compute on already-published versions.
node-version: "24"
registry-url: "https://registry.npmjs.org/"
- name: skip if this version is already on npm (the early guard)
# Runs BEFORE upgrade-npm, BEFORE verify-tag, BEFORE install/build/test.
# If the version already exists, the rest of the workflow is skipped
# entirely. This is what prevents stale tag re-pushes (and historic
# workflow_dispatch retries) from generating failure emails forever.
#
# We extract the version from the tag itself, not package.json,
# because the workflow may be running on a tag whose tree's
# package.json is misaligned (force-moved tags, etc.). The tag is
# the source of truth for "what version did the human intend?"
id: already_published
run: |
TAG_REF="${{ inputs.tag || github.ref }}"
# Strip 'refs/tags/' prefix if present (push trigger), keep raw
# for workflow_dispatch (already just the tag string).
TAG="${TAG_REF#refs/tags/}"
TAG_VERSION="${TAG#v}"
echo "tag: $TAG"
echo "tag version: $TAG_VERSION"
echo "checking npm for sipcode@$TAG_VERSION..."
# 'npm view <pkg>@<version> version' prints the version if it
# exists. We don't want set -e to kill the step on not-found.
set +e
EXISTING="$(npm view "sipcode@$TAG_VERSION" version 2>/dev/null)"
set -e
if [ -n "$EXISTING" ]; then
echo "::notice::sipcode@$TAG_VERSION is already on npm — skipping the rest of the workflow cleanly."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "sipcode@$TAG_VERSION is NOT yet on npm — proceeding to publish."
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: upgrade npm to OIDC-capable version (>=11.5.1) via corepack
if: steps.already_published.outputs.skip != 'true'
# Required for Trusted Publishers / OIDC. Without this, npm 10.x
# silently bypasses OIDC and the publish gets a misleading 404.
#
# We use corepack instead of `npm install -g npm@latest` because
# the latter sometimes corrupts its own install mid-upgrade
# ("Cannot find module 'promise-retry'") — that was v1.1.0's
# publish-blocker. Corepack manages the npm binary atomically.
run: |
corepack enable
corepack prepare npm@11 --activate
echo "npm version: $(npm --version)"
echo "node version: $(node --version)"
- name: verify tag matches package.json version
if: steps.already_published.outputs.skip != 'true'
run: |
PKG_VERSION="$(node -p "require('./package.json').version")"
TAG_REF="${{ inputs.tag || github.ref }}"
TAG="${TAG_REF#refs/tags/}"
TAG_VERSION="${TAG#v}"
echo "package.json version: $PKG_VERSION"
echo "tag version: $TAG_VERSION"
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
echo "::error::package.json version ($PKG_VERSION) does not match tag ($TAG_VERSION). bump package.json before tagging."
exit 1
fi
- name: install dependencies (clean)
if: steps.already_published.outputs.skip != 'true'
run: npm ci
- name: build
if: steps.already_published.outputs.skip != 'true'
run: npm run build
- name: run tests (last safety net before publish)
if: steps.already_published.outputs.skip != 'true'
run: npm test
- name: end-to-end release smoke test (THE release gate)
if: steps.already_published.outputs.skip != 'true'
# This is the gate that catches the bugs that `npm test` can't.
# Builds the exact tarball that would be published, installs it
# in a clean tmpdir, verifies:
# - both binaries (sipcode, sipcode-mcp) install correctly
# - sipcode --version reports the actual package.json version
# (catches hardcoded-version bugs like v1.0.0–v1.1.4)
# - sipcode-mcp boots, completes MCP handshake, registers all
# 4 documented tools (catches the @latest 404 bug)
# - tarball contains pricing JSON + fonts (catches v1.0.0 bug)
# - tests/ NOT included
# - privacy guard preserved in compiled dist/
# ANY failure here BLOCKS the publish.
run: npm run test:e2e
- name: publish to npm
if: steps.already_published.outputs.skip != 'true'
# With Trusted Publishers configured + permissions.id-token=write +
# setup-node's registry-url + npm >= 11.5.1, npm publish auto-uses
# OIDC and auto-generates provenance attestations.
run: npm publish
- name: smoke-test (runs whether skipped or just-published)
# Retries because npm's CDN takes 15-45s to propagate a fresh publish.
# Without the retry, the smoke test 404'd v1.1.2 the moment after
# publish even though the package was live within 30 seconds.
run: |
TAG_REF="${{ inputs.tag || github.ref }}"
TAG="${TAG_REF#refs/tags/}"
TAG_VERSION="${TAG#v}"
echo "verifying sipcode@$TAG_VERSION is reachable on the registry..."
set +e
for i in 1 2 3 4 5 6; do
OUT="$(npm view "sipcode@$TAG_VERSION" version 2>/dev/null)"
if [ -n "$OUT" ]; then
echo "✓ found sipcode@$OUT on attempt $i"
npm view "sipcode@$TAG_VERSION" version dist.tarball dist.unpackedSize
exit 0
fi
echo " attempt $i: not yet on CDN — sleeping 10s..."
sleep 10
done
echo "::error::sipcode@$TAG_VERSION never appeared on the registry after 60s — investigate."
exit 1