Release / publish / main #173
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 | |
| run-name: Release / ${{ inputs.operation }} / ${{ github.ref_name }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| operation: | |
| description: Prepare the release PR, or freeze and publish the qualified candidate | |
| required: true | |
| type: choice | |
| default: prepare-release-pr | |
| options: | |
| - prepare-release-pr | |
| - publish | |
| release_commit: | |
| description: Optional approved source SHA for recovery after publication-only fixes | |
| required: false | |
| type: string | |
| default: "" | |
| approval_run_id: | |
| description: Optional previous Release run with a successfully prepared frozen candidate | |
| required: false | |
| type: string | |
| default: "" | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| CANONICAL_RELEASE_REPOSITORY: f0rr0/oliphaunt | |
| NPM_VERSION: 11.18.0 | |
| PUBLICATION_LOCK_PATH: target/release/publication-lock.json | |
| BOOTSTRAP_LEDGER_PATH: target/release/bootstrap-ledger | |
| RELEASE_JOB_HARD_WINDOW_SECONDS: 21180 | |
| BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS: 19800 | |
| POST_REGISTRY_RESERVE_SECONDS: 3240 | |
| PUBLIC_CONSUMER_SMOKE_TIMEOUT_SECONDS: 780 | |
| PUBLIC_CONSUMER_FINALIZATION_RESERVE_SECONDS: 600 | |
| MAVEN_CENTRAL_NAMESPACE: dev.oliphaunt | |
| concurrency: | |
| group: release-mutation | |
| cancel-in-progress: false | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| validate-inputs: | |
| name: Validate release inputs | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Require canonical release repository | |
| run: | | |
| if [[ "${GITHUB_REPOSITORY}" != "${CANONICAL_RELEASE_REPOSITORY}" ]]; then | |
| echo "Release workflow is pinned to ${CANONICAL_RELEASE_REPOSITORY}; got ${GITHUB_REPOSITORY}" >&2 | |
| exit 1 | |
| fi | |
| - name: Checkout exact workflow commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| ref: ${{ github.sha }} | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Validate release workflow inputs | |
| id: validate_release_inputs | |
| env: | |
| RELEASE_OPERATION: ${{ inputs.operation }} | |
| RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| RELEASE_APPROVAL_RUN_ID: ${{ inputs.approval_run_id }} | |
| run: bash .github/scripts/validate-release-workflow-inputs.sh | |
| prepare-release-pr: | |
| name: Prepare release PR | |
| needs: validate-inputs | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| if: ${{ inputs.operation == 'prepare-release-pr' }} | |
| environment: release-pr | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Require main | |
| run: | | |
| if [[ "${GITHUB_REF}" != "refs/heads/main" ]]; then | |
| echo "Releases must be run from main; got ${GITHUB_REF}" >&2 | |
| exit 1 | |
| fi | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Require current main | |
| id: require_current_main | |
| timeout-minutes: 1 | |
| run: bash .github/scripts/require-current-main.sh "$GITHUB_SHA" | |
| - name: Require release PR token | |
| env: | |
| RELEASE_PR_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }} | |
| run: | | |
| if [[ -z "${RELEASE_PR_TOKEN}" ]]; then | |
| echo "RELEASE_PR_TOKEN is required so generated release PRs trigger normal PR CI." >&2 | |
| echo "Configure a GitHub App or maintainer bot token in the release-pr environment." >&2 | |
| exit 1 | |
| fi | |
| - name: Set up Moon | |
| uses: ./.github/actions/setup-moon | |
| with: | |
| install-workspace: "true" | |
| - name: Generate the Release Please candidate locally | |
| id: prepare_candidate | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }} | |
| run: bash tools/release/prepare-release-pr.sh "$RUNNER_TEMP/release-candidate" | |
| - name: Set up Rust for release manifest synchronization | |
| if: ${{ steps.prepare_candidate.outputs.required == 'true' }} | |
| uses: ./.github/actions/setup-rust | |
| - name: Close and validate the local release candidate | |
| if: ${{ steps.prepare_candidate.outputs.required == 'true' }} | |
| run: bash tools/release/close-release-candidate.sh "$RUNNER_TEMP/release-candidate" | |
| - name: Publish the completed release PR tree | |
| if: ${{ steps.prepare_candidate.outputs.required == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }} | |
| run: bash .github/scripts/publish-release-pr.sh "$RUNNER_TEMP/release-candidate" | |
| - name: Report no release changes | |
| if: ${{ steps.prepare_candidate.outputs.required != 'true' }} | |
| run: echo 'Release Please found no releasable changes.' | |
| plan-candidate: | |
| name: Plan publication and qualification | |
| needs: | |
| - validate-inputs | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| if: ${{ inputs.operation == 'publish' }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Resolve release commit | |
| id: release_head | |
| timeout-minutes: 1 | |
| env: | |
| INPUT_RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| run: .github/scripts/resolve-release-head.sh | |
| - name: Set up Moon | |
| uses: ./.github/actions/setup-moon | |
| with: | |
| install-workspace: "true" | |
| - name: Plan product releases | |
| id: release_plan | |
| run: | | |
| release_plan_args=( | |
| --from-product-tags | |
| --include-current-tags | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| --format github-output | |
| ) | |
| bash tools/release/release-plan.sh "${release_plan_args[@]}" >> "$GITHUB_OUTPUT" | |
| - name: No package release planned | |
| if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }} | |
| run: echo "No release-affecting product changes were found since the last product tag." | |
| - name: Resolve selected registry authentication needs | |
| id: registry_needs | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bun .github/scripts/selected-registry-needs.mts | |
| - name: Prove pending release identity | |
| id: verify_publication_candidate | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 2 | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Prove Release Please PR can complete after publication | |
| id: assert_release_please_markable | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 1 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ | |
| assert-markable \ | |
| --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ | |
| --base main | |
| - name: Plan qualification reuse or request | |
| id: ci_qualification | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| REQUIRES_WASIX_EVIDENCE: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }} | |
| REQUIRES_NATIVE_EVIDENCE: ${{ steps.release_plan.outputs.requires_native_extension_lifecycle_evidence }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| qualification_args=( | |
| CI | |
| "$RELEASE_HEAD_SHA" | |
| 0 | |
| --plan-qualification "$PRODUCTS_JSON" | |
| --event push | |
| --event workflow_dispatch | |
| --job Builds | |
| --job Required | |
| --job Qualified | |
| --artifact artifact-build-plan | |
| --artifact oliphaunt-release-candidate | |
| ) | |
| if [[ "$REQUIRES_NATIVE_EVIDENCE" == true ]]; then | |
| qualification_args+=(--artifact native-extension-lifecycle-evidence) | |
| fi | |
| if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then | |
| qualification_args+=(--artifact wasix-release-regression-evidence) | |
| fi | |
| if [[ "${{ steps.release_plan.outputs.has_extension_artifacts }}" == true ]]; then | |
| qualification_args+=(--artifact oliphaunt-extension-package-artifacts) | |
| fi | |
| bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}" | |
| outputs: | |
| release_plan: ${{ toJSON(steps.release_plan.outputs) }} | |
| release_head: ${{ toJSON(steps.release_head.outputs) }} | |
| registry_needs: ${{ toJSON(steps.registry_needs.outputs) }} | |
| verify_publication_candidate: ${{ toJSON(steps.verify_publication_candidate.outputs) }} | |
| qualification_request_required: ${{ steps.ci_qualification.outputs.qualification_request_required }} | |
| request-qualification: | |
| name: Request missing CI qualification | |
| needs: plan-candidate | |
| if: ${{ needs.plan-candidate.outputs.qualification_request_required == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| actions: write | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Set up Bun | |
| uses: ./.github/actions/setup-bun | |
| - name: Request or reuse the candidate CI run | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| RELEASE_HEAD_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: bash .github/scripts/require-workflow-success.sh CI "$RELEASE_HEAD_SHA" 120 --dispatch-qualification "$PRODUCTS_JSON" | |
| prepare-candidate: | |
| name: Prepare frozen publication candidate | |
| needs: | |
| - plan-candidate | |
| - request-qualification | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| if: ${{ !cancelled() && needs.plan-candidate.result == 'success' && (needs.request-qualification.result == 'success' || needs.request-qualification.result == 'skipped') && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| environment: release-dry-run | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Restore exact publication source | |
| env: | |
| INPUT_RELEASE_COMMIT: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: .github/scripts/resolve-release-head.sh | |
| - name: Set up Moon | |
| uses: ./.github/actions/setup-moon | |
| with: | |
| install-workspace: "true" | |
| - name: Require qualified release-commit CI run | |
| id: ci_qualification | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| REQUIRES_WASIX_EVIDENCE: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence }} | |
| REQUIRES_NATIVE_EVIDENCE: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence }} | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: | | |
| qualification_args=( | |
| CI | |
| "$RELEASE_HEAD_SHA" | |
| 7200 | |
| --qualification-products "$PRODUCTS_JSON" | |
| --event push | |
| --event workflow_dispatch | |
| --job Builds | |
| --job Required | |
| --job Qualified | |
| --artifact artifact-build-plan | |
| --artifact oliphaunt-release-candidate | |
| ) | |
| if [[ "$REQUIRES_NATIVE_EVIDENCE" == true ]]; then | |
| qualification_args+=(--artifact native-extension-lifecycle-evidence) | |
| fi | |
| if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then | |
| qualification_args+=(--artifact wasix-release-regression-evidence) | |
| fi | |
| if [[ "${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts }}" == true ]]; then | |
| qualification_args+=(--artifact oliphaunt-extension-package-artifacts) | |
| fi | |
| bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}" | |
| - name: Download exact-SHA qualification record | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_HEAD_SHA" \ | |
| target/release-candidate \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Qualified \ | |
| --artifact oliphaunt-release-candidate | |
| - name: Download exact-SHA affected plan | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_HEAD_SHA" \ | |
| target/release-candidate/affected-plan \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job "Planning / Affected Work" \ | |
| --artifact artifact-build-plan | |
| - name: Download required exact-SHA WASIX evidence | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_HEAD_SHA" \ | |
| target/release-candidate/wasix-evidence \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job "E2E / WASIX Extension Lifecycle" \ | |
| --artifact wasix-release-regression-evidence | |
| - name: Download required exact-SHA native evidence | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_HEAD_SHA" \ | |
| target/release-candidate/native-evidence \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job "E2E / Native Extension Lifecycle Evidence" \ | |
| --artifact native-extension-lifecycle-evidence | |
| - name: Verify exact-SHA qualification record | |
| id: verify_qualification | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| WASIX_EVIDENCE_REQUIRED: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence }} | |
| NATIVE_EVIDENCE_REQUIRED: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence }} | |
| run: | | |
| bash .github/scripts/release-candidate.sh verify \ | |
| target/release-candidate/oliphaunt-release-candidate.json \ | |
| --plan target/release-candidate/affected-plan/ci-plan.json \ | |
| --qualification-mode release \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --native-evidence-required "$NATIVE_EVIDENCE_REQUIRED" \ | |
| --native-evidence-root target/release-candidate/native-evidence \ | |
| --wasix-evidence-required "$WASIX_EVIDENCE_REQUIRED" \ | |
| --wasix-evidence-root target/release-candidate/wasix-evidence | |
| - name: Require the explicitly selected prepared candidate | |
| id: approved_publication_lock | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| APPROVAL_RUN_ID: ${{ inputs.approval_run_id }} | |
| run: | | |
| approved_artifacts=( | |
| --artifact oliphaunt-publication-lock | |
| --artifact oliphaunt-publication-candidate | |
| ) | |
| gate_output="$RUNNER_TEMP/approved-publication-inputs-gate.out" | |
| GITHUB_OUTPUT="$gate_output" \ | |
| bash .github/scripts/require-workflow-success.sh \ | |
| Release \ | |
| "$RELEASE_HEAD_SHA" \ | |
| 0 \ | |
| --run-id "$APPROVAL_RUN_ID" \ | |
| --release-candidate \ | |
| --event workflow_dispatch \ | |
| "${approved_artifacts[@]}" | |
| cat "$gate_output" >> "$GITHUB_OUTPUT" | |
| - name: Download the approved lock and frozen candidate | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| run-id: ${{ steps.approved_publication_lock.outputs.run_id }} | |
| artifact-ids: ${{ steps.approved_publication_lock.outputs.artifact_ids }} | |
| merge-multiple: true | |
| path: ${{ runner.temp }}/approved-publication | |
| - name: Verify and install the complete approved candidate | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| APPROVAL_RUN_ID: ${{ inputs.approval_run_id }} | |
| run: | | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \ | |
| --transport "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" \ | |
| --approved-lock "$RUNNER_TEMP/approved-publication/publication-lock.json" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --approval-run-id "$APPROVAL_RUN_ID" \ | |
| --workspace-root "$GITHUB_WORKSPACE" | |
| - name: Validate product versions and registry state | |
| id: validate_release_registry_state | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: | | |
| bash tools/release/release-check-registries.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Download WASIX runtime build artifacts | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: bash .github/scripts/download-wasix-runtime-build-artifacts.sh | |
| - name: Download WASIX release assets | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/oliphaunt-wasix/release-assets \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| --artifact liboliphaunt-wasix-release-assets | |
| - name: Download WASIX postmaster release assets | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/oliphaunt-wasix-postmaster/release-assets \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| --artifact liboliphaunt-wasix-postmaster-release-assets | |
| - name: Download exact-extension package artifacts | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/extension-artifacts \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| --artifact oliphaunt-extension-package-artifacts | |
| - name: Download SDK package artifacts | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| download_sdk_artifact() { | |
| local product="$1" | |
| local artifact_args=() | |
| while IFS= read -r artifact; do | |
| artifact_args+=(--artifact "$artifact") | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product "$product" --family sdk-package --format lines) | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| "target/sdk-artifacts/$product" \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| "${artifact_args[@]}" | |
| } | |
| while IFS= read -r product; do | |
| download_sdk_artifact "$product" | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-products --family sdk-package --products-json "$PRODUCTS_JSON" --format lines) | |
| - name: Download liboliphaunt release assets | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/liboliphaunt/release-assets \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| --artifact liboliphaunt-native-release-assets | |
| - name: Download canonical database resources | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'database-resources') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| artifacts=() | |
| for kind in icu-data native-seeds wasix-seeds; do | |
| while IFS= read -r artifact; do | |
| artifacts+=(--artifact "$artifact") | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product database-resources --kind "$kind" --family release-assets --format lines) | |
| done | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/database-resources/release-assets \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| "${artifacts[@]}" | |
| - name: Download native helper release assets | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix')) }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PRODUCT_POSTGRES_TOOLS_WASIX: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix') }} | |
| PRODUCT_POSTGRES_TOOLS_NATIVE: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') }} | |
| PRODUCT_OLIPHAUNT_BROKER: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} | |
| PRODUCT_OLIPHAUNT_NODE_DIRECT: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} | |
| PRODUCT_OLIPHAUNT_WASIX_NAPI: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| download_helper_artifacts() { | |
| local product="$1" | |
| local kind="$2" | |
| local destination="$3" | |
| local artifact_args=() | |
| while IFS= read -r artifact; do | |
| artifact_args+=(--artifact "$artifact") | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product "$product" --kind "$kind" --family release-assets --format lines) | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| "$destination" \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| "${artifact_args[@]}" | |
| } | |
| if [ "$PRODUCT_POSTGRES_TOOLS_WASIX" = "true" ]; then | |
| download_helper_artifacts postgres-tools-wasix wasix-tools target/postgres-tools/wasix/release-assets | |
| download_helper_artifacts postgres-tools-wasix wasix-tools-aot target/postgres-tools/wasix/release-assets | |
| fi | |
| if [ "$PRODUCT_POSTGRES_TOOLS_NATIVE" = "true" ]; then | |
| download_helper_artifacts postgres-tools-native native-tools target/postgres-tools/native/release-assets | |
| fi | |
| if [ "$PRODUCT_OLIPHAUNT_BROKER" = "true" ]; then | |
| download_helper_artifacts \ | |
| oliphaunt-broker \ | |
| broker-helper \ | |
| target/oliphaunt-broker/release-assets | |
| fi | |
| if [ "$PRODUCT_OLIPHAUNT_NODE_DIRECT" = "true" ]; then | |
| download_helper_artifacts \ | |
| oliphaunt-node-direct \ | |
| node-direct-addon \ | |
| target/oliphaunt-node-direct/release-assets | |
| fi | |
| if [ "$PRODUCT_OLIPHAUNT_WASIX_NAPI" = "true" ]; then | |
| download_helper_artifacts \ | |
| oliphaunt-wasix-napi \ | |
| wasix-napi-addon \ | |
| target/oliphaunt-wasix-napi/release-assets | |
| fi | |
| - name: Download Node direct optional npm packages | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| artifact_args=() | |
| while IFS= read -r artifact; do | |
| artifact_args+=(--artifact "$artifact") | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product oliphaunt-node-direct --kind node-direct-addon --family npm-package --format lines) | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/oliphaunt-node-direct/npm-packages \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| "${artifact_args[@]}" | |
| - name: Download WASIX Node-API optional npm packages | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} | |
| RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: | | |
| artifact_args=() | |
| while IFS= read -r artifact; do | |
| artifact_args+=(--artifact "$artifact") | |
| done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product oliphaunt-wasix-napi --kind wasix-napi-addon --family npm-package --format lines) | |
| bash .github/scripts/download-build-artifacts.sh \ | |
| CI \ | |
| "$RELEASE_ARTIFACT_SHA" \ | |
| target/oliphaunt-wasix-napi/npm-packages \ | |
| --run-id "$CI_RUN_ID" \ | |
| --job Builds \ | |
| "${artifact_args[@]}" | |
| - name: Freeze canonical Apple extension carrier input | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native')) }} | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| includes_swift: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} | |
| includes_react_native: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native') }} | |
| run: | | |
| swift_source_carrier=target/sdk-artifacts/oliphaunt-swift/release-tree/src/sdks/swift/Carriers/oliphaunt-react-native-ios-carriers.json | |
| react_native_source_carrier=target/sdk-artifacts/oliphaunt-react-native/ios-carriers/oliphaunt-react-native-ios-carriers.json | |
| extension_manifest_args=() | |
| extension_carrier_args=() | |
| product_roots="$(tools/dev/bun.sh tools/release/query.mts \ | |
| ci-products \ | |
| --family extension-artifacts \ | |
| --carrier-family native \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --field artifact-root \ | |
| --format lines)" | |
| while IFS= read -r product_root; do | |
| [[ -n "$product_root" ]] || continue | |
| product="$(basename "$product_root")" | |
| manifest="$product_root/extension-artifacts.json" | |
| if [[ ! -f "$manifest" ]]; then | |
| echo "Selected extension product $product is missing $manifest" >&2 | |
| exit 1 | |
| fi | |
| extension_manifest_args+=(--extension-manifest "$manifest") | |
| product_carriers=() | |
| while IFS= read -r carrier; do | |
| product_carriers+=("$carrier") | |
| done < <(find "$product_root/release-assets" -maxdepth 1 -type f -name '*-swift-extension-carrier.json' | LC_ALL=C sort) | |
| if (( ${#product_carriers[@]} != 1 )); then | |
| echo "Selected extension product $product must provide exactly one independent Swift carrier; found ${#product_carriers[@]}" >&2 | |
| exit 1 | |
| fi | |
| extension_carrier_args+=(--extension-carrier "${product_carriers[0]}") | |
| done <<< "$product_roots" | |
| rm -rf target/release/ios-carriers target/release-work/ios-carriers | |
| if [[ "$includes_swift" == true && "$includes_react_native" == true ]] && ! cmp -s "$swift_source_carrier" "$react_native_source_carrier"; then | |
| echo 'Swift and React Native source carriers disagree for the same release plan.' >&2 | |
| diff --unified "$swift_source_carrier" "$react_native_source_carrier" || true | |
| exit 1 | |
| fi | |
| if [[ "$includes_react_native" == true ]]; then | |
| mkdir -p target/release/ios-carriers | |
| public_carrier=target/release/ios-carriers/oliphaunt-react-native-ios-carriers.json | |
| if (( ${#extension_manifest_args[@]} == 0 )); then | |
| cp "$react_native_source_carrier" "$public_carrier" | |
| else | |
| public_args=( | |
| --base-carrier "$react_native_source_carrier" | |
| "${extension_manifest_args[@]}" | |
| --output "$public_carrier" | |
| ) | |
| tools/dev/bun.sh src/native/sdks/swift/tools/ios-carrier-manifest.mts "${public_args[@]}" | |
| fi | |
| fi | |
| if [[ "$includes_swift" == true && ${#extension_manifest_args[@]} -gt 0 ]]; then | |
| mkdir -p target/release-work/ios-carriers | |
| local_aggregate_carrier=target/release-work/ios-carriers/oliphaunt-react-native-ios-carriers.json | |
| local_args=( | |
| --base-carrier "$swift_source_carrier" | |
| "${extension_manifest_args[@]}" | |
| --output "$local_aggregate_carrier" | |
| --local-urls | |
| ) | |
| tools/dev/bun.sh src/native/sdks/swift/tools/ios-carrier-manifest.mts "${local_args[@]}" | |
| extensions_csv="$(bun src/native/sdks/swift/tools/ios-carrier-manifest.mts list-extensions "$local_aggregate_carrier")" | |
| if [[ -z "$extensions_csv" || ${#extension_carrier_args[@]} == 0 ]]; then | |
| echo 'Swift extension validation requires selected extension carrier assets.' >&2 | |
| exit 1 | |
| fi | |
| swift_version="$(tools/dev/bun.sh tools/release/product-version.mts version oliphaunt-swift)" | |
| cache=target/release-work/swiftpm-extension-cache | |
| bun src/native/sdks/swift/tools/render-extension-products.mts \ | |
| --carrier "$local_aggregate_carrier" \ | |
| --extensions "$extensions_csv" \ | |
| --cache-dir "$cache" \ | |
| --allow-file-urls \ | |
| --base-package-version "$swift_version" \ | |
| --output-dir target/release-work/swiftpm-extension-cache-warm | |
| bun src/native/sdks/swift/tools/render-extension-products.mts \ | |
| --carrier "$swift_source_carrier" \ | |
| "${extension_carrier_args[@]}" \ | |
| --extensions "$extensions_csv" \ | |
| --cache-dir "$cache" \ | |
| --offline \ | |
| --base-package-version "$swift_version" \ | |
| --output-dir target/release/swiftpm-extension-consumer-fixture | |
| fi | |
| - name: Set up pinned npm publisher | |
| id: setup_github_stage_npm | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} | |
| timeout-minutes: 3 | |
| uses: ./.github/actions/setup-npm-publisher | |
| with: | |
| npm-version: ${{ env.NPM_VERSION }} | |
| - name: Verify unchanged candidate source before assembly | |
| id: validate_release | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| CANDIDATE_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} | |
| run: bash tools/release/qualified-release-replay.sh "$CANDIDATE_SHA" "$CANDIDATE_SHA" | |
| - name: Package public release carriers | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| OLIPHAUNT_BROKER_RELEASE_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-broker/release-assets | |
| OLIPHAUNT_NODE_ADDON_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-node-direct/release-assets | |
| OLIPHAUNT_WASIX_NAPI_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-wasix-napi/release-assets | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: bash tools/release/package-release-carriers.sh --products-json "$PRODUCTS_JSON" | |
| - name: Freeze exhaustive publication lock | |
| id: freeze_publication_lock | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: | | |
| lock_args=( | |
| create | |
| --products-json "$PRODUCTS_JSON" | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| --output target/release/publication-lock.json | |
| ) | |
| for artifact_root in \ | |
| target/release \ | |
| target/sdk-artifacts \ | |
| target/liboliphaunt/release-assets \ | |
| target/liboliphaunt/cargo-artifacts \ | |
| target/postgres-tools/native/release-assets \ | |
| target/postgres-tools/native/cargo-artifacts \ | |
| target/postgres-tools/wasix/release-assets \ | |
| target/postgres-tools/wasix/cargo-artifacts \ | |
| target/database-resources/release-assets \ | |
| target/database-resources/cargo-artifacts \ | |
| target/database-resources/seed-carriers \ | |
| target/oliphaunt-wasix/release-assets \ | |
| target/oliphaunt-wasix-postmaster/release-assets \ | |
| target/oliphaunt-broker/release-assets \ | |
| target/oliphaunt-broker/cargo-artifacts \ | |
| target/oliphaunt-wasix/cargo-artifacts \ | |
| target/oliphaunt-node-direct/release-assets \ | |
| target/oliphaunt-node-direct/npm-packages \ | |
| target/oliphaunt-wasix-napi/release-assets \ | |
| target/oliphaunt-wasix-napi/npm-packages; do | |
| if [[ -e "$artifact_root" ]]; then | |
| lock_args+=(--artifact-root "$artifact_root") | |
| fi | |
| done | |
| while IFS= read -r artifact_root; do | |
| if [[ ! -d "$artifact_root" ]]; then | |
| echo "Selected extension artifacts are missing $artifact_root" >&2 | |
| exit 1 | |
| fi | |
| lock_args+=(--artifact-root "$artifact_root") | |
| done < <(tools/dev/bun.sh tools/release/query.mts \ | |
| ci-products \ | |
| --family extension-artifacts \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --field artifact-root \ | |
| --format lines) | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts "${lock_args[@]}" | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \ | |
| verify \ | |
| --lock target/release/publication-lock.json \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Freeze complete publication candidate | |
| id: freeze_publication_candidate | |
| if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: | | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts pack \ | |
| --lock "$PUBLICATION_LOCK_PATH" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --approval-run-id "$GITHUB_RUN_ID" \ | |
| --qualification-run-id "${{ steps.ci_qualification.outputs.run_id }}" \ | |
| --output target/release/oliphaunt-publication-candidate.tar | |
| - name: Preserve the approved recovery capsule unchanged | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} | |
| run: cp "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" target/release/oliphaunt-publication-candidate.tar | |
| - name: Upload frozen publication lock | |
| id: preserve_publication_lock | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: oliphaunt-publication-lock | |
| path: target/release/publication-lock.json | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Upload complete frozen publication candidate | |
| id: preserve_publication_candidate | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: oliphaunt-publication-candidate | |
| path: target/release/oliphaunt-publication-candidate.tar | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Check whether first registry identities need credentials | |
| id: bootstrap_credentials | |
| if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} | |
| timeout-minutes: 15 | |
| env: | |
| PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} | |
| run: |- | |
| export REGISTRY_MUTATION_DEADLINE_EPOCH="$(( $(date +%s) + 840 ))" | |
| bun .github/scripts/bootstrap-registry-identities.mts --credential-needs | |
| outputs: | |
| has_release_changes: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes }} | |
| lock_artifact_id: ${{ steps.preserve_publication_lock.outputs.artifact-id }} | |
| candidate_artifact_id: ${{ steps.preserve_publication_candidate.outputs.artifact-id }} | |
| bootstrap_required: ${{ steps.bootstrap_credentials.outputs.needs_cargo_token == 'true' || steps.bootstrap_credentials.outputs.needs_npm_token == 'true' }} | |
| publish-bootstrap: | |
| name: Bootstrap registry identities | |
| needs: | |
| - prepare-candidate | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 360 | |
| if: ${{ needs.prepare-candidate.outputs.bootstrap_required == 'true' }} | |
| environment: release-bootstrap | |
| permissions: | |
| actions: read | |
| contents: write | |
| id-token: write | |
| pull-requests: read | |
| steps: | |
| - name: Record bounded bootstrap job deadline | |
| id: bootstrap_job_deadline | |
| run: | | |
| if [[ ! "$RELEASE_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'RELEASE_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2 | |
| exit 1 | |
| fi | |
| hard_deadline=$(( $(date +%s) + RELEASE_JOB_HARD_WINDOW_SECONDS )) | |
| { | |
| echo "REGISTRY_JOB_HARD_DEADLINE_EPOCH=$hard_deadline" | |
| echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots" | |
| } >> "$GITHUB_ENV" | |
| echo "The bootstrap job must stop registry work before Unix time $hard_deadline." | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Resolve exact release commit | |
| id: release_head | |
| timeout-minutes: 1 | |
| env: | |
| INPUT_RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| run: .github/scripts/resolve-release-head.sh | |
| - name: Set up Moon | |
| uses: ./.github/actions/setup-moon | |
| with: | |
| install-workspace: "false" | |
| - name: Require checked publishing code | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required | |
| - name: Plan bootstrap releases | |
| id: release_plan | |
| run: | | |
| bash tools/release/release-plan.sh \ | |
| --from-product-tags \ | |
| --include-current-tags \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --format github-output \ | |
| >> "$GITHUB_OUTPUT" | |
| - name: No package release planned | |
| if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }} | |
| run: echo "No release-affecting product changes were found since the last product tag." | |
| - name: Prove Release Please PR can complete after bootstrap | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \ | |
| --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" \ | |
| --github-output "$RUNNER_TEMP/bootstrap-release-identity" | |
| release_sha="$(sed -n 's/^release_sha=//p' "$RUNNER_TEMP/bootstrap-release-identity")" | |
| tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ | |
| assert-markable --release-sha "$release_sha" --base main | |
| - name: Resolve selected bootstrap authentication needs | |
| id: registry_needs | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bun .github/scripts/selected-registry-needs.mts | |
| - name: Resolve registry identity bootstrap scope | |
| id: bootstrap_scope | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| NEEDS_CARGO: ${{ steps.registry_needs.outputs.needs_cargo }} | |
| NEEDS_NPM: ${{ steps.registry_needs.outputs.needs_npm }} | |
| run: | | |
| required=false | |
| if [[ "$NEEDS_CARGO" == true || "$NEEDS_NPM" == true ]]; then | |
| required=true | |
| fi | |
| echo "required=$required" >> "$GITHUB_OUTPUT" | |
| - name: No registry identities require bootstrap | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.bootstrap_scope.outputs.required != 'true' }} | |
| run: echo 'The selected release has no Cargo or npm identities; bootstrap is a no-op.' | |
| - name: Set up pinned npm publisher | |
| id: setup_bootstrap_npm | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }} | |
| timeout-minutes: 3 | |
| uses: ./.github/actions/setup-npm-publisher | |
| with: | |
| npm-version: ${{ env.NPM_VERSION }} | |
| - name: Preflight selected product tag and release collisions | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bash tools/release/verify-product-tags.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --target "$RELEASE_HEAD_SHA" \ | |
| --allow-missing | |
| bun .github/scripts/manage-release-drafts.mts preflight \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Download the frozen candidate from preparation | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} | |
| path: ${{ runner.temp }}/approved-bootstrap | |
| merge-multiple: true | |
| - name: Verify and install approved publication candidate | |
| id: verify_bootstrap_candidate | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} | |
| run: | | |
| if [[ -e "$GITHUB_WORKSPACE/target" ]]; then | |
| echo 'publication candidate installation requires an absent workspace target directory' >&2 | |
| exit 1 | |
| fi | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \ | |
| --transport "$RUNNER_TEMP/approved-bootstrap/oliphaunt-publication-candidate.tar" \ | |
| --approved-lock "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --approval-run-id "$APPROVAL_RUN_ID" \ | |
| --workspace-root "$GITHUB_WORKSPACE" | |
| - name: Verify external lock equals installed candidate lock | |
| id: verify_bootstrap_lock | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| run: | | |
| if ! cmp -s "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" "$PUBLICATION_LOCK_PATH"; then | |
| echo 'installed candidate lock differs from the separately downloaded approved publication lock' >&2 | |
| exit 1 | |
| fi | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts verify \ | |
| --lock "$PUBLICATION_LOCK_PATH" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Restore prior bootstrap checkpoint chain | |
| id: restore_bootstrap_checkpoint | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: bun .github/scripts/download-bootstrap-ledger.mts | |
| - name: Classify exact bootstrap credential needs | |
| id: bootstrap_credential_needs | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| REGISTRY_MUTATION_DEADLINE_EPOCH: ${{ env.REGISTRY_JOB_HARD_DEADLINE_EPOCH }} | |
| run: bun .github/scripts/bootstrap-registry-identities.mts --credential-needs | |
| - name: Require bootstrap credentials before mutation | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| CRATES_IO_BOOTSTRAP_TOKEN: ${{ secrets.CRATES_IO_BOOTSTRAP_TOKEN }} | |
| NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }} | |
| NEEDS_CARGO_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token }} | |
| NEEDS_NPM_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_npm_token }} | |
| run: | | |
| if [[ "$NEEDS_CARGO_TOKEN" == true && -z "$CRATES_IO_BOOTSTRAP_TOKEN" ]]; then | |
| echo 'approved candidate contains absent Cargo names but CRATES_IO_BOOTSTRAP_TOKEN is unavailable' >&2 | |
| exit 1 | |
| fi | |
| if [[ "$NEEDS_NPM_TOKEN" == true && -z "$NPM_BOOTSTRAP_TOKEN" ]]; then | |
| echo 'approved candidate contains absent npm names but NPM_BOOTSTRAP_TOKEN is unavailable' >&2 | |
| exit 1 | |
| fi | |
| - name: Create bootstrap transport tag token | |
| id: bootstrap_tag_token | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| with: &release_tag_app | |
| client-id: ${{ secrets.RELEASE_TAG_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.RELEASE_TAG_APP_PRIVATE_KEY }} | |
| owner: f0rr0 | |
| repositories: oliphaunt | |
| permission-contents: write | |
| permission-workflows: write | |
| - name: Ensure exact immutable release transport ref | |
| id: ensure_bootstrap_transport_ref | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| timeout-minutes: 3 | |
| env: | |
| GH_TOKEN: ${{ steps.bootstrap_tag_token.outputs.token }} | |
| GH_REPO: ${{ github.repository }} | |
| RELEASE_OPERATION: publish-bootstrap | |
| RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: isolated-bootstrap | |
| run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA" | |
| - name: Start bounded bootstrap mutation window | |
| id: bootstrap_mutation_deadline | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| run: | | |
| if [[ ! "$BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS must be a positive integer' >&2 | |
| exit 1 | |
| fi | |
| if [[ ! "$REGISTRY_JOB_HARD_DEADLINE_EPOCH" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'REGISTRY_JOB_HARD_DEADLINE_EPOCH must be a positive Unix timestamp' >&2 | |
| exit 1 | |
| fi | |
| now=$(date +%s) | |
| window_deadline=$(( now + BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS )) | |
| deadline=$window_deadline | |
| if (( REGISTRY_JOB_HARD_DEADLINE_EPOCH < deadline )); then | |
| deadline=$REGISTRY_JOB_HARD_DEADLINE_EPOCH | |
| fi | |
| echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$deadline" >> "$GITHUB_ENV" | |
| echo "Bootstrap registry mutation must stop before Unix time $deadline." | |
| - name: Configure npm identity-bootstrap authentication | |
| id: configure_bootstrap_npm_auth | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.bootstrap_credential_needs.outputs.needs_npm_token == 'true' }} | |
| env: | |
| NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }} | |
| run: | | |
| umask 077 | |
| npmrc="$RUNNER_TEMP/oliphaunt-bootstrap.npmrc" | |
| printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_BOOTSTRAP_TOKEN" > "$npmrc" | |
| - name: Bootstrap missing Cargo and npm identities | |
| id: bootstrap_registry_identities | |
| if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token == 'true' && secrets.CRATES_IO_BOOTSTRAP_TOKEN || '' }} | |
| NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/oliphaunt-bootstrap.npmrc | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER || '30' }} | |
| REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER || '30' }} | |
| REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER || '6' }} | |
| REGISTRY_BOOTSTRAP_RESERVE_SECONDS: ${{ vars.REGISTRY_BOOTSTRAP_RESERVE_SECONDS || '600' }} | |
| run: bash .github/scripts/bootstrap-registry-identities.sh | |
| - name: Require a typed bootstrap execution decision | |
| id: require_bootstrap_execution_decision | |
| if: ${{ steps.bootstrap_registry_identities.outcome == 'success' }} | |
| timeout-minutes: 1 | |
| env: | |
| COMPLETE: ${{ steps.bootstrap_registry_identities.outputs.complete }} | |
| DEFERRED: ${{ steps.bootstrap_registry_identities.outputs.deferred }} | |
| DEFERRAL_MODE: ${{ steps.bootstrap_registry_identities.outputs.deferral_mode }} | |
| PROGRESS_COUNT: ${{ steps.bootstrap_registry_identities.outputs.progress_count }} | |
| REMAINING_COUNT: ${{ steps.bootstrap_registry_identities.outputs.remaining_count }} | |
| NOT_BEFORE_EPOCH: ${{ steps.bootstrap_registry_identities.outputs.not_before_epoch }} | |
| run: | | |
| if [[ "$COMPLETE" == true && "$DEFERRED" == false ]]; then | |
| if [[ -n "$DEFERRAL_MODE" || "$REMAINING_COUNT" != 0 ]]; then | |
| echo 'complete bootstrap result retains a deferral mode or remaining carriers' >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$COMPLETE" == false && "$DEFERRED" == true ]]; then | |
| if [[ ! "$REMAINING_COUNT" =~ ^[1-9][0-9]*$ || ! "$NOT_BEFORE_EPOCH" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'deferred bootstrap result requires remaining work and a positive not-before time' >&2 | |
| exit 1 | |
| fi | |
| if [[ "$DEFERRAL_MODE" == progress ]]; then | |
| if [[ ! "$PROGRESS_COUNT" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'bootstrap progress deferral requires nonzero durable progress' >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$DEFERRAL_MODE" == rate-limit ]]; then | |
| if [[ "$PROGRESS_COUNT" != 0 ]]; then | |
| echo 'bootstrap rate-limit deferral cannot claim durable progress' >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$DEFERRAL_MODE" == pre-mutation-capacity ]]; then | |
| if [[ "$PROGRESS_COUNT" != 0 ]]; then | |
| echo 'bootstrap pre-mutation capacity deferral cannot claim durable progress' >&2 | |
| exit 1 | |
| fi | |
| elif [[ "$DEFERRAL_MODE" == pre-mutation-deadline ]]; then | |
| if [[ "$PROGRESS_COUNT" != 0 ]]; then | |
| echo 'bootstrap pre-mutation deadline deferral cannot claim durable progress' >&2 | |
| exit 1 | |
| fi | |
| else | |
| echo 'deferred bootstrap result has an unsupported deferral mode' >&2 | |
| exit 1 | |
| fi | |
| else | |
| echo 'bootstrap publisher must emit exactly one of complete or deferred' >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "complete=$COMPLETE" | |
| echo "deferred=$DEFERRED" | |
| echo "deferral_mode=$DEFERRAL_MODE" | |
| echo "progress_count=$PROGRESS_COUNT" | |
| echo "remaining_count=$REMAINING_COUNT" | |
| echo "not_before_epoch=$NOT_BEFORE_EPOCH" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Record bootstrap identity result | |
| if: ${{ steps.require_bootstrap_execution_decision.outputs.complete == 'true' }} | |
| run: | | |
| lock_sha256="$(sha256sum "$PUBLICATION_LOCK_PATH" | awk '{print $1}')" | |
| { | |
| echo '## Registry identity bootstrap complete' | |
| echo | |
| echo "- Release commit: \`$RELEASE_HEAD_SHA\`" | |
| echo "- Publication lock SHA-256: \`$lock_sha256\`" | |
| echo '- Scope: selected Cargo and npm identities only' | |
| echo '- Publication continues automatically after this job. Configure trusted publishers before the next version, then revoke bootstrap tokens.' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Remove bootstrap npm credentials | |
| id: remove_bootstrap_credentials | |
| if: ${{ always() }} | |
| run: rm -f "$RUNNER_TEMP/oliphaunt-bootstrap.npmrc" | |
| - name: Upload bootstrap identity ledger | |
| id: preserve_bootstrap_ledger | |
| if: ${{ always() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: oliphaunt-bootstrap-ledger | |
| path: target/release/bootstrap-ledger | |
| if-no-files-found: warn | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Stop incomplete bootstrap for manual rerun | |
| if: ${{ steps.require_bootstrap_execution_decision.outputs.deferred == 'true' }} | |
| env: | |
| DEFERRAL_MODE: ${{ steps.require_bootstrap_execution_decision.outputs.deferral_mode }} | |
| NOT_BEFORE_EPOCH: ${{ steps.require_bootstrap_execution_decision.outputs.not_before_epoch }} | |
| REMAINING_COUNT: ${{ steps.require_bootstrap_execution_decision.outputs.remaining_count }} | |
| APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} | |
| run: | | |
| { | |
| echo '## Registry identity bootstrap incomplete—rerun required' | |
| echo | |
| echo "- Release commit: \`$RELEASE_HEAD_SHA\`" | |
| echo "- Approved dry-run: \`$APPROVAL_RUN_ID\`" | |
| echo "- Remaining identities: \`$REMAINING_COUNT\`" | |
| echo "- Reason: \`$DEFERRAL_MODE\`" | |
| echo "- Do not rerun before Unix time: \`$NOT_BEFORE_EPOCH\`" | |
| echo "- Resume this exact SHA and approval: \`gh run rerun $GITHUB_RUN_ID --failed\`" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "Bootstrap is incomplete; rerun failed jobs for workflow run $GITHUB_RUN_ID after $NOT_BEFORE_EPOCH." >&2 | |
| exit 1 | |
| outputs: | |
| ledger_artifact_id: ${{ steps.preserve_bootstrap_ledger.outputs.artifact-id }} | |
| publish: | |
| name: Publish release | |
| needs: | |
| - prepare-candidate | |
| - publish-bootstrap | |
| runs-on: macos-26 | |
| timeout-minutes: 360 | |
| if: ${{ always() && !cancelled() && inputs.operation == 'publish' && needs.prepare-candidate.result == 'success' && needs.prepare-candidate.outputs.has_release_changes == 'true' && (needs.publish-bootstrap.result == 'success' || needs.publish-bootstrap.result == 'skipped') }} | |
| environment: release-publish | |
| outputs: | |
| promoted: ${{ steps.promote_github_releases.outcome == 'success' }} | |
| permissions: | |
| actions: read | |
| artifact-metadata: write | |
| attestations: write | |
| contents: write | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| - name: Record release deadline | |
| id: release_job_deadline | |
| run: | | |
| if [[ ! "$RELEASE_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then | |
| echo 'RELEASE_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2 | |
| exit 1 | |
| fi | |
| hard_deadline=$(( $(date +%s) + RELEASE_JOB_HARD_WINDOW_SECONDS )) | |
| { | |
| echo "RELEASE_JOB_HARD_DEADLINE_EPOCH=$hard_deadline" | |
| echo "OLIPHAUNT_GITHUB_CONTENT_WRITE_PACER_PATH=$RUNNER_TEMP/oliphaunt-github-content-write-pacer.json" | |
| echo "OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH=$RUNNER_TEMP/oliphaunt-github-core-request-journal.json" | |
| echo "OLIPHAUNT_REQUIRE_GITHUB_CORE_REQUEST_JOURNAL=true" | |
| echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots" | |
| } >> "$GITHUB_ENV" | |
| echo "The release must finish before Unix time $hard_deadline." | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Resolve release commit | |
| id: release_head | |
| timeout-minutes: 1 | |
| env: | |
| INPUT_RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| run: .github/scripts/resolve-release-head.sh | |
| - name: Set up Moon | |
| uses: ./.github/actions/setup-moon | |
| with: | |
| install-workspace: "true" | |
| - name: Require checked publishing code | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required | |
| - name: Set up Rust | |
| uses: ./.github/actions/setup-rust | |
| - name: Plan product releases | |
| id: release_plan | |
| run: | | |
| release_plan_args=( | |
| --from-product-tags | |
| --include-current-tags | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| --format github-output | |
| ) | |
| bash tools/release/release-plan.sh "${release_plan_args[@]}" >> "$GITHUB_OUTPUT" | |
| - name: No package release planned | |
| if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }} | |
| run: echo "No release-affecting product changes were found since the last product tag." | |
| - name: Resolve selected registry authentication needs | |
| id: registry_needs | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bun .github/scripts/selected-registry-needs.mts | |
| - name: Verify direct-workflow OIDC identity | |
| id: verify_oidc_identity | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| RELEASE_OPERATION: publish | |
| run: bun .github/scripts/verify-github-oidc-identity.mts | |
| - name: Prove pending release identity | |
| id: verify_publication_candidate | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 2 | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Prove Release Please PR can complete after publication | |
| id: assert_release_please_markable | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 1 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ | |
| assert-markable \ | |
| --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ | |
| --base main | |
| - name: Preflight selected product tag and release collisions | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| gh auth setup-git | |
| bash tools/release/verify-product-tags.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --target "$RELEASE_HEAD_SHA" \ | |
| --allow-missing | |
| bun .github/scripts/manage-release-drafts.mts preflight \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Check release tag App permissions | |
| id: check_release_tag_app | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| with: *release_tag_app | |
| - name: Check publish environment | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} | |
| ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | |
| run: tools/release/check_publish_environment.mts --products-json "${PRODUCTS_JSON}" | |
| - name: Verify external registry ownership and trust links | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} | |
| ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} | |
| run: bun .github/scripts/verify-external-publish-readiness.mts | |
| - name: Import, sign, and verify Maven credentials before mutation | |
| id: verify_maven_signing | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_maven == 'true' }} | |
| timeout-minutes: 2 | |
| env: | |
| ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | |
| run: bash tools/release/verify-maven-signing-readiness.sh | |
| - name: Download the frozen candidate from preparation | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} | |
| path: ${{ runner.temp }}/approved-publication | |
| merge-multiple: true | |
| - name: Verify and install the complete approved candidate | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} | |
| run: | | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \ | |
| --transport "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" \ | |
| --approved-lock "$RUNNER_TEMP/approved-publication/publication-lock.json" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --approval-run-id "$APPROVAL_RUN_ID" \ | |
| --workspace-root "$GITHUB_WORKSPACE" | |
| - name: Validate product versions and registry state | |
| id: validate_release_registry_state | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bash tools/release/release-check-registries.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Set up pinned npm publisher | |
| id: setup_github_stage_npm | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }} | |
| timeout-minutes: 3 | |
| uses: ./.github/actions/setup-npm-publisher | |
| with: | |
| npm-version: ${{ env.NPM_VERSION }} | |
| - name: Assemble and sign the exact Maven Central bundle before release mutation | |
| id: preflight_maven_bundle | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_maven == 'true' }} | |
| timeout-minutes: 15 | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | |
| run: | | |
| bash tools/release/preflight-maven-central-bundle.sh \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --release-commit "$RELEASE_HEAD_SHA" | |
| - name: Prove the exact SwiftPM source tag is remotely collision-free | |
| id: preflight_swift_source_tag | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} | |
| timeout-minutes: 2 | |
| run: | | |
| bash tools/release/publish-swiftpm-source-tag.sh --preflight \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --release-commit "$RELEASE_HEAD_SHA" | |
| - name: Classify pre-tag registry publication state | |
| id: bootstrap_ledger_state | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && (steps.registry_needs.outputs.needs_cargo == 'true' || steps.registry_needs.outputs.needs_npm == 'true') }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| RELEASE_HEAD_SHA: ${{ steps.release_head.outputs.sha }} | |
| run: bash .github/scripts/registry-bootstrap-ledger-state.sh | |
| - name: Download the bootstrap ledger from this run | |
| if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' && needs.publish-bootstrap.outputs.ledger_artifact_id != '' }} | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| artifact-ids: ${{ needs.publish-bootstrap.outputs.ledger_artifact_id }} | |
| path: ${{ env.BOOTSTRAP_LEDGER_PATH }} | |
| - name: Restore completed bootstrap evidence for this candidate | |
| if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' && needs.publish-bootstrap.outputs.ledger_artifact_id == '' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: bash .github/scripts/download-completed-bootstrap.sh | |
| - name: Verify immutable bootstrap ledger and registry existence | |
| if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' }} | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| tools/dev/bun.sh tools/release/bootstrap-ledger.mts verify \ | |
| --lock "$PUBLICATION_LOCK_PATH" \ | |
| --ledger "$BOOTSTRAP_LEDGER_PATH" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --require-complete \ | |
| --verify-registries | |
| - name: Upload publication lock audit evidence | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: oliphaunt-publication-lock-${{ inputs.operation }} | |
| path: target/release/publication-lock.json | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Create release tag token | |
| id: release_tag_token | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| with: *release_tag_app | |
| - name: Reserve release transport content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 3 | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve \ | |
| --label "release transport tag" | |
| - name: Ensure exact immutable release transport ref | |
| id: ensure_release_transport_ref | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 3 | |
| env: | |
| GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} | |
| GH_REPO: ${{ github.repository }} | |
| RELEASE_OPERATION: publish | |
| RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: pre-reserved | |
| run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA" | |
| - name: Stage exact-SHA product tags and draft releases | |
| id: stage_github_releases | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 31 | |
| env: | |
| GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| bun .github/scripts/manage-release-drafts.mts stage \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --state staged | |
| - name: Verify exact product tags | |
| id: verify_product_tags | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 5 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bash tools/release/verify-product-tags.sh --products-json "${PRODUCTS_JSON}" --target "$RELEASE_HEAD_SHA" | |
| - name: Verify exact-SHA GitHub release staging | |
| id: verify_github_staging | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 5 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bun .github/scripts/manage-release-drafts.mts verify --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" --state staged | |
| - name: Publish all selected GitHub release asset sets concurrently | |
| id: publish_github_assets | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 95 | |
| env: | |
| GITHUB_RELEASE_ASSET_UPLOAD_REPORT_PATH: ${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/release-publish.mts publish --step github-release-assets --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" | |
| - name: Resolve exact selected extension attestation subjects | |
| id: extension_attestation_subjects | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' }} | |
| env: | |
| EXTENSION_PRODUCTS_JSON: ${{ steps.release_plan.outputs.extension_products_json }} | |
| run: | | |
| tools/dev/bun.sh tools/release/locked-attestation-subjects.mts \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --products-json "$EXTENSION_PRODUCTS_JSON" \ | |
| --github-output "$GITHUB_OUTPUT" | |
| - name: Reserve extension provenance write (batch 1) | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 1" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 1 API attempt" | |
| - name: Attest extension release assets (batch 1) | |
| id: attest_extensions_1 | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_1 }} | |
| - name: Reserve extension provenance write (batch 2) | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 2" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 2 API attempt" | |
| - name: Attest extension release assets (batch 2) | |
| id: attest_extensions_2 | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_2 }} | |
| - name: Reserve liboliphaunt attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "liboliphaunt native attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "liboliphaunt native attestation API attempt" | |
| - name: Attest liboliphaunt release assets | |
| id: attest_liboliphaunt_native | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/liboliphaunt/release-assets/*.tar.gz | |
| target/liboliphaunt/release-assets/*.tar.zst | |
| target/liboliphaunt/release-assets/*.zip | |
| target/liboliphaunt/release-assets/*.tsv | |
| target/liboliphaunt/release-assets/*.sha256 | |
| target/extension-artifacts/liboliphaunt-native/oliphaunt-extension-contrib-pg18/release-assets/* | |
| - name: Create fresh SwiftPM tag token | |
| id: swift_tag_token | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| with: *release_tag_app | |
| - name: Publish Swift SDK GitHub release and SwiftPM tags | |
| id: publish_swift_source_tag | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} | |
| timeout-minutes: 6 | |
| env: | |
| GH_TOKEN: ${{ steps.swift_tag_token.outputs.token }} | |
| run: bash tools/release/publish-swiftpm-source-tag.sh --push --target "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" | |
| - name: Reserve broker attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "broker attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "broker attestation API attempt" | |
| - name: Attest broker release assets | |
| id: attest_broker | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/oliphaunt-broker/release-assets/*.tar.gz | |
| target/oliphaunt-broker/release-assets/*.zip | |
| target/oliphaunt-broker/release-assets/*.sha256 | |
| - name: Reserve Node direct attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "Node direct attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "Node direct attestation API attempt" | |
| - name: Attest Node direct release assets | |
| id: attest_node_direct | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/oliphaunt-node-direct/release-assets/*.tar.gz | |
| target/oliphaunt-node-direct/release-assets/*.zip | |
| target/oliphaunt-node-direct/release-assets/*.sha256 | |
| - name: Reserve WASIX Node-API attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX Node-API attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX Node-API attestation API attempt" | |
| - name: Attest WASIX Node-API release assets | |
| id: attest_wasix_napi | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/oliphaunt-wasix-napi/release-assets/*.tar.gz | |
| target/oliphaunt-wasix-napi/release-assets/*.zip | |
| target/oliphaunt-wasix-napi/release-assets/*.sha256 | |
| - name: Reserve WASIX attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX attestation API attempt" | |
| - name: Attest WASIX release assets | |
| id: attest_wasix | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/oliphaunt-wasix/release-assets/*.tar.zst | |
| target/oliphaunt-wasix/release-assets/*.sha256 | |
| target/extension-artifacts/liboliphaunt-wasix/oliphaunt-extension-contrib-pg18/release-assets/* | |
| - name: Reserve WASIX postmaster attestation content write | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }} | |
| run: | | |
| tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX postmaster attestation" | |
| tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX postmaster attestation API attempt" | |
| - name: Attest WASIX postmaster release assets | |
| id: attest_wasix_postmaster | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }} | |
| timeout-minutes: 5 | |
| uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 | |
| with: | |
| subject-path: | | |
| target/oliphaunt-wasix-postmaster/release-assets/*.tar.zst | |
| target/oliphaunt-wasix-postmaster/release-assets/*.sha256 | |
| - name: Freeze exact GitHub release asset and attestation evidence | |
| id: freeze_github_evidence | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 10 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| EXTENSIONS_ATTESTATION_BUNDLE_1: ${{ steps.attest_extensions_1.outputs.bundle-path }} | |
| EXTENSIONS_ATTESTATION_BUNDLE_2: ${{ steps.attest_extensions_2.outputs.bundle-path }} | |
| LIBOLIPHAUNT_NATIVE_ATTESTATION_BUNDLE: ${{ steps.attest_liboliphaunt_native.outputs.bundle-path }} | |
| BROKER_ATTESTATION_BUNDLE: ${{ steps.attest_broker.outputs.bundle-path }} | |
| NODE_DIRECT_ATTESTATION_BUNDLE: ${{ steps.attest_node_direct.outputs.bundle-path }} | |
| WASIX_NAPI_ATTESTATION_BUNDLE: ${{ steps.attest_wasix_napi.outputs.bundle-path }} | |
| WASIX_ATTESTATION_BUNDLE: ${{ steps.attest_wasix.outputs.bundle-path }} | |
| WASIX_POSTMASTER_ATTESTATION_BUNDLE: ${{ steps.attest_wasix_postmaster.outputs.bundle-path }} | |
| run: | | |
| bundle_args=() | |
| for bundle in \ | |
| "$EXTENSIONS_ATTESTATION_BUNDLE_1" \ | |
| "$EXTENSIONS_ATTESTATION_BUNDLE_2" \ | |
| "$LIBOLIPHAUNT_NATIVE_ATTESTATION_BUNDLE" \ | |
| "$BROKER_ATTESTATION_BUNDLE" \ | |
| "$NODE_DIRECT_ATTESTATION_BUNDLE" \ | |
| "$WASIX_NAPI_ATTESTATION_BUNDLE" \ | |
| "$WASIX_ATTESTATION_BUNDLE" \ | |
| "$WASIX_POSTMASTER_ATTESTATION_BUNDLE" | |
| do | |
| if [[ -n "$bundle" ]]; then | |
| bundle_args+=(--attestation-bundle "$bundle") | |
| fi | |
| done | |
| bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/release/verify-github-release-attestations.sh pre-mutation \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --output target/release/github-release-attestation-receipt.json \ | |
| "${bundle_args[@]}" | |
| - name: Open registry publication window | |
| id: registry_publication_window | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| run: | | |
| for name in RELEASE_JOB_HARD_DEADLINE_EPOCH POST_REGISTRY_RESERVE_SECONDS; do | |
| if [[ ! "${!name:-}" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "$name must be a positive integer" >&2 | |
| exit 1 | |
| fi | |
| done | |
| mutation_deadline=$(( RELEASE_JOB_HARD_DEADLINE_EPOCH - POST_REGISTRY_RESERVE_SECONDS )) | |
| if (( $(date +%s) >= mutation_deadline )); then | |
| echo 'Not enough time remains for registry publication and final verification; rerun this idempotent release.' >&2 | |
| exit 1 | |
| fi | |
| echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$mutation_deadline" >> "$GITHUB_ENV" | |
| - name: Publish and reconcile every exact-lock registry carrier | |
| id: publish_registries | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 240 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} | |
| ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} | |
| run: | | |
| bash tools/release/publish-registries.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" | |
| - name: Verify published release | |
| id: verify_published_release | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 8 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| gh auth setup-git | |
| git fetch --force --tags origin | |
| bash tools/release/release-verify.sh \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --registry-receipts target/release/registry-integrity-receipts.json \ | |
| --github-release-receipt target/release/github-release-attestation-receipt.json | |
| - name: Resolve and install exact public consumer surfaces | |
| id: public_consumer_smoke | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 15 | |
| env: | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| command -v gtimeout >/dev/null || brew install coreutils | |
| bash tools/release/public-consumer-smoke.sh \ | |
| --publication-lock "$PUBLICATION_LOCK_PATH" \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --registry-receipts target/release/registry-integrity-receipts.json \ | |
| --github-release-receipt target/release/github-release-attestation-receipt.json \ | |
| --output target/release/public-consumer-smoke.json | |
| - name: Preserve public consumer evidence | |
| id: preserve_consumer_evidence | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 2 | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: public-consumer-evidence-${{ github.sha }} | |
| path: target/release/public-consumer-smoke.json | |
| if-no-files-found: error | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Reverify exact publication lock before promotion | |
| id: reverify_publication_lock | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 2 | |
| run: | | |
| bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \ | |
| verify \ | |
| --lock "$PUBLICATION_LOCK_PATH" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| - name: Preserve release evidence | |
| id: preserve_release_evidence | |
| if: ${{ always() && steps.release_plan.outputs.has_release_changes == 'true' }} | |
| continue-on-error: true | |
| timeout-minutes: 3 | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: release-evidence-${{ github.sha }} | |
| path: | | |
| target/release/publication-lock.json | |
| target/release/registry-integrity-receipts.json | |
| target/release/github-release-attestation-receipt.json | |
| target/release/public-consumer-smoke.json | |
| ${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json | |
| if-no-files-found: warn | |
| include-hidden-files: true | |
| overwrite: true | |
| retention-days: 90 | |
| - name: Promote verified GitHub release drafts | |
| id: promote_github_releases | |
| if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} | |
| timeout-minutes: 16 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} | |
| run: | | |
| tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ | |
| assert-markable \ | |
| --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ | |
| --base main | |
| bun .github/scripts/manage-release-drafts.mts promote \ | |
| --products-json "$PRODUCTS_JSON" \ | |
| --head-ref "$RELEASE_HEAD_SHA" | |
| tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ | |
| mark-tagged \ | |
| --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ | |
| --base main | |
| request-docs-refresh: | |
| name: Refresh published docs | |
| needs: publish | |
| if: ${{ needs.publish.outputs.promoted == 'true' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| environment: Production | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Require main | |
| run: test "$GITHUB_REF" = refs/heads/main | |
| - name: Checkout docs refresh command | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| ref: ${{ github.sha }} | |
| persist-credentials: false | |
| - name: Set up Bun | |
| uses: ./.github/actions/setup-bun | |
| - name: Record completed public releases | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| mkdir -p target/docs | |
| bun src/docs/tools/verify-live.mts snapshot target/docs/expected-products.json | |
| - name: Request main-branch docs rebuild | |
| id: request_docs | |
| env: | |
| VERCEL_DOCS_DEPLOY_HOOK: ${{ secrets.VERCEL_DOCS_DEPLOY_HOOK }} | |
| run: bash src/docs/tools/request-refresh.sh target/docs/deploy-hook.json | |
| - name: Verify live published versions | |
| id: verify_docs | |
| run: bun src/docs/tools/verify-live.mts verify target/docs/expected-products.json | |
| - name: Preserve docs refresh evidence | |
| if: ${{ always() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: docs-refresh-request-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: | | |
| target/docs/deploy-hook.json | |
| target/docs/expected-products.json | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| - name: Report docs refresh separately from publication | |
| if: ${{ always() }} | |
| env: | |
| REQUEST_OUTCOME: ${{ steps.request_docs.outcome }} | |
| LIVE_OUTCOME: ${{ steps.verify_docs.outcome }} | |
| run: | | |
| { | |
| echo '### Documentation refresh' | |
| echo 'Product publication completed successfully before this job.' | |
| echo "Refresh request: $REQUEST_OUTCOME." | |
| echo "Live published-version check: $LIVE_OUTCOME." | |
| echo 'The live check requires the version page to link the expected completed public releases or newer stable releases; hook acceptance alone is insufficient.' | |
| echo 'Retry this docs job only after checking the Vercel deployment; do not republish products to refresh documentation.' | |
| } >> "$GITHUB_STEP_SUMMARY" |