oy-cli 0.13.5

Focused OpenCode agent with repeatable audits, code reviews, and one-finding fixes
Documentation
name: Release

on:
  push:
    tags: ["v*"]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  release:
    if: github.repository_owner == 'adonm'
    name: Release (${{ matrix.target }})
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read

    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            archive: tar.gz
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
            archive: tar.gz
          - os: macos-14
            target: aarch64-apple-darwin
            archive: tar.gz
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
      - name: Add cross-compilation target
        run: rustup target add ${{ matrix.target }}
      - name: Build size-optimized release binary
        env:
          RUSTFLAGS: -C debuginfo=0
        run: cargo build --release --locked --target ${{ matrix.target }}
      - name: Strip binary
        shell: bash
        run: |
          if [[ "${{ runner.os }}" == "macOS" ]]; then
            strip target/${{ matrix.target }}/release/oy
          else
            strip --strip-all target/${{ matrix.target }}/release/oy
          fi
      - name: Package
        shell: bash
        env:
          REF_NAME: ${{ github.ref_name }}
        run: |
          mkdir -p dist
          cp target/${{ matrix.target }}/release/oy dist/oy
          tar -C dist -czf "oy-${REF_NAME}-${{ matrix.target }}.tar.gz" oy
      - name: Upload artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: oy-${{ github.ref_name }}-${{ matrix.target }}
          path: oy-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }}
          if-no-files-found: error

  publish-release:
    if: github.repository_owner == 'adonm'
    needs: [release, publish-crate, publish-npm]
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      attestations: write

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - name: Download artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: dist
          merge-multiple: true
      - name: Extract changelog release notes
        env:
          REF_NAME: ${{ github.ref_name }}
        run: |
          python3 - <<'PY'
          import os
          import re
          import sys
          from pathlib import Path

          version = os.environ["REF_NAME"].removeprefix("v")
          changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
          pattern = re.compile(
              rf"^## \[{re.escape(version)}\][^\n]*\n(?P<body>.*?)(?=^## |\Z)",
              re.MULTILINE | re.DOTALL,
          )
          match = pattern.search(changelog)
          if match is None:
              sys.exit(f"CHANGELOG.md has no section for {version}")

          notes = match.group("body").strip()
          if not notes:
              sys.exit(f"CHANGELOG.md section for {version} is empty")

          Path("release-notes.md").write_text(notes + "\n", encoding="utf-8")
          PY
      - name: Attest release assets
        uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
        with:
          subject-path: dist/*
      - name: Create draft GitHub release with assets
        env:
          GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
          REF_NAME: ${{ github.ref_name }}
          REPOSITORY: ${{ github.repository }}
          SHA: ${{ github.sha }}
        run: |
          prerelease_args=()
          if [[ "${REF_NAME}" == *-* ]]; then
            prerelease_args+=(--prerelease)
          fi
          if gh release view "${REF_NAME}" --repo "${REPOSITORY}" >/dev/null 2>&1; then
            gh release edit "${REF_NAME}" \
              --repo "${REPOSITORY}" \
              --draft=true \
              "${prerelease_args[@]}" \
              --target "${SHA}" \
              --title "${REF_NAME}" \
              --notes-file release-notes.md
            gh release upload "${REF_NAME}" dist/* --repo "${REPOSITORY}" --clobber
          else
            gh release create "${REF_NAME}" dist/* \
              --repo "${REPOSITORY}" \
              --draft \
              "${prerelease_args[@]}" \
              --target "${SHA}" \
              --title "${REF_NAME}" \
              --notes-file release-notes.md
          fi
      - name: Publish GitHub release
        env:
          GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
          REF_NAME: ${{ github.ref_name }}
          REPOSITORY: ${{ github.repository }}
        run: gh release edit "${REF_NAME}" --repo "${REPOSITORY}" --draft=false

  publish-crate:
    if: github.repository_owner == 'adonm'
    needs: release
    runs-on: ubuntu-latest
    environment: crates-io
    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
      - name: Verify crate package
        run: cargo package --locked
      - name: Authenticate with crates.io
        id: auth
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
      - name: Publish crate to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          output=$(cargo publish --locked 2>&1) && exit_code=0 || exit_code=$?
          echo "$output"
          if [ "$exit_code" -ne 0 ]; then
            if echo "$output" | grep -q "already exists"; then
              echo "::notice::Version already published on crates.io, skipping"
            else
              exit "$exit_code"
            fi
          fi

  publish-npm:
    if: github.repository_owner == 'adonm'
    needs: release
    runs-on: ubuntu-latest
    environment: npm
    permissions:
      contents: read
      id-token: write

    defaults:
      run:
        working-directory: packages/opencode

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
        with:
          node-version: "24"
          registry-url: https://registry.npmjs.org
          package-manager-cache: false
      - name: Install OIDC-capable npm
        run: npm install --global npm@12.0.1
      - name: Require package version to match tag
        env:
          REF_NAME: ${{ github.ref_name }}
        run: test "v$(node -p 'require("./package.json").version')" = "$REF_NAME"
      - name: Install, build, and test
        run: |
          npm ci --ignore-scripts
          npm run build
          npm test
      - name: Publish public package with npm trusted publishing
        env:
          EXPECTED_SHA: ${{ github.sha }}
        run: |
          package=$(node -p 'require("./package.json").name')
          version=$(node -p 'require("./package.json").version')
          if npm view "$package@$version" version >/dev/null 2>&1; then
            published_sha=$(npm view "$package@$version" gitHead)
            if [ "$published_sha" != "$EXPECTED_SHA" ]; then
              echo "::error::$package@$version already exists from unexpected commit $published_sha"
              exit 1
            fi
            echo "::notice::$package@$version is already published from $published_sha; skipping"
          else
            npm publish --access public
          fi