Publish v0.2.36 to npm #1
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: Publish npm | |
| run-name: Publish ${{ inputs.tag }} to npm | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: Existing attested GitHub release tag, for example v0.2.14 | |
| required: true | |
| type: string | |
| confirm_publish: | |
| description: Publish the verified release archive to npm | |
| required: true | |
| default: false | |
| type: boolean | |
| permissions: {} | |
| concurrency: | |
| group: npm-publish | |
| cancel-in-progress: false | |
| jobs: | |
| verify: | |
| name: Verify GitHub release | |
| if: ${{ inputs.confirm_publish && github.ref == 'refs/heads/main' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| outputs: | |
| archive_name: ${{ steps.package.outputs.archive_name }} | |
| archive_sha256: ${{ steps.package.outputs.archive_sha256 }} | |
| artifact_id: ${{ steps.handoff.outputs.artifact-id }} | |
| package_version: ${{ steps.package.outputs.package_version }} | |
| env: | |
| RELEASE_TAG: ${{ inputs.tag }} | |
| steps: | |
| - name: Check out the selected tag | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: refs/tags/${{ inputs.tag }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up Node | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '24' | |
| package-manager-cache: false | |
| - name: Activate pinned release tools | |
| run: | | |
| corepack enable | |
| corepack prepare pnpm@10.28.2 --activate | |
| npm install --global --ignore-scripts --no-audit --no-fund npm@12.0.1 | |
| test "$(npm --version)" = "12.0.1" | |
| - name: Require an exact release tag | |
| run: | | |
| node - <<'NODE' | |
| const pkg = require('./packages/cli/package.json') | |
| if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(pkg.version)) { | |
| throw new Error(`package version ${pkg.version} is not a stable canonical semver`) | |
| } | |
| const expected = `v${pkg.version}` | |
| if (process.env.RELEASE_TAG !== expected) { | |
| throw new Error(`requested tag ${process.env.RELEASE_TAG} does not match ${expected}`) | |
| } | |
| NODE | |
| TAG_COMMIT="$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" | |
| test "$(git rev-parse HEAD)" = "$TAG_COMMIT" | |
| - name: Download the existing GitHub release assets | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| mkdir release | |
| gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir release | |
| gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \ | |
| --json assets,isDraft,isImmutable,isPrerelease,tagName > release.json | |
| node - <<'NODE' | |
| const release = require('./release.json') | |
| const version = require('./packages/cli/package.json').version | |
| if (release.isDraft || release.isPrerelease || !release.isImmutable || release.tagName !== process.env.RELEASE_TAG) { | |
| throw new Error(`release ${process.env.RELEASE_TAG} is not the exact immutable public release`) | |
| } | |
| const { assets } = release | |
| const actual = assets.map((asset) => asset.name).sort() | |
| const expected = [ | |
| `codetruss-cli-${version}.sbom.cdx.json`, | |
| `codetruss-cli-${version}.tgz`, | |
| `codetruss-cli-${version}.tgz.sha256`, | |
| ].sort() | |
| if (JSON.stringify(actual) !== JSON.stringify(expected)) { | |
| throw new Error(`release assets differ from the exact publish set: ${actual.join(', ')}`) | |
| } | |
| NODE | |
| rm release.json | |
| - name: Verify release provenance | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="$(node -p "require('./packages/cli/package.json').version")" | |
| TAG_COMMIT="$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" | |
| gh attestation verify "release/codetruss-cli-${VERSION}.tgz" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --signer-workflow "$GITHUB_REPOSITORY/.github/workflows/release.yml" \ | |
| --source-ref "refs/tags/${RELEASE_TAG}" \ | |
| --source-digest "$TAG_COMMIT" \ | |
| --deny-self-hosted-runners | |
| - name: Resolve the verified package identity | |
| id: package | |
| run: | | |
| node - <<'NODE' | |
| const { createHash } = require('node:crypto') | |
| const { appendFileSync, readFileSync } = require('node:fs') | |
| const pkg = require('./packages/cli/package.json') | |
| const archiveName = `codetruss-cli-${pkg.version}.tgz` | |
| const archive = readFileSync(`release/${archiveName}`) | |
| const archiveSha256 = createHash('sha256').update(archive).digest('hex') | |
| appendFileSync(process.env.GITHUB_OUTPUT, `archive_name=${archiveName}\n`) | |
| appendFileSync(process.env.GITHUB_OUTPUT, `archive_sha256=${archiveSha256}\n`) | |
| appendFileSync(process.env.GITHUB_OUTPUT, `package_version=${pkg.version}\n`) | |
| NODE | |
| - name: Install locked dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Verify exact release bytes | |
| run: | | |
| pnpm build | |
| pnpm release:verify | |
| pnpm test:install | |
| - name: Dry-run the exact npm package path | |
| env: | |
| ARCHIVE_NAME: ${{ steps.package.outputs.archive_name }} | |
| run: | | |
| npm publish "./release/${ARCHIVE_NAME}" \ | |
| --dry-run \ | |
| --access public \ | |
| --ignore-scripts \ | |
| --registry=https://registry.npmjs.org | |
| - name: Require the verified archive to remain unchanged | |
| env: | |
| ARCHIVE_NAME: ${{ steps.package.outputs.archive_name }} | |
| ARCHIVE_SHA256: ${{ steps.package.outputs.archive_sha256 }} | |
| run: echo "${ARCHIVE_SHA256} release/${ARCHIVE_NAME}" | /usr/bin/sha256sum --check --strict | |
| - name: Upload the exact verified package | |
| id: handoff | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| path: release/${{ steps.package.outputs.archive_name }} | |
| archive: false | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Require the handoff digest to match the package | |
| env: | |
| ARCHIVE_SHA256: ${{ steps.package.outputs.archive_sha256 }} | |
| ARTIFACT_SHA256: ${{ steps.handoff.outputs.artifact-digest }} | |
| run: test "$ARTIFACT_SHA256" = "$ARCHIVE_SHA256" | |
| publish: | |
| name: Publish verified package | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: npm | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Validate the verified handoff identity | |
| env: | |
| ARCHIVE_NAME: ${{ needs.verify.outputs.archive_name }} | |
| ARCHIVE_SHA256: ${{ needs.verify.outputs.archive_sha256 }} | |
| ARTIFACT_ID: ${{ needs.verify.outputs.artifact_id }} | |
| PACKAGE_VERSION: ${{ needs.verify.outputs.package_version }} | |
| run: | | |
| [[ "$PACKAGE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] | |
| test "$ARCHIVE_NAME" = "codetruss-cli-${PACKAGE_VERSION}.tgz" | |
| [[ "$ARCHIVE_SHA256" =~ ^[0-9a-f]{64}$ ]] | |
| [[ "$ARTIFACT_ID" =~ ^[0-9]+$ ]] | |
| - name: Download the exact verified package | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| artifact-ids: ${{ needs.verify.outputs.artifact_id }} | |
| path: release | |
| digest-mismatch: error | |
| - name: Set up Node | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '24' | |
| package-manager-cache: false | |
| - name: Verify the downloaded package identity | |
| env: | |
| ARCHIVE_NAME: ${{ needs.verify.outputs.archive_name }} | |
| ARCHIVE_SHA256: ${{ needs.verify.outputs.archive_sha256 }} | |
| PACKAGE_VERSION: ${{ needs.verify.outputs.package_version }} | |
| run: | | |
| test "$(find release -mindepth 1 -maxdepth 1 -type f | wc -l | tr -d ' ')" = "1" | |
| test -z "$(find release -mindepth 1 -maxdepth 1 ! -type f -print -quit)" | |
| test -f "release/${ARCHIVE_NAME}" | |
| echo "${ARCHIVE_SHA256} release/${ARCHIVE_NAME}" | /usr/bin/sha256sum --check --strict | |
| tar -xOf "release/${ARCHIVE_NAME}" package/package.json > release/package.json | |
| PACKAGE_VERSION="$PACKAGE_VERSION" node - <<'NODE' | |
| const manifest = require('./release/package.json') | |
| if (manifest.name !== '@codetruss/cli' || manifest.version !== process.env.PACKAGE_VERSION) { | |
| throw new Error(`unexpected package identity ${manifest.name}@${manifest.version}`) | |
| } | |
| const publishConfig = Object.entries(manifest.publishConfig ?? {}) | |
| if (publishConfig.length !== 1 | |
| || publishConfig[0]?.[0] !== 'access' | |
| || publishConfig[0]?.[1] !== 'public') { | |
| throw new Error('package publishConfig must contain only public access') | |
| } | |
| NODE | |
| rm release/package.json | |
| - name: Activate pinned npm | |
| run: | | |
| npm install --global --ignore-scripts --no-audit --no-fund npm@12.0.1 | |
| test "$(npm --version)" = "12.0.1" | |
| - name: Publish with npm trusted publishing | |
| env: | |
| ARCHIVE_NAME: ${{ needs.verify.outputs.archive_name }} | |
| run: | | |
| npm publish "./release/${ARCHIVE_NAME}" \ | |
| --access public \ | |
| --ignore-scripts \ | |
| --provenance \ | |
| --registry=https://registry.npmjs.org |