macos release #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: macos release | |
| on: | |
| workflow_call: | |
| inputs: | |
| version: | |
| required: true | |
| type: string | |
| secrets: | |
| APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64: | |
| required: true | |
| APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD: | |
| required: true | |
| APPLE_NOTARY_APPLE_ID: | |
| required: true | |
| APPLE_NOTARY_APP_SPECIFIC_PASSWORD: | |
| required: true | |
| APPLE_NOTARY_TEAM_ID: | |
| required: true | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: Package version to release. Defaults to packages/cli/package.json. | |
| required: false | |
| type: string | |
| dry_run: | |
| description: Build and audit artifacts without notarizing, publishing, or pushing the tap. | |
| required: true | |
| type: boolean | |
| default: true | |
| concurrency: | |
| group: macos-release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-macos: | |
| name: build macOS ${{ matrix.arch }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: arm64 | |
| runner: macos-15 | |
| - arch: x64 | |
| runner: macos-15-intel | |
| env: | |
| INFLOW_NOTARY_PROFILE: inflow-notary | |
| INFLOW_RELEASE_CODESIGN_IDENTITY: 'Developer ID Application: Jarwin, Inc. (B96U57DTR2)' | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.15.0 | |
| cache: pnpm | |
| - name: Install | |
| run: pnpm install --frozen-lockfile | |
| - name: Prepare pinned Argon2 source | |
| run: | | |
| node scripts/prepare-argon2-source.mjs "$RUNNER_TEMP/argon2-20190702" | |
| echo "INFLOW_ARGON2_SOURCE_DIR=$RUNNER_TEMP/argon2-20190702" >> "$GITHUB_ENV" | |
| - name: Resolve release metadata | |
| id: metadata | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| package_version="$(node -p "JSON.parse(require('node:fs').readFileSync('packages/cli/package.json', 'utf8')).version")" | |
| requested_version="${{ inputs.version }}" | |
| if [[ -n "$requested_version" && "$requested_version" != "$package_version" ]]; then | |
| echo "::error::Requested version $requested_version does not match packages/cli/package.json version $package_version" | |
| exit 1 | |
| fi | |
| if [[ '${{ github.event_name }}' == 'workflow_dispatch' && '${{ inputs.dry_run }}' != 'true' ]]; then | |
| echo "::error::Production macOS artifacts must be staged by the native release workflow" | |
| exit 1 | |
| fi | |
| if [[ '${{ github.event_name }}' == 'workflow_call' && "$GITHUB_REF" != "refs/tags/v$package_version" ]]; then | |
| echo "::error::Production macOS releases must run from refs/tags/v$package_version" | |
| exit 1 | |
| fi | |
| echo "version=$package_version" >> "$GITHUB_OUTPUT" | |
| echo "artifact=dist/macos/inflow-$package_version-darwin-${{ matrix.arch }}.zip" >> "$GITHUB_OUTPUT" | |
| echo "artifact_name=inflow-macos-${{ matrix.arch }}-$package_version" >> "$GITHUB_OUTPUT" | |
| - name: Configure Apple signing | |
| if: github.event_name == 'workflow_call' | |
| shell: bash | |
| env: | |
| CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }} | |
| CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }} | |
| NOTARY_APPLE_ID: ${{ secrets.APPLE_NOTARY_APPLE_ID }} | |
| NOTARY_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_NOTARY_APP_SPECIFIC_PASSWORD }} | |
| NOTARY_TEAM_ID: ${{ secrets.APPLE_NOTARY_TEAM_ID }} | |
| run: | | |
| set -euo pipefail | |
| required=( | |
| CERTIFICATE_BASE64 | |
| CERTIFICATE_PASSWORD | |
| NOTARY_APPLE_ID | |
| NOTARY_APP_SPECIFIC_PASSWORD | |
| NOTARY_TEAM_ID | |
| ) | |
| for name in "${required[@]}"; do | |
| if [[ -z "${!name}" ]]; then | |
| echo "::error::$name is required for a real macOS release" | |
| exit 1 | |
| fi | |
| done | |
| certificate_path="$RUNNER_TEMP/inflow-developer-id.p12" | |
| keychain_path="$RUNNER_TEMP/inflow-signing.keychain-db" | |
| keychain_password="$(openssl rand -hex 24)" | |
| printf '%s' "$CERTIFICATE_BASE64" | base64 --decode > "$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" -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path" | |
| security list-keychains -d user -s "$keychain_path" | |
| security default-keychain -s "$keychain_path" | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain_path" | |
| security find-identity -v -p codesigning "$keychain_path" | |
| xcrun notarytool store-credentials "$INFLOW_NOTARY_PROFILE" \ | |
| --apple-id "$NOTARY_APPLE_ID" \ | |
| --team-id "$NOTARY_TEAM_ID" \ | |
| --password "$NOTARY_APP_SPECIFIC_PASSWORD" \ | |
| --keychain "$keychain_path" | |
| - name: Build macOS artifact | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then | |
| INFLOW_CODESIGN_IDENTITY=- pnpm build:macos-app | |
| else | |
| INFLOW_CODESIGN_IDENTITY="$INFLOW_RELEASE_CODESIGN_IDENTITY" pnpm build:macos-release | |
| fi | |
| - name: Verify macOS artifact | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -f '${{ steps.metadata.outputs.artifact }}' | |
| dist/macos/bin/inflow --version | |
| codesign --verify --deep --strict --verbose=2 dist/macos/InFlow.app | |
| if [[ '${{ github.event_name }}' == 'workflow_call' ]]; then | |
| xcrun stapler validate dist/macos/InFlow.app | |
| spctl --assess --type execute --verbose=2 dist/macos/InFlow.app | |
| fi | |
| - name: Write artifact checksum | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| artifact='${{ steps.metadata.outputs.artifact }}' | |
| checksum="${artifact}.sha256" | |
| shasum -a 256 "$artifact" | sed "s# .*# $(basename "$artifact")#" > "$checksum" | |
| (cd "$(dirname "$artifact")" && shasum -a 256 -c "$(basename "$checksum")") | |
| - name: Upload macOS artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ steps.metadata.outputs.artifact_name }} | |
| path: | | |
| ${{ steps.metadata.outputs.artifact }} | |
| ${{ steps.metadata.outputs.artifact }}.sha256 | |
| dist/macos/manifest.json | |
| if-no-files-found: error | |
| stage-macos: | |
| name: stage macOS artifacts | |
| runs-on: macos-15 | |
| needs: build-macos | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.15.0 | |
| cache: pnpm | |
| - name: Install | |
| run: pnpm install --frozen-lockfile | |
| - name: Resolve release metadata | |
| id: metadata | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| package_version="$(node -p "JSON.parse(require('node:fs').readFileSync('packages/cli/package.json', 'utf8')).version")" | |
| requested_version="${{ inputs.version }}" | |
| if [[ -n "$requested_version" && "$requested_version" != "$package_version" ]]; then | |
| echo "::error::Requested version $requested_version does not match packages/cli/package.json version $package_version" | |
| exit 1 | |
| fi | |
| echo "version=$package_version" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$package_version" >> "$GITHUB_OUTPUT" | |
| - name: Download macOS artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: inflow-macos-*-${{ steps.metadata.outputs.version }} | |
| path: dist/release-artifacts | |
| - name: Resolve downloaded artifacts | |
| id: artifacts | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version='${{ steps.metadata.outputs.version }}' | |
| arm64="$(find dist/release-artifacts -name "inflow-$version-darwin-arm64.zip" -print -quit)" | |
| x64="$(find dist/release-artifacts -name "inflow-$version-darwin-x64.zip" -print -quit)" | |
| arm64_checksum="$(find dist/release-artifacts -name "inflow-$version-darwin-arm64.zip.sha256" -print -quit)" | |
| x64_checksum="$(find dist/release-artifacts -name "inflow-$version-darwin-x64.zip.sha256" -print -quit)" | |
| if [[ -z "$arm64" || -z "$x64" || -z "$arm64_checksum" || -z "$x64_checksum" ]]; then | |
| echo "::error::Expected both darwin-arm64 and darwin-x64 artifacts with checksum files" | |
| find dist/release-artifacts -maxdepth 4 -type f | |
| exit 1 | |
| fi | |
| echo "arm64=$arm64" >> "$GITHUB_OUTPUT" | |
| echo "x64=$x64" >> "$GITHUB_OUTPUT" | |
| echo "arm64_checksum=$arm64_checksum" >> "$GITHUB_OUTPUT" | |
| echo "x64_checksum=$x64_checksum" >> "$GITHUB_OUTPUT" | |
| (cd "$(dirname "$arm64")" && shasum -a 256 -c "$(basename "$arm64_checksum")") | |
| (cd "$(dirname "$x64")" && shasum -a 256 -c "$(basename "$x64_checksum")") | |
| - name: Render Homebrew Cask | |
| env: | |
| INFLOW_HOMEBREW_ARM64_ARTIFACT: ${{ steps.artifacts.outputs.arm64 }} | |
| INFLOW_HOMEBREW_X64_ARTIFACT: ${{ steps.artifacts.outputs.x64 }} | |
| run: pnpm build:homebrew-cask | |
| - name: Audit Homebrew Cask | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ruby -c dist/homebrew/Casks/inflow.rb | |
| tap="$RUNNER_TEMP/homebrew-tap-audit" | |
| mkdir -p "$tap/Casks" | |
| cp dist/homebrew/Casks/inflow.rb "$tap/Casks/inflow.rb" | |
| git -C "$tap" init | |
| git -C "$tap" config user.name github-actions | |
| git -C "$tap" config user.email github-actions@github.com | |
| git -C "$tap" add Casks/inflow.rb | |
| git -C "$tap" commit -m "Add inflow cask" | |
| brew tap inflowpayai/tap-audit "$tap" | |
| HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_DEVELOPER=1 brew audit --cask --strict --skip-style inflowpayai/tap-audit/inflow | |
| brew untap inflowpayai/tap-audit | |
| - name: Upload dry-run artifacts | |
| if: github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: inflow-macos-release-dry-run-${{ steps.metadata.outputs.version }} | |
| path: | | |
| dist/release-artifacts/**/*.zip | |
| dist/release-artifacts/**/*.zip.sha256 | |
| dist/release-artifacts/**/manifest.json | |
| dist/homebrew/Casks/inflow.rb | |
| if-no-files-found: error | |
| - name: Stage production artifacts | |
| if: github.event_name == 'workflow_call' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: inflow-native-macos-${{ steps.metadata.outputs.version }} | |
| path: | | |
| dist/release-artifacts/**/*.zip | |
| dist/release-artifacts/**/*.zip.sha256 | |
| dist/homebrew/Casks/inflow.rb | |
| if-no-files-found: error | |
| retention-days: 7 |