Merge pull request #16 from Anuj7411/here-everywhere #58
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |