shipsafe 0.2.1

AI-Powered Pre-Deploy Security Gate
name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to build and publish (e.g. v0.1.0)'
        required: true

# Least privilege at the workflow level; each job opts into only what it needs.
permissions: {}

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    name: Build (${{ matrix.target }})
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
          - os: macos-latest
            target: x86_64-apple-darwin
          - os: macos-latest
            target: aarch64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false
      - name: Install Rust (stable)
        shell: bash
        run: |
          rustup toolchain install stable --profile minimal
          rustup default stable
          rustup target add ${{ matrix.target }}
      - name: Build
        run: cargo build --release --target ${{ matrix.target }}
      - name: Package (unix)
        if: runner.os != 'Windows'
        run: |
          cd target/${{ matrix.target }}/release
          tar -czf "$GITHUB_WORKSPACE/shipsafe-${{ matrix.target }}.tar.gz" shipsafe
          cd "$GITHUB_WORKSPACE"
          shasum -a 256 "shipsafe-${{ matrix.target }}.tar.gz" > "shipsafe-${{ matrix.target }}.tar.gz.sha256"
      - name: Package (windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          Compress-Archive -Path "target/${{ matrix.target }}/release/shipsafe.exe" -DestinationPath "shipsafe-${{ matrix.target }}.zip"
          $hash = (Get-FileHash "shipsafe-${{ matrix.target }}.zip" -Algorithm SHA256).Hash.ToLower()
          "$hash  shipsafe-${{ matrix.target }}.zip" | Out-File -Encoding ascii "shipsafe-${{ matrix.target }}.zip.sha256"
      - name: Upload artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: release-${{ matrix.target }}
          path: shipsafe-${{ matrix.target }}.*

  release:
    name: Create GitHub Release
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false
      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          pattern: release-*
          merge-multiple: true
      - name: Combined checksums
        run: cat ./*.sha256 > SHA256SUMS
      - name: Create Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG: ${{ inputs.tag || github.ref_name }}
        run: |
          if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
            gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
              shipsafe-*.tar.gz shipsafe-*.tar.gz.sha256 \
              shipsafe-*.zip shipsafe-*.zip.sha256 \
              SHA256SUMS
          else
            gh release create "$TAG" \
              --repo "$GITHUB_REPOSITORY" \
              --title "$TAG" \
              --generate-notes \
              shipsafe-*.tar.gz shipsafe-*.tar.gz.sha256 \
              shipsafe-*.zip shipsafe-*.zip.sha256 \
              SHA256SUMS
          fi

  docker:
    name: Push Docker image to ghcr.io
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false
      - name: Log in to ghcr.io
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
      - name: Build and push
        env:
          TAG: ${{ inputs.tag || github.ref_name }}
        run: |
          VERSION="${TAG#v}"
          MAJOR_MINOR="${VERSION%.*}"
          IMAGE="ghcr.io/baneido/shipsafe"
          docker build \
            --label org.opencontainers.image.revision="$GITHUB_SHA" \
            --label org.opencontainers.image.version="$VERSION" \
            -t "$IMAGE:$VERSION" -t "$IMAGE:$MAJOR_MINOR" -t "$IMAGE:latest" .
          docker push "$IMAGE:$VERSION"
          docker push "$IMAGE:$MAJOR_MINOR"
          docker push "$IMAGE:latest"
      - name: Smoke test image
        run: |
          docker run --rm ghcr.io/baneido/shipsafe:latest version
          docker run --rm ghcr.io/baneido/shipsafe:latest doctor

  crates:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    # Trusted Publishing: scope id-token to this job only (overrides top-level perms).
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false
      - name: Install Rust (stable)
        run: rustup toolchain install stable --profile minimal && rustup default stable
      - name: Authenticate to crates.io (Trusted Publishing / OIDC)
        id: auth
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
      - name: Publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish --locked

  homebrew:
    name: Update Homebrew formula
    needs: release
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false
      - name: Update baneido/homebrew-tap
        env:
          TAP_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
          TAG: ${{ inputs.tag || github.ref_name }}
        run: |
          if [ -z "$TAP_TOKEN" ]; then
            echo "::warning::TAP_GITHUB_TOKEN not set — skipping Homebrew formula update"
            exit 0
          fi
          VERSION="${TAG#v}"
          URL="https://github.com/baneido/shipsafe/archive/refs/tags/${TAG}.tar.gz"
          curl -fsSL "$URL" -o source.tar.gz
          SHA=$(sha256sum source.tar.gz | awk '{print $1}')

          git clone "https://x-access-token:${TAP_TOKEN}@github.com/baneido/homebrew-tap.git" tap
          mkdir -p tap/Formula
          sed -e "s|{{VERSION}}|${VERSION}|g" \
              -e "s|{{URL}}|${URL}|g" \
              -e "s|{{SHA256}}|${SHA}|g" \
              scripts/shipsafe.rb.tmpl > tap/Formula/shipsafe.rb
          cd tap
          git config user.name "shipsafe-release-bot"
          git config user.email "contact@baneido.com"
          git add Formula/shipsafe.rb
          git commit -m "shipsafe ${VERSION}" || echo "no changes"
          git push