Skip to content

Release VoiceScribe

Release VoiceScribe #4

Workflow file for this run

name: Release VoiceScribe
on:
workflow_dispatch:
inputs:
version:
description: 'Version number (e.g., 1.0.0)'
required: true
type: string
jobs:
build-and-release:
runs-on: macos-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Update version in project
run: |
# Update MARKETING_VERSION in project.pbxproj
sed -i '' "s/MARKETING_VERSION = [^;]*/MARKETING_VERSION = ${{ inputs.version }}/g" VoiceScribe.xcodeproj/project.pbxproj
# Extract major version number for CURRENT_PROJECT_VERSION (e.g., 1.0.0 -> 1)
BUILD_NUMBER=$(echo "${{ inputs.version }}" | sed 's/[^0-9]*\([0-9]*\).*/\1/')
sed -i '' "s/CURRENT_PROJECT_VERSION = [^;]*/CURRENT_PROJECT_VERSION = $BUILD_NUMBER/g" VoiceScribe.xcodeproj/project.pbxproj
echo "Updated version to ${{ inputs.version }} (build $BUILD_NUMBER)"
- name: Install Apple certificate
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12
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 $RUNNER_TEMP/certificate.p12 \
-P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 \
-k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
rm -f $RUNNER_TEMP/certificate.p12
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
- name: Build app
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
xcodebuild clean build \
-project VoiceScribe.xcodeproj \
-scheme VoiceScribe \
-configuration Release \
-derivedDataPath ./build \
-arch x86_64 -arch arm64 \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Developer ID Application" \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \
OTHER_CODE_SIGN_FLAGS="--timestamp --options runtime"
- name: Verify code signature
run: |
APP_PATH="build/Build/Products/Release/VoiceScribe.app"
echo "Verifying code signature..."
codesign -vvv --deep --strict "$APP_PATH"
echo "Checking signature details..."
codesign -dvv "$APP_PATH"
echo "Checking entitlements..."
codesign -d --entitlements - "$APP_PATH"
- name: Notarize app
env:
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
run: |
APP_PATH="build/Build/Products/Release/VoiceScribe.app"
echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > $RUNNER_TEMP/AuthKey.p8
/usr/bin/ditto -c -k --keepParent "$APP_PATH" "$RUNNER_TEMP/VoiceScribe-notarize.zip"
echo "Submitting for notarization..."
SUBMIT_OUTPUT=$(xcrun notarytool submit "$RUNNER_TEMP/VoiceScribe-notarize.zip" \
--key "$RUNNER_TEMP/AuthKey.p8" \
--key-id "$APP_STORE_CONNECT_KEY_ID" \
--issuer "$APP_STORE_CONNECT_ISSUER_ID" \
--wait 2>&1)
echo "$SUBMIT_OUTPUT"
# Extract submission ID
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep "id:" | head -1 | awk '{print $2}')
# Check if notarization succeeded
if echo "$SUBMIT_OUTPUT" | grep -q "status: Accepted"; then
echo "Notarization succeeded!"
else
echo "Notarization failed. Fetching detailed log..."
xcrun notarytool log "$SUBMISSION_ID" \
--key "$RUNNER_TEMP/AuthKey.p8" \
--key-id "$APP_STORE_CONNECT_KEY_ID" \
--issuer "$APP_STORE_CONNECT_ISSUER_ID"
rm -f $RUNNER_TEMP/AuthKey.p8 $RUNNER_TEMP/VoiceScribe-notarize.zip
exit 1
fi
echo "Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
echo "Verifying..."
spctl -a -t exec -vv "$APP_PATH"
xcrun stapler validate "$APP_PATH"
rm -f $RUNNER_TEMP/AuthKey.p8 $RUNNER_TEMP/VoiceScribe-notarize.zip
- name: Create ZIP
run: |
cd build/Build/Products/Release
xattr -cr VoiceScribe.app
find VoiceScribe.app -name '._*' -delete
zip -r ../../../../VoiceScribe-${{ inputs.version }}.zip VoiceScribe.app
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ inputs.version }}
name: VoiceScribe v${{ inputs.version }}
body: |
## VoiceScribe v${{ inputs.version }}
### Installation
1. Download `VoiceScribe-${{ inputs.version }}.zip`
2. Unzip and move `VoiceScribe.app` to Applications
3. Double-click to open
This release is **signed and notarized** by Apple.
### Features
- **Global hotkey recording** - Press Option-Shift-Space to record from anywhere (customizable)
- **Dual transcription engines** - Choose between privacy-focused local [WhisperKit](https://github.com/argmaxinc/WhisperKit) (on-device, Apple Silicon only) or cloud-based OpenAI Transcription
- **Multiple AI models** - Download and switch between WhisperKit models (Base/Small/Medium) or select OpenAI models (Whisper V2/GPT-4o/GPT-4o Mini)
- **AI-powered enhancement** - Optional post-processing to add perfect punctuation, capitalization, and formatting
- **Smart paste** - Transcriptions paste directly into your active app
- **Transcription history** - Review past transcriptions anytime
- **Secure storage** - API keys encrypted in macOS Keychain
### Requirements
- macOS 14.0 (Sonoma) or later
- Apple Silicon (M-series) Mac for local WhisperKit transcription and MLX enhancement
- Intel Macs supported with OpenAI Transcription API
### Privacy
- All audio processing happens on-device with [WhisperKit](https://github.com/argmaxinc/WhisperKit) (no network transmission)
- OpenAI mode sends audio to their servers - review [OpenAI's Privacy Policy](https://openai.com/policies/privacy-policy)
- API keys encrypted in macOS Keychain with device-only access
- No telemetry or analytics
files: VoiceScribe-${{ inputs.version }}.zip
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup keychain
if: always()
run: |
if [[ -f "${KEYCHAIN_PATH:-}" ]]; then
security delete-keychain $KEYCHAIN_PATH || true
fi