publish #5
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
| # Releases the autoevals npm + PyPI packages TOGETHER, at one shared version, behind ONE | |
| # approval gate — via braintrustdata/sdk-actions (custom / build-ownership shape). | |
| # | |
| # Shape (fan-out → fan-in → fan-out), one lane per package: | |
| # configure-{js,py} → prepare-{js,py} → validate-{js,py} → pack-{js,py} (unprivileged build+attest) | |
| # └────────────────────────── request-approval (single gate: builds one approval covering | |
| # both packages, then parks at the `publish` environment) | |
| # request-approval → ship-package-{js,py} (gated: verify attestation → publish → GitHub release) | |
| # | |
| # This is hand-authored, but deliberately modeled as the output a future sdk-actions "monorepo" | |
| # template should generate: each lane is an independent, uniformly-parameterized column; the only | |
| # shared, once-per-release job is request-approval, which composes a `packages` array (one entry per | |
| # lane, via build-package-manifest) so reviewers approve BOTH packages in one gate. A failure in | |
| # either lane skips request-approval → neither ships (the packages always release together). | |
| # | |
| # ── SETUP (repo Settings) ───────────────────────────────────────────────────────────────────── | |
| # Environments: `publish` (required reviewers) and `publish-dry-run`. | |
| # Trusted publishers (OIDC, no tokens) — BOTH must set environment=`publish`, which is what keeps | |
| # the unprivileged pack jobs (id-token for attestation only) from being able to publish: | |
| # npm `autoevals` → repo braintrustdata/autoevals, workflow publish.yaml, environment publish | |
| # PyPI `autoevals` → repo braintrustdata/autoevals, workflow publish.yaml, environment publish | |
| # Secrets/vars (optional Slack): SLACK_BOT_TOKEN, SLACK_SDK_RELEASE_CHANNEL. | |
| # | |
| # Version-sync (package.json == py/autoevals/version.py) is enforced by the version-sync.yaml CI | |
| # workflow on PRs, so it is intentionally NOT re-checked here. Prereleases publish the COMMITTED | |
| # version (release_type only suppresses the GitHub release / sets the npm rc channel) — bump to an | |
| # rc version in a PR if you want a prerelease. | |
| # | |
| # sdk-actions pinned @3cdedcc (branch release_request_approval_multi_package, off #63). RE-PIN to the | |
| # merged main SHA before merging this PR. | |
| name: publish | |
| concurrency: | |
| group: publish-${{ inputs.release_type }}-${{ inputs.sha }} | |
| cancel-in-progress: false | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| _instructions: | |
| description: "⚠️ Before starting: merge a version-bump PR (package.json + py/autoevals/version.py, matching). The release publishes the version at the SHA — it can't be overridden here." | |
| type: string | |
| default: "I have merged a version bump PR" | |
| required: false | |
| release_type: | |
| description: "Release type (stable → GitHub releases; prerelease → none, npm rc channel)" | |
| type: choice | |
| default: stable | |
| options: [stable, prerelease] | |
| sha: | |
| description: "Commit SHA (of the version bump) to release — same commit for js + py" | |
| required: true | |
| type: string | |
| prev_release: | |
| description: "Release-notes anchor: a tag or SHA (empty → each lane's previous tag)" | |
| type: string | |
| required: false | |
| dry_run: | |
| description: "Dry run: build + pack + attest, but do not publish or create releases" | |
| type: boolean | |
| default: false | |
| jobs: | |
| # ─────────────────────────── JS lane ─────────────────────────── | |
| configure-js: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.configure.outputs.version }} | |
| release_tag: ${{ steps.configure.outputs.release_tag }} | |
| prev_release: ${{ steps.configure.outputs.prev_release }} | |
| branch: ${{ steps.configure.outputs.branch }} | |
| on_release_branch: ${{ steps.configure.outputs.on_release_branch }} | |
| commit_message: ${{ steps.configure.outputs.commit_message }} | |
| channel: ${{ steps.configure.outputs.channel }} | |
| github_release: ${{ steps.configure.outputs.github_release }} | |
| steps: | |
| - name: Configure release | |
| id: configure | |
| uses: braintrustdata/sdk-actions/actions/release/lang/js/configure@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| sha: ${{ inputs.sha }} | |
| working_directory: . | |
| tag_format: 'js-{version}' | |
| release_type: ${{ inputs.release_type }} | |
| prepare-js: | |
| needs: configure-js | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: write # releases/generate-notes API | |
| outputs: | |
| pr_list: ${{ steps.prepare.outputs.pr_list }} | |
| notes: ${{ steps.prepare.outputs.notes }} | |
| steps: | |
| - name: Prepare release | |
| id: prepare | |
| uses: braintrustdata/sdk-actions/actions/release/prepare@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| release_tag: ${{ needs.configure-js.outputs.release_tag }} | |
| sha: ${{ inputs.sha }} | |
| prev_release: ${{ inputs.prev_release || needs.configure-js.outputs.prev_release }} | |
| validate-js: | |
| needs: [configure-js, prepare-js] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Validate release | |
| uses: braintrustdata/sdk-actions/actions/release/lang/js/validate@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| version: ${{ needs.configure-js.outputs.version }} | |
| sha: ${{ inputs.sha }} | |
| dry_run: ${{ inputs.dry_run }} | |
| working_directory: . | |
| node_version: .tool-versions | |
| package_name: autoevals | |
| release_tag: ${{ needs.configure-js.outputs.release_tag }} | |
| channel: ${{ needs.configure-js.outputs.channel }} | |
| allowed_channels: 'latest,rc' | |
| on_release_branch: ${{ needs.configure-js.outputs.on_release_branch }} | |
| notes: ${{ needs.prepare-js.outputs.notes }} | |
| build: 'false' # pack-js owns the build | |
| sbom: 'false' # pack-js generates + attests the SBOM | |
| pack-js: | |
| needs: [configure-js, prepare-js, validate-js] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - name: Pack + attest | |
| id: pack | |
| uses: braintrustdata/sdk-actions/actions/release/lang/js/pack-pnpm@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| sha: ${{ inputs.sha }} | |
| working_directory: . | |
| node_version: .tool-versions | |
| # autoevals installs + builds on its pinned pnpm 10.33 (to honor its supply-chain config); | |
| # the packer resolves a pnpm >= 11.8 on its own for `pnpm pack` + the SBOM. No override here. | |
| dry_run: ${{ inputs.dry_run }} | |
| - name: Stage artifact | |
| run: | | |
| mkdir -p artifact | |
| cp "${{ steps.pack.outputs.tarball }}" artifact/ | |
| cp ./sbom.json artifact/ 2>/dev/null || true | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: release-package-js | |
| path: artifact/ | |
| retention-days: 1 | |
| if-no-files-found: error | |
| # ─────────────────────────── PY lane ─────────────────────────── | |
| configure-py: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.configure.outputs.version }} | |
| release_tag: ${{ steps.configure.outputs.release_tag }} | |
| prev_release: ${{ steps.configure.outputs.prev_release }} | |
| branch: ${{ steps.configure.outputs.branch }} | |
| on_release_branch: ${{ steps.configure.outputs.on_release_branch }} | |
| commit_message: ${{ steps.configure.outputs.commit_message }} | |
| github_release: ${{ steps.configure.outputs.github_release }} | |
| steps: | |
| - name: Configure release | |
| id: configure | |
| uses: braintrustdata/sdk-actions/actions/release/lang/py/configure@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| version_file: py/autoevals/version.py | |
| sha: ${{ inputs.sha }} | |
| working_directory: . | |
| tag_format: 'py-{version}' | |
| release_type: ${{ inputs.release_type }} | |
| prepare-py: | |
| needs: configure-py | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: write | |
| outputs: | |
| pr_list: ${{ steps.prepare.outputs.pr_list }} | |
| notes: ${{ steps.prepare.outputs.notes }} | |
| steps: | |
| - name: Prepare release | |
| id: prepare | |
| uses: braintrustdata/sdk-actions/actions/release/prepare@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| release_tag: ${{ needs.configure-py.outputs.release_tag }} | |
| sha: ${{ inputs.sha }} | |
| prev_release: ${{ inputs.prev_release || needs.configure-py.outputs.prev_release }} | |
| validate-py: | |
| needs: [configure-py, prepare-py] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Validate release | |
| uses: braintrustdata/sdk-actions/actions/release/lang/py/validate@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| version: ${{ needs.configure-py.outputs.version }} | |
| sha: ${{ inputs.sha }} | |
| dry_run: ${{ inputs.dry_run }} | |
| working_directory: . | |
| python_version: .tool-versions | |
| package_name: autoevals | |
| release_tag: ${{ needs.configure-py.outputs.release_tag }} | |
| on_release_branch: ${{ needs.configure-py.outputs.on_release_branch }} | |
| notes: ${{ needs.prepare-py.outputs.notes }} | |
| build: 'false' # pack-py owns the build | |
| sbom: 'false' # pack-py generates + attests the SBOM | |
| pack-py: | |
| needs: [configure-py, prepare-py, validate-py] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - name: Pack + attest | |
| uses: braintrustdata/sdk-actions/actions/release/lang/py/pack@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| sha: ${{ inputs.sha }} | |
| working_directory: . | |
| python_version: .tool-versions | |
| dry_run: ${{ inputs.dry_run }} | |
| - name: Stage artifact | |
| run: | | |
| mkdir -p artifact/dist | |
| cp ./dist/*.whl ./dist/*.tar.gz artifact/dist/ | |
| cp ./sbom.json artifact/ 2>/dev/null || true | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: release-package-py | |
| path: artifact/ | |
| retention-days: 1 | |
| if-no-files-found: error | |
| # ─────────────── fan-in: ONE approval covering both packages ─────────────── | |
| request-approval: | |
| needs: [configure-js, prepare-js, pack-js, configure-py, prepare-py, pack-py] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: {} | |
| steps: | |
| # One package-manifest entry per lane (label/emoji specified here, near use). Splicing the two | |
| # entries into `packages` is safe — build-package-manifest jq-escapes pr_list (x1f) / notes. | |
| - name: Build js package entry | |
| id: pkg-js | |
| uses: braintrustdata/sdk-actions/actions/release/build-package-manifest@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| name: autoevals | |
| label: 'autoevals (js)' | |
| emoji: ':javascript:' | |
| version: ${{ needs.configure-js.outputs.version }} | |
| tag: ${{ needs.configure-js.outputs.release_tag }} | |
| prev_release: ${{ needs.configure-js.outputs.prev_release }} | |
| pr_list: ${{ needs.prepare-js.outputs.pr_list }} | |
| notes: ${{ needs.prepare-js.outputs.notes }} | |
| - name: Build py package entry | |
| id: pkg-py | |
| uses: braintrustdata/sdk-actions/actions/release/build-package-manifest@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| name: autoevals | |
| label: 'autoevals (py)' | |
| emoji: ':python:' | |
| version: ${{ needs.configure-py.outputs.version }} | |
| tag: ${{ needs.configure-py.outputs.release_tag }} | |
| prev_release: ${{ needs.configure-py.outputs.prev_release }} | |
| pr_list: ${{ needs.prepare-py.outputs.pr_list }} | |
| notes: ${{ needs.prepare-py.outputs.notes }} | |
| - name: Request release approval | |
| uses: braintrustdata/sdk-actions/actions/release/request-approval@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| packages: '[${{ steps.pkg-js.outputs.package }}, ${{ steps.pkg-py.outputs.package }}]' | |
| title: autoevals | |
| sha: ${{ inputs.sha }} | |
| branch: ${{ needs.configure-js.outputs.branch }} | |
| on_release_branch: ${{ needs.configure-js.outputs.on_release_branch }} | |
| commit_message: ${{ needs.configure-js.outputs.commit_message }} | |
| dry_run: ${{ inputs.dry_run }} | |
| slack_token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| slack_channel: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} | |
| # ─────────────── fan-out: gated per-lane publish (one approval releases both) ─────────────── | |
| ship-package-js: | |
| needs: [configure-js, prepare-js, pack-js, request-approval] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| environment: ${{ inputs.dry_run && 'publish-dry-run' || 'publish' }} | |
| permissions: | |
| contents: write | |
| id-token: write # OIDC trusted publishing + provenance | |
| attestations: read # gh attestation verify | |
| steps: | |
| - name: Download artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: release-package-js | |
| path: dl | |
| - name: Resolve tarball | |
| id: art | |
| run: echo "tarball=$(ls dl/*.tgz)" >> "$GITHUB_OUTPUT" | |
| - name: Ship package | |
| uses: braintrustdata/sdk-actions/actions/release/lang/js/ship-package@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| tarball: ${{ steps.art.outputs.tarball }} | |
| working_directory: dl | |
| sha: ${{ inputs.sha }} | |
| dry_run: ${{ inputs.dry_run }} | |
| release_tag: ${{ needs.configure-js.outputs.release_tag }} | |
| github_release: ${{ needs.configure-js.outputs.github_release }} | |
| version: ${{ needs.configure-js.outputs.version }} | |
| access: 'public' | |
| provenance: 'true' | |
| channel: ${{ needs.configure-js.outputs.channel }} | |
| package_name: autoevals | |
| label: 'autoevals (js)' | |
| notes: ${{ needs.prepare-js.outputs.notes }} | |
| prev_release: ${{ needs.configure-js.outputs.prev_release }} | |
| branch: ${{ needs.configure-js.outputs.branch }} | |
| on_release_branch: ${{ needs.configure-js.outputs.on_release_branch }} | |
| pr_list: ${{ needs.prepare-js.outputs.pr_list }} | |
| slack_token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| slack_channel: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} | |
| emoji: ':javascript:' | |
| ship-package-py: | |
| needs: [configure-py, prepare-py, pack-py, request-approval] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| environment: ${{ inputs.dry_run && 'publish-dry-run' || 'publish' }} | |
| permissions: | |
| contents: write | |
| id-token: write # OIDC trusted publishing + PEP 740 | |
| attestations: read # gh attestation verify | |
| steps: | |
| - name: Download artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: release-package-py | |
| path: dl | |
| - name: Ship package | |
| uses: braintrustdata/sdk-actions/actions/release/lang/py/ship-package@3cdedcc1ffa219aa05e8687ab7451f2fd72a8950 | |
| with: | |
| working_directory: dl # holds dist/ (whl + sdist) + sbom.json | |
| sha: ${{ inputs.sha }} | |
| dry_run: ${{ inputs.dry_run }} | |
| release_tag: ${{ needs.configure-py.outputs.release_tag }} | |
| github_release: ${{ needs.configure-py.outputs.github_release }} | |
| version: ${{ needs.configure-py.outputs.version }} | |
| package_name: autoevals | |
| label: 'autoevals (py)' | |
| notes: ${{ needs.prepare-py.outputs.notes }} | |
| prev_release: ${{ needs.configure-py.outputs.prev_release }} | |
| branch: ${{ needs.configure-py.outputs.branch }} | |
| on_release_branch: ${{ needs.configure-py.outputs.on_release_branch }} | |
| pr_list: ${{ needs.prepare-py.outputs.pr_list }} | |
| slack_token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| slack_channel: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} | |
| emoji: ':python:' |