Skip to content

npm publish

npm publish #712

Workflow file for this run

name: npm publish
on:
# Publish only after the canonical CI workflow completes for the exact main
# commit. The former duplicate gate omitted validators and native checks.
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]
concurrency:
group: npm-publish
cancel-in-progress: false
jobs:
publish:
# workflow_run can represent PR CI as well. Expose npm credentials only
# after a successful push CI run on main.
if: >-
${{ github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 2
persist-credentials: false
- uses: actions/setup-node@v7
with:
# Node 20 matches the engines field: build + publish on the minimum
# supported runtime so the published artifact is verified against it.
node-version: 20
registry-url: https://registry.npmjs.org
# Only the commit that actually changed package.json may publish. This
# prevents a later same-version main commit from becoming the package
# provenance when the version commit's CI finishes earlier or later.
- name: Verify version commit and check npm
id: version-check
run: |
set -euo pipefail
PACKAGE_VERSION=$(node -p "require('./package.json').version")
echo "version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
PARENT_VERSION=$(git show HEAD^:package.json 2>/dev/null | node -e \
"let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const v=JSON.parse(s).version;if(typeof v!=='string'||!v)throw new Error('parent package version is missing');process.stdout.write(v)})")
node -e "const {assertReleaseVersion}=require('./scripts/version-files.cjs');assertReleaseVersion(process.argv[1],'package.json version');assertReleaseVersion(process.argv[2],'parent package.json version')" \
"$PACKAGE_VERSION" "$PARENT_VERSION"
if [ "$PARENT_VERSION" = "$PACKAGE_VERSION" ]; then
echo "eligible=false" >> "$GITHUB_OUTPUT"
echo "package.json version did not change in this tested commit; skipping publish."
exit 0
fi
echo "eligible=true" >> "$GITHUB_OUTPUT"
npm_result=$(mktemp)
npm_error=$(mktemp)
trap 'rm -f "$npm_result" "$npm_error"' EXIT
if npm view tokentracker-cli@"$PACKAGE_VERSION" version --json >"$npm_result" 2>"$npm_error"; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
lookup_code=$(node -e "const fs=require('fs');try{const r=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(r?.error?.code||'')}catch{}" "$npm_result")
if [ "$lookup_code" = "E404" ]; then
echo "exists=false" >> "$GITHUB_OUTPUT"
else
cat "$npm_error" >&2
echo "npm registry lookup failed without a confirmed E404" >&2
exit 1
fi
fi
- name: Install root dependencies
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'false'
run: npm ci
- name: Install dashboard dependencies
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'false'
run: npm ci --prefix dashboard
# InsForge URL + anon key are public client-side values shipped in every
# dashboard bundle. They MUST be present at build time or Vite inlines
# empty strings — breaks leaderboard fetch and disables cloud OAuth.
# `dashboard/.env.local` is gitignored so CI cannot read it.
- name: Build dashboard
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'false'
env:
VITE_INSFORGE_BASE_URL: https://srctyff5.us-east.insforge.app
VITE_INSFORGE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OC0xMjM0LTU2NzgtOTBhYi1jZGVmMTIzNDU2NzgiLCJlbWFpbCI6ImFub25AaW5zZm9yZ2UuY29tIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODExNDU5NDd9.T0auta_IrVIh0uXW1bob5QSnzvsnJmN28r5XkSGEuQY
run: npm run dashboard:build
- name: Publish to npm
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'false'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Skip unchanged-version commit
if: steps.version-check.outputs.eligible != 'true'
env:
PACKAGE_VERSION: ${{ steps.version-check.outputs.version }}
run: printf 'v%s was not changed by this commit; skipping publish.\n' "$PACKAGE_VERSION"
- name: Skip already-published version
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'true'
env:
PACKAGE_VERSION: ${{ steps.version-check.outputs.version }}
run: printf 'v%s is already published; skipping.\n' "$PACKAGE_VERSION"
# Notify homebrew-tokentracker tap to bump the Formula immediately.
# If HOMEBREW_DISPATCH_TOKEN is not set, this step silently no-ops and
# the tap's own hourly cron picks up the new version within ~1 hour.
- name: Dispatch homebrew tap update
if: steps.version-check.outputs.eligible == 'true' && steps.version-check.outputs.exists == 'false'
env:
HOMEBREW_DISPATCH_TOKEN: ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}
VERSION: ${{ steps.version-check.outputs.version }}
run: |
if [ -z "${HOMEBREW_DISPATCH_TOKEN:-}" ]; then
echo "HOMEBREW_DISPATCH_TOKEN not set — tap will update via hourly cron."
exit 0
fi
curl -sSL -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $HOMEBREW_DISPATCH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/xiufengsun/homebrew-tokentracker/dispatches \
-d "{\"event_type\":\"tokentracker-release\",\"client_payload\":{\"source\":\"npm\",\"version\":\"$VERSION\"}}"