Documentation
name: Release

on:
  workflow_dispatch:
    inputs:
      bump_type:
        description: "Version bump type"
        required: true
        type: choice
        options:
          - patch
          - minor
          - major
      dry_run:
        description: "Dry run (no publish, no push)"
        required: false
        type: boolean
        default: false

env:
  CARGO_TERM_COLOR: always
  HOMEBREW_TAP_REPO: ahkohd/homebrew-writestead

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    outputs:
      version: ${{ steps.version.outputs.new }}

    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ssh-key: ${{ secrets.DEPLOY_KEY }}

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt

      - uses: Swatinem/rust-cache@v2

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: "24"
          registry-url: "https://registry.npmjs.org"

      - name: Quality gates
        run: |
          cargo fmt --all -- --check
          cargo clippy --workspace --all-targets -- -D warnings
          cargo build --workspace --bins
          cargo test --workspace --all-targets

      - name: Calculate new version
        id: version
        run: |
          current=$(grep -m1 '^version = ' Cargo.toml | sed 's/.*"\(.*\)"/\1/')
          IFS='.' read -r major minor patch <<< "$current"

          case "${{ inputs.bump_type }}" in
            major) major=$((major + 1)); minor=0; patch=0 ;;
            minor) minor=$((minor + 1)); patch=0 ;;
            patch) patch=$((patch + 1)) ;;
          esac

          new="${major}.${minor}.${patch}"
          echo "current=$current" >> "$GITHUB_OUTPUT"
          echo "new=$new" >> "$GITHUB_OUTPUT"
          echo "Bumping $current -> $new"

      - name: Update Cargo version
        run: |
          NEW_VERSION="${{ steps.version.outputs.new }}"
          perl -0777 -i -pe 's/^version = ".*"/version = "'"$NEW_VERSION"'"/m' Cargo.toml
          cargo update --workspace

      - name: Update npm package versions
        run: node scripts/sync-npm-version.cjs "${{ steps.version.outputs.new }}"

      - name: npm package dry run
        run: npm pack --dry-run

      - name: Configure git
        if: ${{ !inputs.dry_run }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Commit and tag
        if: ${{ !inputs.dry_run }}
        run: |
          BRANCH="${GITHUB_REF_NAME}"
          git add Cargo.toml Cargo.lock
          git add package.json
          git add npm/darwin-arm64/package.json npm/darwin-x64/package.json npm/linux-x64-gnu/package.json npm/win32-x64-msvc/package.json
          git commit -m "chore: release v${{ steps.version.outputs.new }}"
          git tag "v${{ steps.version.outputs.new }}"
          git push origin "HEAD:${BRANCH}"
          git push origin "v${{ steps.version.outputs.new }}"

      - name: Publish crate
        if: ${{ !inputs.dry_run }}
        run: cargo publish --locked
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

      - name: Wait for crates.io index
        if: ${{ !inputs.dry_run }}
        run: sleep 30

      - name: Dry run summary
        if: ${{ inputs.dry_run }}
        run: |
          echo "## Dry Run Summary" >> "$GITHUB_STEP_SUMMARY"
          echo "- Current version: ${{ steps.version.outputs.current }}" >> "$GITHUB_STEP_SUMMARY"
          echo "- New version: ${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "- Would commit, tag, and push v${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "- Would publish writestead crate to crates.io" >> "$GITHUB_STEP_SUMMARY"
          echo "- Would build release artifacts for macOS, Linux, and Windows" >> "$GITHUB_STEP_SUMMARY"
          echo "- Would publish npm packages:" >> "$GITHUB_STEP_SUMMARY"
          echo "  - @ahkohd/writestead-darwin-arm64@${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "  - @ahkohd/writestead-darwin-x64@${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "  - @ahkohd/writestead-linux-x64-gnu@${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "  - @ahkohd/writestead-win32-x64-msvc@${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "  - @ahkohd/writestead@${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          echo "- Would create GitHub Release v${{ steps.version.outputs.new }}" >> "$GITHUB_STEP_SUMMARY"
          cargo publish --dry-run --allow-dirty --locked
          npm publish --dry-run --access public

  build:
    name: Build (${{ matrix.target }})
    needs: release
    if: ${{ !inputs.dry_run }}
    strategy:
      matrix:
        include:
          - target: aarch64-apple-darwin
            os: macos-14
          - target: x86_64-apple-darwin
            os: macos-15
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v6
        with:
          ref: v${{ needs.release.outputs.version }}

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

      - name: Build
        run: cargo build --release --bin writestead --target ${{ matrix.target }}

      - name: Package (unix)
        if: ${{ matrix.os != 'windows-latest' }}
        run: |
          tar -czvf writestead-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release writestead

      - name: Package (windows)
        if: ${{ matrix.os == 'windows-latest' }}
        shell: pwsh
        run: |
          tar -czvf writestead-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release writestead.exe

      - name: Upload artifact
        uses: actions/upload-artifact@v7
        with:
          name: writestead-${{ matrix.target }}
          path: writestead-${{ matrix.target }}.tar.gz

  publish-npm:
    name: Publish npm packages
    needs: [release, build]
    if: ${{ !inputs.dry_run }}
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v6
        with:
          ref: v${{ needs.release.outputs.version }}

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: "24"
          registry-url: "https://registry.npmjs.org"

      - name: Download artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Stage npm packages
        run: |
          STAGE_DIR="${RUNNER_TEMP}/writestead-npm"
          rm -rf "${STAGE_DIR}"
          mkdir -p "${STAGE_DIR}"

          mkdir -p "${STAGE_DIR}/main"
          cp -R package.json README.md bin lib "${STAGE_DIR}/main"

          mkdir -p "${STAGE_DIR}/darwin-arm64"
          cp -R npm/darwin-arm64/. "${STAGE_DIR}/darwin-arm64"
          rm -f "${STAGE_DIR}/darwin-arm64/bin/.gitkeep"
          tar -xzf artifacts/writestead-aarch64-apple-darwin.tar.gz -C "${STAGE_DIR}/darwin-arm64/bin"
          chmod +x "${STAGE_DIR}/darwin-arm64/bin/writestead"

          mkdir -p "${STAGE_DIR}/darwin-x64"
          cp -R npm/darwin-x64/. "${STAGE_DIR}/darwin-x64"
          rm -f "${STAGE_DIR}/darwin-x64/bin/.gitkeep"
          tar -xzf artifacts/writestead-x86_64-apple-darwin.tar.gz -C "${STAGE_DIR}/darwin-x64/bin"
          chmod +x "${STAGE_DIR}/darwin-x64/bin/writestead"

          mkdir -p "${STAGE_DIR}/linux-x64-gnu"
          cp -R npm/linux-x64-gnu/. "${STAGE_DIR}/linux-x64-gnu"
          rm -f "${STAGE_DIR}/linux-x64-gnu/bin/.gitkeep"
          tar -xzf artifacts/writestead-x86_64-unknown-linux-gnu.tar.gz -C "${STAGE_DIR}/linux-x64-gnu/bin"
          chmod +x "${STAGE_DIR}/linux-x64-gnu/bin/writestead"

          mkdir -p "${STAGE_DIR}/win32-x64-msvc"
          cp -R npm/win32-x64-msvc/. "${STAGE_DIR}/win32-x64-msvc"
          rm -f "${STAGE_DIR}/win32-x64-msvc/bin/.gitkeep"
          tar -xzf artifacts/writestead-x86_64-pc-windows-msvc.tar.gz -C "${STAGE_DIR}/win32-x64-msvc/bin"

          echo "STAGE_DIR=${STAGE_DIR}" >> "$GITHUB_ENV"

      - name: Verify npm auth
        run: npm whoami
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Publish platform packages
        run: |
          npm publish "${STAGE_DIR}/darwin-arm64" --provenance --access public
          npm publish "${STAGE_DIR}/darwin-x64" --provenance --access public
          npm publish "${STAGE_DIR}/linux-x64-gnu" --provenance --access public
          npm publish "${STAGE_DIR}/win32-x64-msvc" --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Wait for npm index
        run: sleep 15

      - name: Publish main package
        run: npm publish "${STAGE_DIR}/main" --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  publish:
    name: Publish Release
    needs: [release, build]
    if: ${{ !inputs.dry_run }}
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: v${{ needs.release.outputs.version }}
          generate_release_notes: true
          files: artifacts/*

  update-homebrew:
    name: Update Homebrew Tap
    needs: [release, publish]
    if: ${{ !inputs.dry_run }}
    runs-on: ubuntu-latest

    steps:
      - name: Check token and tap repo
        id: tap
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
        run: |
          if [ -z "${GH_TOKEN}" ]; then
            echo "ready=false" >> "$GITHUB_OUTPUT"
            echo "reason=missing_token" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          if git ls-remote "https://github.com/${HOMEBREW_TAP_REPO}.git" >/dev/null 2>&1; then
            echo "ready=true" >> "$GITHUB_OUTPUT"
            echo "reason=ok" >> "$GITHUB_OUTPUT"
          else
            echo "ready=false" >> "$GITHUB_OUTPUT"
            echo "reason=missing_repo" >> "$GITHUB_OUTPUT"
          fi

      - name: Skip summary
        if: ${{ steps.tap.outputs.ready != 'true' }}
        run: |
          if [ "${{ steps.tap.outputs.reason }}" = "missing_token" ]; then
            echo "- Homebrew update skipped (missing HOMEBREW_TAP_TOKEN secret)" >> "$GITHUB_STEP_SUMMARY"
          else
            echo "- Homebrew update skipped (missing repo ${HOMEBREW_TAP_REPO})" >> "$GITHUB_STEP_SUMMARY"
          fi

      - name: Download artifacts
        if: ${{ steps.tap.outputs.ready == 'true' }}
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          merge-multiple: true

      - name: Compute SHA256
        if: ${{ steps.tap.outputs.ready == 'true' }}
        id: sha
        run: |
          echo "macos_arm64=$(shasum -a 256 artifacts/writestead-aarch64-apple-darwin.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
          echo "macos_x86_64=$(shasum -a 256 artifacts/writestead-x86_64-apple-darwin.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
          echo "linux_x86_64=$(shasum -a 256 artifacts/writestead-x86_64-unknown-linux-gnu.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"

      - name: Update formula
        if: ${{ steps.tap.outputs.ready == 'true' }}
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
        run: |
          VERSION="${{ needs.release.outputs.version }}"
          SHA_MACOS_ARM64="${{ steps.sha.outputs.macos_arm64 }}"
          SHA_MACOS_X86_64="${{ steps.sha.outputs.macos_x86_64 }}"
          SHA_LINUX_X86_64="${{ steps.sha.outputs.linux_x86_64 }}"

          git clone "https://x-access-token:${GH_TOKEN}@github.com/${HOMEBREW_TAP_REPO}.git"
          cd "${HOMEBREW_TAP_REPO##*/}"

          mkdir -p Formula
          cat > Formula/writestead.rb << 'FORMULA'
          class Writestead < Formula
            desc "LLM Wiki"
            homepage "https://github.com/ahkohd/writestead"
            version "VERSION_PLACEHOLDER"
            license "MIT"

            on_macos do
              on_arm do
                url "https://github.com/ahkohd/writestead/releases/download/vVERSION_PLACEHOLDER/writestead-aarch64-apple-darwin.tar.gz"
                sha256 "SHA_MACOS_ARM64_PLACEHOLDER"
              end
              on_intel do
                url "https://github.com/ahkohd/writestead/releases/download/vVERSION_PLACEHOLDER/writestead-x86_64-apple-darwin.tar.gz"
                sha256 "SHA_MACOS_X86_64_PLACEHOLDER"
              end
            end

            on_linux do
              on_intel do
                url "https://github.com/ahkohd/writestead/releases/download/vVERSION_PLACEHOLDER/writestead-x86_64-unknown-linux-gnu.tar.gz"
                sha256 "SHA_LINUX_X86_64_PLACEHOLDER"
              end
            end

            def install
              bin.install "writestead"
            end

            test do
              system "#{bin}/writestead", "--version"
            end
          end
          FORMULA

          sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" Formula/writestead.rb
          sed -i "s/SHA_MACOS_ARM64_PLACEHOLDER/${SHA_MACOS_ARM64}/g" Formula/writestead.rb
          sed -i "s/SHA_MACOS_X86_64_PLACEHOLDER/${SHA_MACOS_X86_64}/g" Formula/writestead.rb
          sed -i "s/SHA_LINUX_X86_64_PLACEHOLDER/${SHA_LINUX_X86_64}/g" Formula/writestead.rb

          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/writestead.rb
          git commit -m "writestead ${VERSION}"
          git push