π¦ npm Publish #28
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: π¦ 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' || '' }} |