rho-coding-agent 0.17.0

A lightweight agent harness inspired by Pi
name: release-please

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
      tag_name: ${{ steps.release.outputs.tag_name }}
    steps:
      - id: release
        uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          config-file: .release-please-config.json
      - uses: actions/checkout@v4
        if: ${{ steps.release.outputs.release_created == 'true' }}
        with:
          ref: ${{ steps.release.outputs.tag_name }}
      - name: Install Rust
        if: ${{ steps.release.outputs.release_created == 'true' }}
        uses: dtolnay/rust-toolchain@stable
      - name: Cache Cargo registry
        if: ${{ steps.release.outputs.release_created == 'true' }}
        uses: swatinem/rust-cache@v2
      - name: Publish crate
        if: ${{ steps.release.outputs.release_created == 'true' }}
        run: cargo publish --locked --token "$CARGO_REGISTRY_TOKEN"
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  publish-arch-package:
    needs: release
    if: ${{ needs.release.outputs.release_created == 'true' }}
    runs-on: ubuntu-latest
    container: archlinux:latest
    permissions:
      contents: read
    steps:
      - name: Install build tools
        run: |
          pacman -Sy --noconfirm base-devel git pacman-contrib rust sqlite

      - name: Checkout package repo
        uses: actions/checkout@v4
        with:
          ref: ${{ needs.release.outputs.tag_name }}

      - name: Build package
        run: |
          set -euo pipefail
          useradd --create-home builder
          chown -R builder:builder "$GITHUB_WORKSPACE"
          runuser -u builder -- bash -lc "cd '$GITHUB_WORKSPACE' && makepkg -s --noconfirm"

      - name: Checkout mjiang-extras
        uses: actions/checkout@v4
        with:
          repository: matthewyjiang/mjiang-extras
          token: ${{ secrets.MJIANG_EXTRAS_PUSH_TOKEN }}
          path: mjiang-extras

      - name: Publish package files
        run: |
          set -euo pipefail

          chmod +x mjiang-extras/scripts/publish-packages.sh
          mjiang-extras/scripts/publish-packages.sh --repo-dir mjiang-extras ./*.pkg.tar.zst

          cd mjiang-extras
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add x86_64/
          git commit -m "Update packages from ${{ github.repository }}" || exit 0
          git push

  release-binaries:
    needs: release
    if: ${{ needs.release.outputs.release_created == 'true' }}
    name: release binary ${{ matrix.target }}
    permissions:
      contents: write
      pull-requests: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
            target: x86_64-unknown-linux-gnu
            cache-targets: false
          - os: macos-13
            target: x86_64-apple-darwin
            cache-targets: true
          - os: macos-14
            target: aarch64-apple-darwin
            cache-targets: true
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            cache-targets: true
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.release.outputs.tag_name }}
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Cache Cargo registry
        uses: swatinem/rust-cache@v2
        with:
          cache-targets: ${{ matrix.cache-targets }}
          shared-key: release-binaries-${{ matrix.target }}
      - name: Build binary
        run: cargo build --release --locked --bin rho --target ${{ matrix.target }}
      - name: Sign Windows binary
        if: runner.os == 'Windows'
        shell: pwsh
        env:
          WINDOWS_SIGNING_CERT_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_BASE64 }}
          WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }}
        run: |
          if ([string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_BASE64)) {
            Write-Host "WINDOWS_SIGNING_CERT_BASE64 is not set; skipping Authenticode signing."
            exit 0
          }
          $CertPath = Join-Path $env:RUNNER_TEMP "rho-signing-cert.pfx"
          [IO.File]::WriteAllBytes($CertPath, [Convert]::FromBase64String($env:WINDOWS_SIGNING_CERT_BASE64))
          $Signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
            Where-Object { $_.FullName -match "x64" } |
            Select-Object -First 1 -ExpandProperty FullName
          if (-not $Signtool) {
            throw "signtool.exe was not found"
          }
          & $Signtool sign /f $CertPath /p $env:WINDOWS_SIGNING_CERT_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 target/${{ matrix.target }}/release/rho.exe
          & $Signtool verify /pa target/${{ matrix.target }}/release/rho.exe
      - name: Package Unix binary
        if: runner.os != 'Windows'
        run: |
          mkdir -p dist
          cp target/${{ matrix.target }}/release/rho dist/rho
          tar -C dist -czf rho-${{ matrix.target }}.tar.gz rho
          shasum -a 256 rho-${{ matrix.target }}.tar.gz > rho-${{ matrix.target }}.tar.gz.sha256
      - name: Package Windows binary
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path dist | Out-Null
          Copy-Item target/${{ matrix.target }}/release/rho.exe dist/rho.exe
          Compress-Archive -Path dist/rho.exe -DestinationPath rho-${{ matrix.target }}.zip -Force
          $Hash = (Get-FileHash -Algorithm SHA256 rho-${{ matrix.target }}.zip).Hash.ToLowerInvariant()
          "$Hash  rho-${{ matrix.target }}.zip" | Out-File -Encoding ascii rho-${{ matrix.target }}.zip.sha256
      - name: Upload Unix release assets
        if: runner.os != 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        run: gh release upload "$TAG" rho-${{ matrix.target }}.tar.gz rho-${{ matrix.target }}.tar.gz.sha256 --clobber
      - name: Upload Windows release assets
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        shell: pwsh
        run: gh release upload "$env:TAG" rho-${{ matrix.target }}.zip rho-${{ matrix.target }}.zip.sha256 --clobber
      - name: Checkout main for Scoop manifest update
        if: runner.os == 'Windows'
        uses: actions/checkout@v4
        with:
          ref: main
      - name: Download Windows checksum
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
          TAG: ${{ needs.release.outputs.tag_name }}
        shell: pwsh
        run: gh release download "$env:TAG" --pattern "rho-${{ matrix.target }}.zip.sha256"
      - name: Update Scoop manifest
        if: runner.os == 'Windows'
        env:
          GH_TOKEN: ${{ github.token }}
        shell: pwsh
        run: |
          $Tag = "${{ needs.release.outputs.tag_name }}"
          $Version = $Tag -replace '^rho-coding-agent-v', ''
          $Asset = "rho-${{ matrix.target }}.zip"
          $Hash = ((Get-Content "$Asset.sha256" -Raw) -split '\s+')[0]
          $Url = "https://github.com/${{ github.repository }}/releases/download/$Tag/$Asset"

          $ManifestPath = "bucket/rho.json"
          $Manifest = Get-Content $ManifestPath -Raw | ConvertFrom-Json
          $Manifest.version = $Version
          $Manifest.architecture.'64bit'.url = $Url
          $Manifest.architecture.'64bit'.hash = $Hash
          $Manifest | ConvertTo-Json -Depth 10 | Set-Content -Path $ManifestPath -Encoding UTF8

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add $ManifestPath
          if (git diff --cached --quiet) {
            Write-Host "Scoop manifest is already up to date."
            exit 0
          }
          $Branch = "build/update-scoop-manifest-$Tag"
          if (git ls-remote --heads origin $Branch) {
            git fetch origin "${Branch}:refs/remotes/origin/${Branch}"
          }
          git checkout -B $Branch
          git commit -m "build(windows): update scoop manifest for $Tag"
          git push --force-with-lease origin "HEAD:$Branch"

          $Title = "build(windows): update scoop manifest for $Tag"
          $BodyFile = New-TemporaryFile
          Set-Content -Path $BodyFile -Value "Updates the Scoop manifest for $Tag."
          $ExistingPr = gh pr list --head $Branch --base main --json number --jq '.[0].number'
          if ($ExistingPr) {
            gh pr edit $ExistingPr --title $Title --body-file $BodyFile
          } else {
            gh pr create --base main --head $Branch --title $Title --body-file $BodyFile
          }