Skip to content

macos release

macos release #12

Workflow file for this run

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