Finalize product release #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: Finalize CLI npm release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| stage_run_id: | |
| description: Successful Stage CLI npm release workflow run ID | |
| required: true | |
| type: string | |
| stage_run_attempt: | |
| description: Successful Stage CLI npm release workflow run attempt | |
| required: true | |
| type: string | |
| version: | |
| description: Exact staged maka-agent version | |
| required: true | |
| type: string | |
| permissions: | |
| actions: read | |
| contents: read | |
| concurrency: | |
| group: cli-npm-finalize | |
| cancel-in-progress: false | |
| jobs: | |
| inspect: | |
| name: Verify the public npm release | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| outputs: | |
| dist_tag: ${{ steps.release.outputs.dist_tag }} | |
| git_tag: ${{ steps.release.outputs.git_tag }} | |
| public_release_artifact_id: ${{ steps.public-release.outputs.artifact-id }} | |
| source_sha: ${{ steps.release.outputs.source_sha }} | |
| tarball: ${{ steps.release.outputs.tarball }} | |
| version: ${{ steps.release.outputs.version }} | |
| steps: | |
| - name: Require main | |
| env: | |
| RELEASE_REF: ${{ github.ref }} | |
| run: | | |
| if [[ "$RELEASE_REF" != "refs/heads/main" ]]; then | |
| echo "CLI releases must be dispatched from main; found $RELEASE_REF" >&2 | |
| exit 1 | |
| fi | |
| - name: Load the exact stage workflow run | |
| id: stage-run | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| STAGE_RUN_ID: ${{ inputs.stage_run_id }} | |
| STAGE_RUN_ATTEMPT: ${{ inputs.stage_run_attempt }} | |
| run: | | |
| if [[ ! "$STAGE_RUN_ID" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "Stage workflow run ID must be a positive integer" >&2 | |
| exit 1 | |
| fi | |
| if [[ ! "$STAGE_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "Stage workflow run attempt must be a positive integer" >&2 | |
| exit 1 | |
| fi | |
| gh api "repos/$GITHUB_REPOSITORY/actions/runs/$STAGE_RUN_ID/attempts/$STAGE_RUN_ATTEMPT" > "$RUNNER_TEMP/stage-run.json" | |
| node -e ' | |
| const fs = require("node:fs"); | |
| const run = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); | |
| if ( | |
| String(run.id) !== process.env.STAGE_RUN_ID || | |
| String(run.run_attempt) !== process.env.STAGE_RUN_ATTEMPT || | |
| run.path !== ".github/workflows/release-cli-stage.yml" || | |
| run.event !== "workflow_dispatch" || | |
| run.head_branch !== "main" || | |
| run.conclusion !== "success" || | |
| run.head_repository?.full_name !== process.env.GITHUB_REPOSITORY | |
| ) { | |
| throw new Error("Stage run is not an exact successful main CLI stage attempt"); | |
| } | |
| if (!/^[0-9a-f]{40}$/.test(run.head_sha)) throw new Error("Stage run has no valid source SHA"); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, "source_sha=" + run.head_sha + "\n"); | |
| ' "$RUNNER_TEMP/stage-run.json" | |
| - name: Check out the current release verifier | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.sha }} | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '24' | |
| package-manager-cache: false | |
| - name: Select the release npm toolchain | |
| run: npm install --global --no-audit --no-fund "$(node -p 'require("./package.json").packageManager')" | |
| - name: Download the exact staged candidate | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: cli-staged-release-${{ inputs.stage_run_attempt }} | |
| path: packages/cli/release | |
| github-token: ${{ github.token }} | |
| repository: ${{ github.repository }} | |
| run-id: ${{ inputs.stage_run_id }} | |
| - name: Verify the stage run and release record | |
| id: release | |
| env: | |
| EXPECTED_VERSION: ${{ inputs.version }} | |
| run: | | |
| node scripts/release-cli-publication.mjs validate-stage-run \ | |
| packages/cli/release \ | |
| "$RUNNER_TEMP/stage-run.json" \ | |
| "$EXPECTED_VERSION" \ | |
| "$GITHUB_OUTPUT" | |
| - name: Fetch and verify the public registry bytes | |
| run: | | |
| node scripts/release-cli-publication.mjs fetch-registry \ | |
| packages/cli/release \ | |
| "$RUNNER_TEMP/registry-release" | |
| - name: Verify npm signatures and provenance | |
| run: | | |
| node scripts/release-cli-publication.mjs prepare-audit \ | |
| packages/cli/release \ | |
| "$RUNNER_TEMP/signature-audit" | |
| cd "$RUNNER_TEMP/signature-audit" | |
| npm audit signatures --json --include-attestations > audit.json | |
| node "$GITHUB_WORKSPACE/scripts/release-cli-publication.mjs" validate-audit \ | |
| "$GITHUB_WORKSPACE/packages/cli/release" \ | |
| "$RUNNER_TEMP/signature-audit/audit.json" | |
| - name: Preserve the verified public release | |
| id: public-release | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: cli-public-release-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/registry-release | |
| if-no-files-found: error | |
| compression-level: 0 | |
| retention-days: 30 | |
| publish: | |
| name: Create the GitHub CLI release | |
| needs: inspect | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| environment: | |
| name: npm-release | |
| url: https://github.com/maka-agent/maka-agent/releases/tag/${{ needs.inspect.outputs.git_tag }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Check out the current release finalizer | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.sha }} | |
| persist-credentials: false | |
| - name: Download the verified public release | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| artifact-ids: ${{ needs.inspect.outputs.public_release_artifact_id }} | |
| path: ${{ runner.temp }}/registry-release | |
| - name: Create the Git tag and GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_DIST_TAG: ${{ needs.inspect.outputs.dist_tag }} | |
| RELEASE_DIRECTORY: ${{ runner.temp }}/registry-release | |
| RELEASE_SHA: ${{ needs.inspect.outputs.source_sha }} | |
| RELEASE_TAG: ${{ needs.inspect.outputs.git_tag }} | |
| RELEASE_TARBALL_NAME: ${{ needs.inspect.outputs.tarball }} | |
| RELEASE_VERSION: ${{ needs.inspect.outputs.version }} | |
| run: | | |
| tag_json="$RUNNER_TEMP/release-tag.json" | |
| tag_ref="repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" | |
| if ! gh api "$tag_ref" > "$tag_json" 2>/dev/null; then | |
| if ! gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \ | |
| -f ref="refs/tags/$RELEASE_TAG" \ | |
| -f sha="$RELEASE_SHA" > "$tag_json"; then | |
| gh api "$tag_ref" > "$tag_json" | |
| fi | |
| fi | |
| TAG_JSON="$tag_json" node -e ' | |
| const fs = require("node:fs"); | |
| const tag = JSON.parse(fs.readFileSync(process.env.TAG_JSON, "utf8")); | |
| if ( | |
| tag.ref !== "refs/tags/" + process.env.RELEASE_TAG || | |
| tag.object?.type !== "commit" || | |
| tag.object.sha !== process.env.RELEASE_SHA | |
| ) { | |
| throw new Error("Git tag does not point to the verified CLI release commit"); | |
| } | |
| ' | |
| release_flags=(--latest=false) | |
| if [[ "$RELEASE_DIST_TAG" == "next" ]]; then | |
| release_flags+=(--prerelease) | |
| elif [[ "$RELEASE_DIST_TAG" == "latest" ]]; then | |
| release_flags+=(--prerelease=false) | |
| else | |
| echo "Unsupported CLI release dist-tag: $RELEASE_DIST_TAG" >&2 | |
| exit 1 | |
| fi | |
| release_endpoint="repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" | |
| release_json="$RUNNER_TEMP/github-release.json" | |
| release_assets=( | |
| "$RELEASE_DIRECTORY/$RELEASE_TARBALL_NAME" | |
| "$RELEASE_DIRECTORY/$RELEASE_TARBALL_NAME.sha256" | |
| "$RELEASE_DIRECTORY/$RELEASE_TARBALL_NAME.files.json" | |
| "$RELEASE_DIRECTORY/release.json" | |
| ) | |
| if ! gh api "$release_endpoint" > "$release_json" 2>/dev/null; then | |
| if ! gh release create "$RELEASE_TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --verify-tag \ | |
| --draft \ | |
| "${release_flags[@]}" \ | |
| --title "Maka CLI $RELEASE_VERSION" \ | |
| --notes-file "$RELEASE_DIRECTORY/release-notes.md"; then | |
| echo "GitHub Release creation did not confirm success; inspecting remote state" >&2 | |
| fi | |
| gh api "$release_endpoint" > "$release_json" | |
| fi | |
| release_draft="$(RELEASE_JSON="$release_json" node -e ' | |
| const fs = require("node:fs"); | |
| const release = JSON.parse(fs.readFileSync(process.env.RELEASE_JSON, "utf8")); | |
| if (typeof release.draft !== "boolean") throw new Error("GitHub Release draft state is invalid"); | |
| process.stdout.write(String(release.draft)); | |
| ')" | |
| if [[ "$release_draft" == "true" ]]; then | |
| gh release edit "$RELEASE_TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --verify-tag \ | |
| --draft=true \ | |
| "${release_flags[@]}" \ | |
| --title "Maka CLI $RELEASE_VERSION" \ | |
| --notes-file "$RELEASE_DIRECTORY/release-notes.md" | |
| gh release upload "$RELEASE_TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber \ | |
| "${release_assets[@]}" | |
| gh release edit "$RELEASE_TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --verify-tag \ | |
| --draft=false \ | |
| "${release_flags[@]}" \ | |
| --title "Maka CLI $RELEASE_VERSION" \ | |
| --notes-file "$RELEASE_DIRECTORY/release-notes.md" | |
| fi | |
| gh api "$release_endpoint" > "$release_json" | |
| node scripts/release-cli-publication.mjs validate-github-release \ | |
| "$RELEASE_DIRECTORY" \ | |
| "$release_json" | |
| latest_json="$RUNNER_TEMP/latest-release.json" | |
| if gh api "repos/$GITHUB_REPOSITORY/releases/latest" > "$latest_json" 2>/dev/null; then | |
| LATEST_JSON="$latest_json" node -e ' | |
| const fs = require("node:fs"); | |
| const latest = JSON.parse(fs.readFileSync(process.env.LATEST_JSON, "utf8")); | |
| if (latest.tag_name === process.env.RELEASE_TAG) { | |
| throw new Error("CLI release must not become the repository GitHub Latest release"); | |
| } | |
| ' | |
| fi |