Skip to content

fix(insertion): harden text insertion with multi-tier clipboard verif… #26

fix(insertion): harden text insertion with multi-tier clipboard verif…

fix(insertion): harden text insertion with multi-tier clipboard verif… #26

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
env:
WHISPER_VERSION: "1.8.2"
jobs:
build-and-release:
runs-on: macos-26
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Install build tools
run: brew install xcodegen create-dmg
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Download whisper.cpp XCFramework
run: |
mkdir -p deps/whisper.cpp
curl -L -o whisper-xcframework.zip \
"https://github.com/ggml-org/whisper.cpp/releases/download/v$WHISPER_VERSION/whisper-v$WHISPER_VERSION-xcframework.zip"
unzip -q whisper-xcframework.zip -d deps/whisper.cpp
rm whisper-xcframework.zip
ls -la deps/whisper.cpp/build-apple/
echo "Downloaded whisper.cpp v$WHISPER_VERSION xcframework"
- name: Download models
run: make models
- name: Generate Xcode project
run: xcodegen generate
- name: Build with xcodebuild
run: |
xcodebuild -project OpenDictation.xcodeproj \
-scheme OpenDictation \
-configuration Release \
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
build
echo "Build complete"
ls -la "$RUNNER_TEMP/DerivedData/Build/Products/Release/"
- name: Inject version into built app
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
APP_PATH="$RUNNER_TEMP/DerivedData/Build/Products/Release/OpenDictation.app"
INFO_PLIST="$APP_PATH/Contents/Info.plist"
echo "Injecting version into built app..."
echo "Marketing version: $VERSION"
echo "Build number: $GITHUB_RUN_NUMBER"
# Set marketing version
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$INFO_PLIST"
# Set build number
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GITHUB_RUN_NUMBER" "$INFO_PLIST"
# Verify injection
echo "Verification:"
echo " CFBundleShortVersionString: $(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST")"
echo " CFBundleVersion: $(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFO_PLIST")"
echo "Version injection complete"
- name: Sign app bundle
run: |
APP_PATH="$RUNNER_TEMP/DerivedData/Build/Products/Release/OpenDictation.app"
# Sign the app (ad-hoc signing)
echo "Signing app bundle..."
codesign --deep --force -s - "$APP_PATH"
# Verify signature
echo "Verifying signature..."
codesign --verify --deep --strict "$APP_PATH"
echo "App signed successfully"
- name: Create DMG
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
DMG_NAME="OpenDictation.dmg"
APP_PATH="$RUNNER_TEMP/DerivedData/Build/Products/Release/OpenDictation.app"
create-dmg \
--volname "Open Dictation" \
--volicon "OpenDictation/Resources/DMG/VolumeIcon.icns" \
--background "OpenDictation/Resources/DMG/background.tiff" \
--window-pos 200 120 \
--window-size 500 400 \
--icon-size 70 \
--icon "OpenDictation.app" 100 200 \
--hide-extension "OpenDictation.app" \
--app-drop-link 350 200 \
"$RUNNER_TEMP/$DMG_NAME" \
"$APP_PATH"
echo "DMG_PATH=$RUNNER_TEMP/$DMG_NAME" >> $GITHUB_ENV
echo "DMG_NAME=$DMG_NAME" >> $GITHUB_ENV
ls -lh "$RUNNER_TEMP/$DMG_NAME"
- name: Sign update with Sparkle
env:
SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
if [ -z "$SPARKLE_KEY" ]; then
echo "SPARKLE_SIGNATURE=" >> $GITHUB_ENV
echo "Skipping Sparkle signing (no key configured)"
exit 0
fi
# Use deterministic path for Sparkle tools (following CodeEdit/TimeMachineStatus pattern)
SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin"
if [ ! -f "$SPARKLE_BIN/sign_update" ]; then
echo "Error: Sparkle sign_update not found at $SPARKLE_BIN"
echo "SPARKLE_SIGNATURE=" >> $GITHUB_ENV
exit 1
fi
echo "Using Sparkle tools from: $SPARKLE_BIN"
echo -n "$SPARKLE_KEY" > "$RUNNER_TEMP/sparkle_key"
SIGNATURE=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$DMG_PATH" | grep "sparkle:edSignature" | cut -d'"' -f2)
echo "SPARKLE_SIGNATURE=$SIGNATURE" >> $GITHUB_ENV
rm "$RUNNER_TEMP/sparkle_key"
- name: Get DMG size
run: |
DMG_SIZE=$(stat -f%z "$DMG_PATH")
echo "DMG_SIZE=$DMG_SIZE" >> $GITHUB_ENV
- name: Update appcast.xml
run: |
if [ -z "$SPARKLE_SIGNATURE" ]; then
echo "Skipping appcast update (no signature)"
exit 0
fi
export VERSION="${{ steps.version.outputs.VERSION }}"
export BUILD_NUMBER="${GITHUB_RUN_NUMBER}"
export PUB_DATE=$(date -R)
python3 << 'PYTHON_SCRIPT'
import xml.etree.ElementTree as ET
import os
version = os.environ['VERSION']
build = os.environ['BUILD_NUMBER']
pub_date = os.environ['PUB_DATE']
dmg_name = os.environ['DMG_NAME']
signature = os.environ['SPARKLE_SIGNATURE']
size = os.environ['DMG_SIZE']
# Register Sparkle namespace
ET.register_namespace('sparkle', 'http://www.andymatuschak.org/xml-namespaces/sparkle')
ET.register_namespace('dc', 'http://purl.org/dc/elements/1.1/')
tree = ET.parse('appcast.xml')
root = tree.getroot()
channel = root.find('channel')
# Create new item
item = ET.SubElement(channel, 'item')
ET.SubElement(item, 'title').text = f'Version {version}'
ET.SubElement(item, 'pubDate').text = pub_date
ET.SubElement(item, '{http://www.andymatuschak.org/xml-namespaces/sparkle}version').text = build
ET.SubElement(item, '{http://www.andymatuschak.org/xml-namespaces/sparkle}shortVersionString').text = version
ET.SubElement(item, '{http://www.andymatuschak.org/xml-namespaces/sparkle}minimumSystemVersion').text = '14.0'
enclosure = ET.SubElement(item, 'enclosure')
enclosure.set('url', f'https://github.com/kdcokenny/OpenDictation/releases/download/v{version}/{dmg_name}')
enclosure.set('{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature', signature)
enclosure.set('length', size)
enclosure.set('type', 'application/octet-stream')
tree.write('appcast.xml', encoding='utf-8', xml_declaration=True)
print(f'Updated appcast.xml with version {version}')
PYTHON_SCRIPT
- name: Commit appcast.xml
run: |
if [ -z "$SPARKLE_SIGNATURE" ]; then
echo "Skipping appcast commit (no signature)"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add appcast.xml
git commit -m "Update appcast for v${{ steps.version.outputs.VERSION }}"
git push origin HEAD:main
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: ${{ env.DMG_PATH }}
generate_release_notes: true
draft: false
prerelease: ${{ contains(steps.version.outputs.VERSION, 'alpha') || contains(steps.version.outputs.VERSION, 'beta') }}