Skip to content

πŸ“¦ npm Publish #28

πŸ“¦ npm Publish

πŸ“¦ npm Publish #28

Workflow file for this run

name: πŸ“¦ npm publish
# Cascade-owned β€” every npm-publishing repo carries the byte-identical copy
# (adopt by copying the template once; the sync then keeps it in lock-step;
# member edits are reverted on the next cascade). The thin dispatch shell:
# checkout β†’ setup-and-install β†’ build β†’ scripts/fleet/npm-publish.mts, which
# owns what + how the repo publishes.
#
# Default flow: manual dispatch, DRY-RUN unless `publish: true`; publishes the
# workspace's publishable packages via the fleet staged-publish script with
# npm provenance (OIDC trusted publishing β€” id-token: write, no long-lived
# npm token).
#
# ORDER RULE: this workflow only STAGES β€” nothing is public, and NO git tag or
# GitHub release exists yet. The tag + immutable GH release are cut LAST, by
# the local `--approve` promote (publish-pipeline.mts / npm-publish.mts
# --approve), only after the approved version is live on npm. The
# github-release workflow independently refuses to cut for a version that is
# not resolvable on the registry.
#
# BACKFILL: to republish prior content as a skipped GAP version β€” 1.4.3
# between a live 1.4.2 and 1.4.4 β€” dispatch from MAIN, where this file always
# exists, with `backfill-version` + `checkout-ref`. The checkout-ref supplies
# the CONTENT while the workflow definition stays main's. The bump/changelog
# gate is bypassed; hard gap-fill-only guards replace it (never-published
# version, lower than latest, non-latest dist-tag, content declares its own
# version) β€” see scripts/fleet/publish-infra/npm/backfill.mts.
on:
workflow_dispatch:
inputs:
publish:
description: 'Publish for real (false = dry-run, the default).'
type: boolean
default: false
dist-tag:
description: 'npm dist-tag to publish under.'
type: string
default: 'latest'
release-as:
description: >-
Force the bump level. MAJOR is never derived from commits β€” the
bump script stops on breaking commits unless a human selects
major here.
type: choice
options:
- ''
- major
- minor
- patch
default: ''
bump:
description: >-
Run the CI bump step: consume the committed version hint, write
CHANGELOG, and commit via the release App before staging. The
publish pipeline dispatches with false β€” its bump stage already
landed the bump commit, and a re-entrant CI bump once committed a
duplicate CHANGELOG section. Ignored by backfills, which never
bump.
type: boolean
default: true
backfill-version:
description: >-
Backfill a never-published GAP version below registry latest with
the content at checkout-ref. Bypasses the bump/changelog gate
behind hard gap-fill-only guards; requires checkout-ref and a
non-latest dist-tag.
type: string
default: ''
checkout-ref:
description: >-
Backfill only β€” the branch/tag/SHA whose CONTENT is republished.
The workflow definition always comes from the dispatched ref,
main, so historical content stays reachable.
type: string
default: ''
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
# npm's trusted-publisher config pins this GitHub environment name; the
# OIDC token exchange 404s if the job runs outside it.
environment: npm-publish
permissions:
contents: read
# npm provenance / trusted publishing mints its OIDC token here.
id-token: write
env:
# Socket Firewall + CLI auth for the sfw-wrapped setup + pnpm install β€”
# sfw and socket-cli read SOCKET_API_KEY from the org-wide secret.
SOCKET_API_KEY: ${{ secrets.SOCKET_API_TOKEN_FOR_CLI_AND_SFW }}
steps:
# First step must be the third-party actions/checkout (GitHub fetches it
# independently) to populate the workspace so the LOCAL
# ./.github/actions/* composite resolves; setup-and-install re-checks-out
# at its own deeper default.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 (2026-05-15)
with:
# Backfill checks out the content ref; empty = the dispatched ref.
ref: ${{ inputs.checkout-ref }}
fetch-depth: 1
# The bump derivation anchors on registry-latest + the last v-tag.
# On a first-publish repo the registry has nothing, so the tags are
# the only anchor β€” a tagless shallow checkout makes the engine
# derive from zero (0.1.0) and trip the half-applied-bump gate on
# historical CHANGELOG sections that describe shipped versions.
fetch-tags: true
persist-credentials: false
- name: Setup + install
uses: ./.github/actions/fleet/setup-and-install
with:
# Forward the backfill content ref β€” setup-and-install re-checks-out
# internally (fleet checkout falls back to the TRIGGERING ref when
# unset), which would silently swap the backfill content back to
# main's tree; the backfill gate then refuses against main's
# version. Empty forwards as unset, so normal dispatches keep the
# dispatched-ref re-checkout.
checkout-ref: ${{ inputs.checkout-ref }}
# Build dist/ before publishing β€” the staged-publish script imports the
# package's own built output (`@socketsecurity/lib/stdio/prompts` self-
# resolves to dist/), and `files` ships dist/, so the tarball needs it.
- name: Build
run: pnpm run build
# The release App signs the bump commit via the GitHub API β€” the
# workflow's own GITHUB_TOKEN stays contents: read. A backfill never
# commits a bump, and a bump=false pipeline dispatch already carries
# its bump commit, so both skip the mint.
- name: Mint release App token
if: ${{ inputs.backfill-version == '' && inputs.bump }}
id: release-app
uses: ./.github/actions/fleet/github-release-app-token
with:
client-id: ${{ vars.SOCKET_RELEASE_CLIENT_ID }}
private-key: ${{ secrets.SOCKET_RELEASE_APP_PRIVATE_KEY }}
# The promote PR that lands the bump on the default branch needs
# pull_requests:write, which the release App does not carry β€” the PR App
# does. Minted here, beside the release token and BEFORE the publish
# step, so a missing/ungranted PR App refuses while nothing is published:
# resolveReleaseEnv demands PR_APP_TOKEN at bump time, and the promote PR
# only runs after the irreversible registry write.
- name: Mint PR App token
if: ${{ inputs.backfill-version == '' && inputs.bump }}
id: pr-app
uses: ./.github/actions/fleet/github-pr-app-token
with:
client-id: ${{ vars.SOCKET_PR_CLIENT_ID }}
private-key: ${{ secrets.SOCKET_PR_APP_PRIVATE_KEY }}
# --bump consumes the committed version hint (X.Y.Z-prerelease β†’
# X.Y.Z), writes CHANGELOG, and commits via the release App (verified,
# signed) onto a throwaway npm-publish-v<version> branch β€” main is
# fast-forwarded to it only after the publish succeeds, and it is nuked on
# a rejected publish, so a failed stage never creeps the version. The
# version decision stays with the human: it is whatever the committed hint
# names.
# The bump runs EXACTLY ONCE across the pipeline + workflow chain: the
# publish pipeline dispatches with bump=false because its own bump stage
# already landed the bump commit β€” the CI re-bump once re-derived the
# same version and committed a duplicate CHANGELOG section. Manual
# dispatches keep the default bump=true hint-consuming flow.
# Backfill swaps --bump for --backfill + --checkout-ref: no bump commit,
# no changelog β€” the checked-out content publishes as-is once the
# backfill guards pass (npm-publish.mts fails loud when they don't).
- name: Publish
env:
BACKFILL_VERSION: ${{ inputs.backfill-version }}
CHECKOUT_REF: ${{ inputs.checkout-ref }}
DIST_TAG: ${{ inputs.dist-tag }}
PR_APP_TOKEN: ${{ steps.pr-app.outputs.token }}
RELEASE_AS: ${{ inputs.release-as }}
RELEASE_APP_TOKEN: ${{ steps.release-app.outputs.token }}
# CHECKOUT_REF forwards on its own so a checkout-ref dispatch WITHOUT
# backfill-version is refused by the script instead of silently
# bump-publishing historical content.
run: node scripts/fleet/npm-publish.mts --staged ${{ (inputs.backfill-version == '' && inputs.bump) && '--bump' || '' }} --tag "$DIST_TAG" ${RELEASE_AS:+--release-as "$RELEASE_AS"} ${BACKFILL_VERSION:+--backfill "$BACKFILL_VERSION"} ${CHECKOUT_REF:+--checkout-ref "$CHECKOUT_REF"} ${{ inputs.publish != true && '--dry-run' || '' }}