release #116
Workflow file for this run
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 | |
| on: | |
| push: | |
| tags: ["v*.*.*"] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: Semver (with or without leading v) | |
| required: true | |
| permissions: | |
| contents: read | |
| concurrency: | |
| # A tag push and a manual dispatch can target the same immutable release tag | |
| # from different refs. Serialize releases so those paths cannot publish twice. | |
| group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && (startsWith(inputs.version, 'v') && inputs.version || format('v{0}', inputs.version)) || github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| runs-on: ${{ vars.PUBLIC_RELEASE_RUNNER || 'ubuntu-latest' }} | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| outputs: | |
| package_name: ${{ steps.release.outputs.package_name }} | |
| release_sha: ${{ steps.release.outputs.release_sha }} | |
| release_tag: ${{ steps.release.outputs.release_tag }} | |
| release_version: ${{ steps.release.outputs.release_version }} | |
| release_channel: ${{ steps.release.outputs.release_channel }} | |
| npm_tag: ${{ steps.release.outputs.npm_tag }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| fetch-depth: 1 | |
| - id: release | |
| name: Resolve immutable release tag | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REQUESTED: ${{ github.event.inputs.version || github.ref_name }} | |
| TRIGGER_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| release_version="${REQUESTED#v}" | |
| if [[ ! "$release_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then | |
| echo "::error::Requested release version '$REQUESTED' is not semver." | |
| exit 1 | |
| fi | |
| release_tag="v${release_version}" | |
| release_channel="$(node scripts/resolve-release-channel.mjs --version "$release_version")" | |
| npm_tag="${release_channel}" | |
| if [[ "$release_channel" == "stable" ]]; then | |
| npm_tag=latest | |
| fi | |
| release_sha="$TRIGGER_SHA" | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| for attempt in 1 2 3; do | |
| if timeout 60s git \ | |
| -c http.lowSpeedLimit=1000 \ | |
| -c http.lowSpeedTime=30 \ | |
| fetch --force --no-tags origin "refs/tags/${release_tag}:refs/tags/${release_tag}"; then | |
| break | |
| fi | |
| if [[ "$attempt" -eq 3 ]]; then | |
| echo "::error::Unable to fetch ${release_tag} after ${attempt} bounded attempts." | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| release_sha="$(git rev-list -n 1 "$release_tag")" | |
| elif [[ "$EVENT_NAME" != "push" ]]; then | |
| echo "::error::Unsupported release event ${EVENT_NAME}." | |
| exit 1 | |
| fi | |
| if [[ ! "$release_sha" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "::error::Release source ${release_sha} is not an immutable commit SHA." | |
| exit 1 | |
| fi | |
| git checkout --detach "$release_sha" | |
| package_version="$(node -p "require('./package.json').version")" | |
| package_name="$(node -p "require('./package.json').name")" | |
| if [[ "$package_version" != "$release_version" ]]; then | |
| echo "::error::${release_tag} contains package version ${package_version}, not ${release_version}." | |
| exit 1 | |
| fi | |
| { | |
| echo "package_name=$package_name" | |
| echo "release_sha=$release_sha" | |
| echo "release_tag=$release_tag" | |
| echo "release_version=$release_version" | |
| echo "release_channel=$release_channel" | |
| echo "npm_tag=$npm_tag" | |
| } >> "$GITHUB_OUTPUT" | |
| binaries: | |
| needs: prepare | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { platform: linux-x64, target: x86_64-unknown-linux-gnu, os: ubuntu-latest } | |
| - { platform: linux-arm64, target: aarch64-unknown-linux-gnu, os: ubuntu-24.04-arm } | |
| - { platform: darwin-x64, target: x86_64-apple-darwin, os: macos-15 } | |
| - { platform: darwin-arm64, target: aarch64-apple-darwin, os: macos-15 } | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_sha }} | |
| - uses: ./.github/actions/setup-rust | |
| with: | |
| toolchain: stable | |
| targets: ${{ matrix.target }} | |
| workspaces: . -> target | |
| - name: Build canonical native binary | |
| env: | |
| RELEASE_PLATFORM: ${{ matrix.platform }} | |
| run: node scripts/build-release-binary.mjs --platform "$RELEASE_PLATFORM" | |
| - name: Smoke host binary | |
| env: | |
| RELEASE_PLATFORM: ${{ matrix.platform }} | |
| run: node scripts/smoke-release-native-only.mjs "dist/release/maestro-${RELEASE_PLATFORM}" | |
| - name: Record smoke marker | |
| env: | |
| RELEASE_PLATFORM: ${{ matrix.platform }} | |
| run: | | |
| binary="dist/release/maestro-${RELEASE_PLATFORM}" | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| digest="$(sha256sum "$binary" | awk '{print $1}')" | |
| else | |
| digest="$(shasum -a 256 "$binary" | awk '{print $1}')" | |
| fi | |
| printf '%s maestro-%s\n' "$digest" "$RELEASE_PLATFORM" > "dist/release/smoked-${RELEASE_PLATFORM}.txt" | |
| - name: Verify macOS release signature | |
| if: ${{ startsWith(matrix.platform, 'darwin-') }} | |
| env: | |
| RELEASE_PLATFORM: ${{ matrix.platform }} | |
| run: | | |
| node scripts/check-macos-release-signature.mjs \ | |
| "dist/release/maestro-${RELEASE_PLATFORM}" \ | |
| --marker "dist/release/signed-${RELEASE_PLATFORM}.json" | |
| - name: Record release toolchain | |
| env: | |
| RELEASE_PLATFORM: ${{ matrix.platform }} | |
| run: rustc -Vv > "dist/release/rustc-${RELEASE_PLATFORM}.txt" | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0 | |
| with: | |
| name: maestro-${{ matrix.platform }} | |
| path: | | |
| dist/release/maestro-${{ matrix.platform }} | |
| dist/release/smoked-${{ matrix.platform }}.txt | |
| dist/release/signed-${{ matrix.platform }}.json | |
| dist/release/rustc-${{ matrix.platform }}.txt | |
| publish: | |
| needs: | |
| - prepare | |
| - binaries | |
| # Public free hosted minutes by default. Do not point this at private | |
| # self-hosted labels unless the runner group allows public repos. | |
| runs-on: ${{ vars.PUBLIC_RELEASE_RUNNER || 'ubuntu-latest' }} | |
| timeout-minutes: 60 | |
| environment: npm-release | |
| permissions: | |
| contents: read | |
| # Required for npm trusted publishing (OIDC). Last successful | |
| # @evalops/maestro publish used GitHub OIDC, not classic NPM_TOKEN. | |
| id-token: write | |
| outputs: | |
| package_name: ${{ needs.prepare.outputs.package_name }} | |
| release_tag: ${{ needs.prepare.outputs.release_tag }} | |
| release_version: ${{ needs.prepare.outputs.release_version }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_sha }} | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| registry-url: https://registry.npmjs.org | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.0 | |
| with: | |
| pattern: maestro-* | |
| path: release-binaries | |
| merge-multiple: true | |
| - name: Verify native package inputs | |
| env: | |
| RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} | |
| run: | | |
| set -euo pipefail | |
| test "$(node -p "require('./package.json').version")" = "$RELEASE_VERSION" | |
| test -f packages/web/dist/index.html | |
| npm run check:rust-only-runtime | |
| - name: Materialize native npm package | |
| run: node scripts/materialize-native-package.mjs --input-dir release-binaries | |
| - name: Package browser assets for native installs | |
| run: tar -czf maestro-web-dist.tar.gz -C packages/web/dist . | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| retention-days: 14 | |
| name: release-web-dist-${{ needs.prepare.outputs.release_tag }} | |
| overwrite: true | |
| path: maestro-web-dist.tar.gz | |
| - id: pack | |
| run: | | |
| PACK_JSON=$(npm pack --json) | |
| echo "$PACK_JSON" | |
| node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); p.name="@evalops/maestro"; fs.writeFileSync("package.json", JSON.stringify(p, null, "\t")+"\n")' | |
| ALIAS_PACK_JSON=$(npm pack --json) | |
| echo "$ALIAS_PACK_JSON" | |
| git checkout -- package.json | |
| { | |
| echo "tarball=$(echo "$PACK_JSON" | jq -r '.[0].filename')" | |
| echo "integrity=$(echo "$PACK_JSON" | jq -r '.[0].integrity')" | |
| echo "alias_tarball=$(echo "$ALIAS_PACK_JSON" | jq -r '.[0].filename')" | |
| echo "alias_integrity=$(echo "$ALIAS_PACK_JSON" | jq -r '.[0].integrity')" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Smoke packed package without JS runtime | |
| env: | |
| MAESTRO_REQUIRE_PACKAGED_TUI: "1" | |
| NPM_CONFIG_FETCH_RETRIES: "1" | |
| NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "2000" | |
| NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000" | |
| NPM_CONFIG_FETCH_TIMEOUT: "30000" | |
| NPM_CONFIG_REGISTRY: https://registry.npmjs.org | |
| PACKED_TARBALL: ${{ steps.pack.outputs.tarball }} | |
| run: node scripts/smoke-packed-cli.js "$PACKED_TARBALL" | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| retention-days: 14 | |
| name: npm-tarball-${{ needs.prepare.outputs.release_tag }} | |
| overwrite: true | |
| path: | | |
| ${{ steps.pack.outputs.tarball }} | |
| ${{ steps.pack.outputs.alias_tarball }} | |
| - name: Publish to npm | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| PACKAGE_NAME: ${{ needs.prepare.outputs.package_name }} | |
| ALIAS_PACKAGE_NAME: "@evalops/maestro" | |
| PACKED_INTEGRITY: ${{ steps.pack.outputs.integrity }} | |
| ALIAS_PACKED_INTEGRITY: ${{ steps.pack.outputs.alias_integrity }} | |
| RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} | |
| NPM_TAG: ${{ needs.prepare.outputs.npm_tag }} | |
| TARBALL: ${{ steps.pack.outputs.tarball }} | |
| ALIAS_TARBALL: ${{ steps.pack.outputs.alias_tarball }} | |
| NPM_CONFIG_FETCH_RETRIES: "1" | |
| NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "2000" | |
| NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000" | |
| NPM_CONFIG_FETCH_TIMEOUT: "30000" | |
| NPM_CONFIG_REGISTRY: https://registry.npmjs.org | |
| run: | | |
| set -euo pipefail | |
| # Trusted publishing (OIDC) is the primary path for @evalops/maestro. | |
| # Classic NPM_TOKEN is fallback only; a dead token returns E404 on PUT. | |
| publish_with_oidc() { | |
| local tarball="$1" | |
| # npm ≥ 11.5.1 required for OIDC; pin via npx for stable behavior. | |
| npx --yes npm@11.10.0 publish "$tarball" --access public --tag "$NPM_TAG" --registry "$NPM_CONFIG_REGISTRY" | |
| } | |
| publish_with_token() { | |
| local tarball="$1" | |
| if [[ -z "${NODE_AUTH_TOKEN:-}" ]]; then | |
| echo "::error::OIDC publish failed and secrets.NPM_TOKEN is empty. Configure npm trusted publishing for evalops/maestro release.yml (npm-release env) and ensure this job has id-token: write, or set a granular NPM_TOKEN with write on @evalops/maestro." | |
| return 1 | |
| fi | |
| printf "//registry.npmjs.org/:_authToken=%s\n" "$NODE_AUTH_TOKEN" > "$RUNNER_TEMP/npmrc" | |
| NPM_CONFIG_USERCONFIG="$RUNNER_TEMP/npmrc" \ | |
| NODE_AUTH_TOKEN="$NODE_AUTH_TOKEN" \ | |
| npx --yes npm@11.10.0 publish "$tarball" --access public --tag "$NPM_TAG" --registry "$NPM_CONFIG_REGISTRY" | |
| } | |
| verify_published_tarball() { | |
| local package_name="$1" | |
| local packed_integrity="$2" | |
| local registry_integrity | |
| registry_integrity="$( | |
| command npm view "${package_name}@${RELEASE_VERSION}" --registry "$NPM_CONFIG_REGISTRY" dist.integrity 2>/dev/null | |
| )" || return 1 | |
| if [[ -z "$registry_integrity" ]]; then | |
| return 1 | |
| fi | |
| if [[ "$registry_integrity" != "$packed_integrity" ]]; then | |
| echo "::error::npm already contains ${package_name}@${RELEASE_VERSION} with integrity ${registry_integrity}, not ${packed_integrity}." | |
| return 2 | |
| fi | |
| echo "Verified ${package_name}@${RELEASE_VERSION} already contains the packed tarball." | |
| } | |
| publish_or_verify() { | |
| local publisher="$1" | |
| local package_name="$2" | |
| local tarball="$3" | |
| local packed_integrity="$4" | |
| local publish_status=0 | |
| local registry_status=0 | |
| "$publisher" "$tarball" || publish_status=$? | |
| if [[ "$publish_status" -eq 0 ]]; then | |
| return 0 | |
| fi | |
| verify_published_tarball "$package_name" "$packed_integrity" || registry_status=$? | |
| if [[ "$registry_status" -eq 0 ]]; then | |
| return 0 | |
| fi | |
| if [[ "$registry_status" -eq 2 ]]; then | |
| return 2 | |
| fi | |
| return "$publish_status" | |
| } | |
| publish_package() { | |
| local package_name="$1" | |
| local tarball="$2" | |
| local packed_integrity="$3" | |
| local registry_status=0 | |
| verify_published_tarball "$package_name" "$packed_integrity" || registry_status=$? | |
| if [[ "$registry_status" -eq 0 ]]; then | |
| return 0 | |
| fi | |
| if [[ "$registry_status" -eq 2 ]]; then | |
| return 2 | |
| fi | |
| if publish_or_verify publish_with_oidc "$package_name" "$tarball" "$packed_integrity"; then | |
| return 0 | |
| fi | |
| echo "::warning::OIDC trusted publish failed for ${package_name}; trying secrets.NPM_TOKEN fallback." | |
| publish_or_verify publish_with_token "$package_name" "$tarball" "$packed_integrity" | |
| } | |
| # shellcheck disable=SC2153 # Values are supplied by the step env block. | |
| publish_package "$PACKAGE_NAME" "$TARBALL" "$PACKED_INTEGRITY" | |
| publish_package "$ALIAS_PACKAGE_NAME" "$ALIAS_TARBALL" "$ALIAS_PACKED_INTEGRITY" | |
| github-release: | |
| needs: | |
| - prepare | |
| - binaries | |
| - publish | |
| runs-on: ${{ vars.PUBLIC_RELEASE_RUNNER || 'ubuntu-latest' }} | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_sha }} | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.0 | |
| with: | |
| name: npm-tarball-${{ needs.prepare.outputs.release_tag }} | |
| path: release-assets | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.0 | |
| with: | |
| pattern: maestro-* | |
| path: release-assets | |
| merge-multiple: true | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.0 | |
| with: | |
| name: release-web-dist-${{ needs.prepare.outputs.release_tag }} | |
| path: release-assets | |
| - name: Install cosign | |
| run: | | |
| set -euo pipefail | |
| cosign_version="2.6.1" | |
| cosign_sha256="064954c5d8c7e3b28188eee5b1727b31c411550bc5fefd41aa672d3c761d103a" | |
| mkdir -p "$RUNNER_TEMP/cosignbin" | |
| curl -fsSL --max-time 180 --retry 2 --retry-delay 5 \ | |
| -o "$RUNNER_TEMP/cosignbin/cosign" \ | |
| "https://github.com/sigstore/cosign/releases/download/v${cosign_version}/cosign-linux-amd64" | |
| echo "${cosign_sha256} $RUNNER_TEMP/cosignbin/cosign" | sha256sum -c - | |
| chmod +x "$RUNNER_TEMP/cosignbin/cosign" | |
| echo "$RUNNER_TEMP/cosignbin" >> "$GITHUB_PATH" | |
| - name: Create signed channel metadata | |
| env: | |
| CHANNEL_KEY_ID: ${{ needs.prepare.outputs.release_channel == 'stable' && 'stable-2026-08-0c3df2ac' || 'preview-2026-08-912a0dab' }} | |
| MAESTRO_CHANNEL_PRIVATE_KEY: ${{ needs.prepare.outputs.release_channel == 'stable' && secrets.MAESTRO_STABLE_CHANNEL_PRIVATE_KEY || secrets.MAESTRO_PREVIEW_CHANNEL_PRIVATE_KEY }} | |
| RELEASE_CHANNEL: ${{ needs.prepare.outputs.release_channel }} | |
| RELEASE_SHA: ${{ needs.prepare.outputs.release_sha }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} | |
| RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} | |
| run: | | |
| set -euo pipefail | |
| compatibility_digest="$(jq -r '.compatibilityDigest' proto/maestro/v1/protocol-compatibility-manifest.json)" | |
| for platform in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do | |
| case "$platform" in | |
| linux-x64) target=x86_64-unknown-linux-gnu ;; | |
| linux-arm64) target=aarch64-unknown-linux-gnu ;; | |
| darwin-x64) target=x86_64-apple-darwin ;; | |
| darwin-arm64) target=aarch64-apple-darwin ;; | |
| esac | |
| digest="sha256:$(sha256sum "release-assets/maestro-${platform}" | awk '{print $1}')" | |
| rustc_identity="$(cat "release-assets/rustc-${platform}.txt")" | |
| node scripts/generate-runtime-passport.mjs \ | |
| --artifact-kind native_binary \ | |
| --artifact-name "maestro-${platform}" \ | |
| --artifact-digest "$digest" \ | |
| --source-sha "$RELEASE_SHA" \ | |
| --compatibility-digest "$compatibility_digest" \ | |
| --launch-spec-version evalops.maestro.hosted-launch-spec.v1 \ | |
| --receipt-version evalops.maestro.runtime-receipt.v1 \ | |
| --rustc "$rustc_identity" \ | |
| --target "$target" \ | |
| --behavior-profile hosted-http-sse-v1 \ | |
| --out "runtime-passport-maestro-${platform}.json" | |
| done | |
| node scripts/create-release-metadata.mjs \ | |
| --version "$RELEASE_VERSION" \ | |
| --release-tag "$RELEASE_TAG" \ | |
| --source-sha "$RELEASE_SHA" \ | |
| --out release-assets/release-metadata.json | |
| cp release-assets/release-metadata.json release-assets/version.json | |
| node scripts/create-release-channel-manifest.mjs \ | |
| --version "$RELEASE_VERSION" \ | |
| --channel "$RELEASE_CHANNEL" \ | |
| --key-id "$CHANNEL_KEY_ID" \ | |
| --release-url "https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}" \ | |
| --metadata-url "https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/release-metadata.json" \ | |
| --metadata-file release-assets/release-metadata.json \ | |
| --source-sha "$RELEASE_SHA" \ | |
| --out release-assets/channel-manifest.json | |
| cp release-assets/channel-manifest.json release-assets/manifest.json | |
| mv runtime-passport-maestro-*.json release-assets/ | |
| - name: Sign native release assets | |
| working-directory: release-assets | |
| run: | | |
| set -euo pipefail | |
| files=( | |
| maestro-darwin-arm64 | |
| maestro-darwin-x64 | |
| maestro-linux-arm64 | |
| maestro-linux-x64 | |
| maestro-web-dist.tar.gz | |
| ) | |
| files+=(release-metadata.json version.json channel-manifest.json manifest.json) | |
| files+=(runtime-passport-maestro-*.json) | |
| sha256sum "${files[@]}" > SHA256SUMS | |
| cosign sign-blob --yes --bundle SHA256SUMS.cosign.bundle SHA256SUMS | |
| for binary in maestro-darwin-arm64 maestro-darwin-x64 maestro-linux-arm64 maestro-linux-x64; do | |
| cosign sign-blob --yes --bundle "${binary}.cosign.bundle" "$binary" | |
| done | |
| for passport in runtime-passport-maestro-*.json; do | |
| cosign sign-blob --yes --bundle "${passport}.cosign.bundle" "$passport" | |
| done | |
| - name: Verify release tag has not moved | |
| env: | |
| EXPECTED_RELEASE_SHA: ${{ needs.prepare.outputs.release_sha }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| for attempt in 1 2 3; do | |
| if timeout 60s git \ | |
| -c http.lowSpeedLimit=1000 \ | |
| -c http.lowSpeedTime=30 \ | |
| fetch --force --no-tags origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"; then | |
| break | |
| fi | |
| if [[ "$attempt" -eq 3 ]]; then | |
| echo "::error::Unable to verify ${RELEASE_TAG} after ${attempt} bounded attempts." | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| current_release_sha="$(git rev-list -n 1 "$RELEASE_TAG")" | |
| if [[ "$current_release_sha" != "$EXPECTED_RELEASE_SHA" ]]; then | |
| echo "::error::${RELEASE_TAG} moved from ${EXPECTED_RELEASE_SHA} to ${current_release_sha}; refusing to create a mismatched release." | |
| exit 1 | |
| fi | |
| - uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.release_tag }} | |
| name: Maestro ${{ needs.prepare.outputs.release_version }} | |
| generate_release_notes: true | |
| prerelease: ${{ needs.prepare.outputs.release_channel != 'stable' }} | |
| make_latest: ${{ needs.prepare.outputs.release_channel == 'stable' }} | |
| files: release-assets/* | |
| post-publish-canary: | |
| needs: | |
| - prepare | |
| - publish | |
| runs-on: ${{ vars.PUBLIC_RELEASE_RUNNER || 'ubuntu-latest' }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| ref: ${{ needs.prepare.outputs.release_sha }} | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| registry-url: https://registry.npmjs.org | |
| - name: Verify published package from npm | |
| env: | |
| CLI_COMMAND: maestro | |
| MAESTRO_INSTALL_AUDIT_LEVEL: critical | |
| MAESTRO_PUBLISHED_REPLAY_SANDBOX_MODE: local | |
| MAESTRO_REGISTRY_SMOKE_EVIDENCE_DIR: published-replay-evidence | |
| MAESTRO_REGISTRY_POLL_ATTEMPTS: "120" | |
| MAESTRO_REGISTRY_POLL_DELAY_MS: "5000" | |
| NPM_CONFIG_FETCH_RETRIES: "1" | |
| NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "2000" | |
| NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000" | |
| NPM_CONFIG_FETCH_TIMEOUT: "30000" | |
| NPM_CONFIG_REGISTRY: https://registry.npmjs.org | |
| PACKAGE_NAME: ${{ needs.prepare.outputs.package_name }} | |
| RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} | |
| run: | | |
| set -euo pipefail | |
| node scripts/smoke-registry-install.js \ | |
| --package "$PACKAGE_NAME" \ | |
| --version "$RELEASE_VERSION" \ | |
| --cli-command "$CLI_COMMAND" | |
| - name: Validate published replay evidence | |
| run: node scripts/verify-published-replay-evidence.js --evidence-dir published-replay-evidence | |
| - name: Upload published replay evidence | |
| if: ${{ always() && hashFiles('published-replay-evidence/*.json') != '' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| retention-days: 14 | |
| name: published-replay-evidence-${{ needs.prepare.outputs.release_tag }} | |
| overwrite: true | |
| path: published-replay-evidence/*.json |