devclean-cli 0.5.0

Audit and safely remove rebuildable development artifacts
Documentation
name: Release

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

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  verify:
    name: Verify release gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
      - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
      - run: cargo fmt --all -- --check
      - run: cargo test --all-features --locked
      - run: cargo clippy --all-targets --all-features --locked -- -D warnings

  build:
    name: Build ${{ matrix.target }}
    needs: verify
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
      id-token: write
      attestations: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            asset: devclean-linux-x86_64
          - os: macos-latest
            target: aarch64-apple-darwin
            asset: devclean-macos-arm64
          - os: macos-latest
            target: x86_64-apple-darwin
            asset: devclean-macos-x86_64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            asset: devclean-windows-x86_64
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
      - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          key: ${{ matrix.target }}
      - run: cargo build --release --locked --target ${{ matrix.target }}

      - name: Package Unix archive
        if: runner.os != 'Windows'
        shell: bash
        run: |
          mkdir package
          cp "target/${{ matrix.target }}/release/devclean" package/
          cp README.md LICENSE package/
          tar -C package -czf "${{ matrix.asset }}.tar.gz" .

      - name: Package Windows archive
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Path package
          Copy-Item "target/${{ matrix.target }}/release/devclean.exe" package/
          Copy-Item README.md,LICENSE package/
          Compress-Archive -Path package/* -DestinationPath "${{ matrix.asset }}.zip"

      - uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
        with:
          subject-path: ${{ matrix.asset }}.*

      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: ${{ matrix.asset }}
          path: ${{ matrix.asset }}.*
          if-no-files-found: error

  macos-app:
    name: Build macOS menu bar app
    needs: [verify, build]
    runs-on: macos-latest
    permissions:
      contents: read
      id-token: write
      attestations: write
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          pattern: devclean-macos-*
          path: cli-artifacts
      - name: Create universal Rust helper
        shell: bash
        run: |
          mkdir -p cli-arm64 cli-x86_64 target/universal-apple-darwin/release
          tar -xzf cli-artifacts/devclean-macos-arm64/devclean-macos-arm64.tar.gz -C cli-arm64
          tar -xzf cli-artifacts/devclean-macos-x86_64/devclean-macos-x86_64.tar.gz -C cli-x86_64
          lipo -create cli-arm64/devclean cli-x86_64/devclean \
            -output target/universal-apple-darwin/release/devclean
          test "$(lipo -archs target/universal-apple-darwin/release/devclean)" = "x86_64 arm64" \
            -o "$(lipo -archs target/universal-apple-darwin/release/devclean)" = "arm64 x86_64"
      - name: Test Swift package
        run: swift test --package-path apps/macos
      - name: Import Developer ID certificate
        env:
          MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
        shell: bash
        run: |
          test -n "$MACOS_CERTIFICATE_P12"
          certificate_path="$RUNNER_TEMP/developer-id.p12"
          keychain_path="$RUNNER_TEMP/devclean-signing.keychain-db"
          keychain_password="$(openssl rand -hex 32)"
          printf '%s' "$MACOS_CERTIFICATE_P12" | base64 --decode > "$certificate_path"
          chmod 600 "$certificate_path"
          security create-keychain -p "$keychain_password" "$keychain_path"
          security set-keychain-settings -lut 21600 "$keychain_path"
          security unlock-keychain -p "$keychain_password" "$keychain_path"
          security import "$certificate_path" -k "$keychain_path" \
            -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          security set-key-partition-list -S apple-tool:,apple:,codesign: \
            -s -k "$keychain_password" "$keychain_path"
          security list-keychains -d user -s "$keychain_path"
          identity_hash="$(security find-identity -v -p codesigning "$keychain_path" | \
            awk '/Developer ID Application: LE ANH TUAN \(VGQU7EVXZV\)/ {print $2; exit}')"
          test -n "$identity_hash"
          echo "CODE_SIGN_IDENTITY=$identity_hash" >> "$GITHUB_ENV"
          echo "SIGNING_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
      - name: Build universal app
        env:
          UNIVERSAL: "1"
          HELPER_EXECUTABLE: ${{ github.workspace }}/target/universal-apple-darwin/release/devclean
          SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
        run: apps/macos/scripts/build-app.sh
      - name: Verify universal app
        run: |
          codesign --verify --deep --strict --verbose=2 dist/DevCleaner.app
          test -f dist/DevCleaner.app/Contents/Resources/AppIcon.icns
          lipo -archs dist/DevCleaner.app/Contents/MacOS/DevcleanMenuBar | grep arm64
          lipo -archs dist/DevCleaner.app/Contents/MacOS/DevcleanMenuBar | grep x86_64
          lipo -archs dist/DevCleaner.app/Contents/Helpers/devclean | grep arm64
          lipo -archs dist/DevCleaner.app/Contents/Helpers/devclean | grep x86_64
          codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'Authority=Developer ID Application'
          codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'TeamIdentifier=VGQU7EVXZV'
          codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'Runtime Version='
      - name: Notarize and staple app
        env:
          APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
        shell: bash
        run: |
          test -n "$APPLE_API_KEY"
          api_key_path="$RUNNER_TEMP/AuthKey.p8"
          submission="$RUNNER_TEMP/devclean-notarization.zip"
          result="$RUNNER_TEMP/notarization-result.json"
          printf '%s' "$APPLE_API_KEY" > "$api_key_path"
          chmod 600 "$api_key_path"
          ditto -c -k --keepParent dist/DevCleaner.app "$submission"
          xcrun notarytool submit "$submission" \
            --key "$api_key_path" \
            --key-id "$APPLE_API_KEY_ID" \
            --issuer "$APPLE_API_ISSUER" \
            --wait --output-format json > "$result"
          jq -e '.status == "Accepted"' "$result"
          xcrun stapler staple dist/DevCleaner.app
          xcrun stapler validate dist/DevCleaner.app
          codesign --verify --deep --strict --verbose=2 dist/DevCleaner.app
          spctl --assess --type execute --verbose=4 dist/DevCleaner.app
          rm -f "$api_key_path" "$submission" "$result"
      - name: Package app bundle
        run: ditto -c -k --sequesterRsrc --keepParent dist/DevCleaner.app devclean-menubar-macos-universal.zip
      - uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
        with:
          subject-path: devclean-menubar-macos-universal.zip
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
        with:
          name: devclean-menubar-macos-universal
          path: devclean-menubar-macos-universal.zip
          if-no-files-found: error
      - name: Delete temporary signing keychain
        if: always()
        run: security delete-keychain "$SIGNING_KEYCHAIN" || true

  publish:
    name: Publish GitHub release
    needs: [build, macos-app]
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      attestations: write
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
        with:
          path: dist
          merge-multiple: true
      - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
        with:
          path: .
          format: cyclonedx-json
          output-file: dist/devclean-sbom.cdx.json
          upload-artifact: false
      - uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3
        with:
          subject-path: dist/devclean-*.*
          sbom-path: dist/devclean-sbom.cdx.json
      - name: Package skill and checksums
        shell: bash
        run: |
          tar -czf dist/dev-disk-cleaner-skill.tar.gz skills/dev-disk-cleaner
          cd dist
          sha256sum * > SHA256SUMS
      - uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3
        with:
          name: devclean ${{ github.ref_name }}
          body_path: RELEASE_NOTES.md
          make_latest: true
          files: dist/*