typechar 1.1.0

Type any Unicode string on macOS, regardless of keyboard layout
Documentation
name: Release

# Builds a universal (arm64 + x86_64) binary, signs it with a Developer ID
# certificate, notarizes it with Apple, publishes a GitHub release, and bumps
# the Homebrew tap formula.
#
# Required repository secrets (see RELEASING.md for how to create them):
#   MACOS_CERTIFICATE              base64-encoded Developer ID Application .p12
#   MACOS_CERTIFICATE_PASSWORD     password of the .p12
#   MACOS_SIGNING_IDENTITY         e.g. "Developer ID Application: Max Winter (TEAMID)"
#   APP_STORE_CONNECT_KEY          base64-encoded App Store Connect API .p8 key
#   APP_STORE_CONNECT_KEY_ID       key ID shown next to the key
#   APP_STORE_CONNECT_ISSUER_ID    issuer UUID shown on the Integrations page
#   TAP_GITHUB_TOKEN               PAT with push access to blaueeiner/homebrew-tap
#                                  (optional — tap bump is skipped without it)

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

permissions:
  contents: write
  id-token: write # crates.io trusted publishing (OIDC)

jobs:
  release:
    runs-on: macos-14
    env:
      # secrets aren't allowed in step `if` conditions; route through env
      HAS_TAP_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN != '' }}
    steps:
      - uses: actions/checkout@v4

      - name: Check signing secrets are configured
        env:
          MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
          APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }}
        run: |
          if [ -z "$MACOS_CERTIFICATE" ] || [ -z "$APP_STORE_CONNECT_KEY" ]; then
            echo "::error::Signing/notarization secrets are missing. See RELEASING.md." >&2
            exit 1
          fi

      - name: Rust toolchain
        run: |
          rustup toolchain install stable --profile minimal
          rustup target add aarch64-apple-darwin x86_64-apple-darwin

      - name: Check tag matches Cargo.toml version
        run: |
          CARGO_VERSION="$(cargo pkgid | cut -d'#' -f2)"
          if [ "v$CARGO_VERSION" != "$GITHUB_REF_NAME" ]; then
            echo "::error::Tag $GITHUB_REF_NAME does not match Cargo.toml version $CARGO_VERSION" >&2
            exit 1
          fi

      - uses: Swatinem/rust-cache@v2

      - name: Test
        run: cargo test

      - name: Build universal binary
        run: |
          cargo build --release --target aarch64-apple-darwin
          cargo build --release --target x86_64-apple-darwin
          mkdir -p dist
          lipo -create -output dist/typechar \
            target/aarch64-apple-darwin/release/typechar \
            target/x86_64-apple-darwin/release/typechar
          lipo -info dist/typechar

      - name: Import signing certificate
        env:
          MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
        run: |
          KEYCHAIN_PASSWORD="$(uuidgen)"
          echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
          security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security import certificate.p12 -k build.keychain \
            -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple: \
            -s -k "$KEYCHAIN_PASSWORD" build.keychain
          rm certificate.p12

      - name: Sign
        env:
          MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
        run: |
          codesign --force --options runtime --timestamp \
            --sign "$MACOS_SIGNING_IDENTITY" dist/typechar
          codesign --verify --strict --verbose=2 dist/typechar

      - name: Notarize
        env:
          APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }}
          APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
          APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
        run: |
          echo "$APP_STORE_CONNECT_KEY" | base64 --decode > AuthKey.p8
          ditto -c -k --keepParent dist/typechar notarize.zip
          xcrun notarytool submit notarize.zip \
            --key AuthKey.p8 \
            --key-id "$APP_STORE_CONNECT_KEY_ID" \
            --issuer "$APP_STORE_CONNECT_ISSUER_ID" \
            --wait
          rm AuthKey.p8

      - name: Package
        id: package
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          ARCHIVE="typechar-${VERSION}-macos-universal.tar.gz"
          tar -czf "$ARCHIVE" -C dist typechar
          SHA256="$(shasum -a 256 "$ARCHIVE" | cut -d' ' -f1)"
          echo "version=$VERSION"  >> "$GITHUB_OUTPUT"
          echo "archive=$ARCHIVE"  >> "$GITHUB_OUTPUT"
          echo "sha256=$SHA256"    >> "$GITHUB_OUTPUT"

      - name: Create GitHub release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$GITHUB_REF_NAME" "${{ steps.package.outputs.archive }}" \
            --title "typechar ${{ steps.package.outputs.version }}" \
            --generate-notes

      - name: Publish to crates.io
        id: crates-auth
        uses: rust-lang/crates-io-auth-action@v1

      - name: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
        run: cargo publish

      - name: Bump Homebrew tap
        if: ${{ env.HAS_TAP_TOKEN == 'true' }}
        env:
          TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
          VERSION: ${{ steps.package.outputs.version }}
          ARCHIVE: ${{ steps.package.outputs.archive }}
          SHA256: ${{ steps.package.outputs.sha256 }}
        run: |
          git clone "https://x-access-token:${TAP_GITHUB_TOKEN}@github.com/blaueeiner/homebrew-tap.git" tap
          mkdir -p tap/Formula
          sed -e "s/{{VERSION}}/${VERSION}/g" -e "s/{{SHA256}}/${SHA256}/g" \
            Formula/typechar.rb > tap/Formula/typechar.rb
          cd tap
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/typechar.rb
          git commit -m "typechar ${VERSION}"
          git push