erinra 0.2.0

Memory MCP server for LLM coding assistants
name: Release
run-name: Release ${{ inputs.version }}

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Release version (e.g., v0.1.0 or v0.1.0-alpha.1)"
        required: true
        type: string

permissions:
  contents: write

jobs:
  validate:
    runs-on: ubuntu-latest
    outputs:
      prerelease: ${{ steps.version.outputs.prerelease }}
    steps:
      - name: Validate version format
        id: version
        run: |
          VERSION="${{ inputs.version }}"
          if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then
            echo "::error::Version must match vX.Y.Z or vX.Y.Z-suffix format (got: $VERSION)"
            exit 1
          fi
          if [[ "$VERSION" == *-* ]]; then
            echo "prerelease=true" >> "$GITHUB_OUTPUT"
          else
            echo "prerelease=false" >> "$GITHUB_OUTPUT"
          fi

      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Check tag does not exist
        run: |
          if git rev-parse "${{ inputs.version }}" >/dev/null 2>&1; then
            echo "::error::Tag ${{ inputs.version }} already exists"
            exit 1
          fi

      - name: Verify Cargo.toml version matches tag
        run: |
          CARGO_VERSION="v$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')"
          if [[ "$CARGO_VERSION" != "${{ inputs.version }}" ]]; then
            echo "::error::Cargo.toml version ($CARGO_VERSION) does not match tag (${{ inputs.version }}). Update Cargo.toml before releasing."
            exit 1
          fi

      - name: Verify changelog entry
        if: steps.version.outputs.prerelease == 'false'
        run: |
          if ! grep -q "^## ${{ inputs.version }}" CHANGELOG.md; then
            echo "::error::CHANGELOG.md has no entry for ${{ inputs.version }}"
            exit 1
          fi

      - name: Create and push tag
        run: |
          git tag "${{ inputs.version }}"
          git push origin "${{ inputs.version }}"

  build:
    needs: validate
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            binary: erinra
            archive: erinra-${{ inputs.version }}-x86_64-unknown-linux-gnu.tar.gz
          - target: aarch64-apple-darwin
            os: macos-latest
            binary: erinra
            archive: erinra-${{ inputs.version }}-aarch64-apple-darwin.tar.gz
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            binary: erinra.exe
            archive: erinra-${{ inputs.version }}-x86_64-pc-windows-msvc.zip
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - uses: jdx/mise-action@v4

      - name: Install cargo-binstall
        uses: cargo-bins/cargo-binstall@main

      - name: Install cargo-about
        run: cargo binstall --no-confirm cargo-about

      - name: CI pipeline
        run: mise run ci
        env:
          CARGO_BUILD_TARGET: ${{ matrix.target }}

      - name: Package (unix)
        if: runner.os != 'Windows'
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../${{ matrix.archive }} ${{ matrix.binary }}

      - name: Package (windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          Compress-Archive -Path "target/${{ matrix.target }}/release/${{ matrix.binary }}" -DestinationPath "${{ matrix.archive }}"

      - uses: actions/upload-artifact@v7
        with:
          name: ${{ matrix.target }}
          path: ${{ matrix.archive }}

  release:
    needs: [validate, build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Extract release notes
        run: |
          if [[ "${{ needs.validate.outputs.prerelease }}" == "true" ]]; then
            echo "Pre-release build from $(git rev-parse --short HEAD)." > release-notes.md
          else
            awk '/^## ${{ inputs.version }}/{found=1; next} /^## /{if(found) exit} found' CHANGELOG.md > release-notes.md
            if [[ ! -s release-notes.md ]]; then
              echo "::error::Release notes for ${{ inputs.version }} are empty"
              exit 1
            fi
          fi

      - uses: actions/download-artifact@v8
        with:
          merge-multiple: true

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "${{ inputs.version }}" \
            --title "${{ inputs.version }}" \
            --notes-file release-notes.md \
            ${{ needs.validate.outputs.prerelease == 'true' && '--prerelease' || '' }} \
            erinra-${{ inputs.version }}-*.tar.gz \
            erinra-${{ inputs.version }}-*.zip

  cleanup:
    if: ${{ failure() }}
    needs: [validate, build, release]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Delete tag
        run: git push origin --delete "${{ inputs.version }}" 2>/dev/null || true

      - name: Delete draft release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh release delete "${{ inputs.version }}" --yes 2>/dev/null || true