Skip to content

[mobile] Adjust release workflow to include apk app #54

[mobile] Adjust release workflow to include apk app

[mobile] Adjust release workflow to include apk app #54

name: release / desktop publish
# Triggered when a desktop release-prepare PR is merged. The merge commit's
# message begins with "release(desktop): vX.Y.Z" — that's how we filter.
#
# Order: assert secrets → import signing identity → install JS deps + Pods →
# stamp CFBundleVersion → xcodebuild archive + export → notarize + staple →
# create-dmg → move tag onto merge commit → create GitHub Release with .dmg.
on:
push:
branches:
- main
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g. 0.0.1). Must already be committed in Info.plist / package.json on the chosen ref.'
required: true
type: string
permissions:
contents: write
jobs:
publish:
# On push: `contains` (not `startsWith`) so this fires regardless of merge
# strategy — default merge commits put the PR title in the body, while
# squash/rebase put it on the first line.
# On workflow_dispatch: always run (version comes from the input).
if: "${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, 'release(desktop): v') }}"
runs-on: macos-15
timeout-minutes: 60
env:
APP_NAME: entangle
SCHEME: entangle-macOS
WORKSPACE: apps/desktop/macos/entangle.xcworkspace
ARCHIVE_PATH: build/entangle.xcarchive
EXPORT_PATH: build/export
EXPORT_OPTIONS: apps/desktop/macos/ExportOptions.plist
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Switch to Xcode 26.2
run: sudo xcode-select --switch /Applications/Xcode_26.2.app
- name: Resolve version
id: parse
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
INPUT_VERSION: ${{ inputs.version }}
# workflow_dispatch → use the input; push → scan the merge commit
# message (PR title may be on any line, depending on merge strategy).
run: |
if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
else
VERSION=$(printf '%s' "$COMMIT_MSG" | sed -nE 's/.*release\(desktop\): v([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n1)
fi
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Could not determine a valid x.y.z version" >&2
echo " input: $INPUT_VERSION" >&2
echo " commit: $COMMIT_MSG" >&2
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Assert required secrets
env:
MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
missing=()
for s in MACOS_CERT_P12_BASE64 MACOS_CERT_PASSWORD APPLE_ID APPLE_APP_PASSWORD APPLE_TEAM_ID; do
if [ -z "${!s}" ]; then missing+=("$s"); fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "Missing required repo secrets: ${missing[*]}" >&2
echo "See RELEASING.md for how to set them up." >&2
exit 1
fi
- name: Setup Node + pnpm
uses: actions/setup-node@v4
with:
node-version: "20"
- run: corepack enable && corepack prepare pnpm@10.33.0 --activate
- name: Install monorepo deps
run: pnpm install --frozen-lockfile
- name: Install JS deps
working-directory: apps/desktop
run: pnpm install --ignore-workspace --frozen-lockfile
- name: Setup Ruby + Bundler
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
bundler-cache: true
working-directory: apps/desktop
- name: Install Pods
working-directory: apps/desktop/macos
run: bundle exec pod install --verbose
- name: Import signing certificate
env:
MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
run: |
KEYCHAIN=build.keychain
KEYCHAIN_PWD=$(uuidgen)
echo "$MACOS_CERT_P12_BASE64" | base64 --decode > /tmp/cert.p12
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security import /tmp/cert.p12 -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" \
-T /usr/bin/codesign -T /usr/bin/productbuild -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
rm /tmp/cert.p12
- name: xcodebuild archive
# Signing config (Developer ID identity, hardened runtime, timestamp,
# team) lives in entangle.xcodeproj/project.pbxproj Release config so
# local archives match CI. Nothing to override here.
run: |
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-archivePath "$ARCHIVE_PATH" \
-destination 'generic/platform=macOS' \
archive
- name: xcodebuild exportArchive
run: |
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS"
- name: Notarize
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
APP_PATH="$EXPORT_PATH/$APP_NAME.app"
ZIP_PATH="$EXPORT_PATH/$APP_NAME.zip"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
JSON=$(xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--output-format json \
--wait)
echo "$JSON"
ID=$(echo "$JSON" | jq -r .id)
STATUS=$(echo "$JSON" | jq -r .status)
# Always fetch the log — even Accepted submissions can have warnings
# worth seeing, and on Invalid the log is the only useful signal.
echo "::group::notarytool log ($ID)"
xcrun notarytool log "$ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" || true
echo "::endgroup::"
if [ "$STATUS" != "Accepted" ]; then
echo "Notarization status: $STATUS — see the log group above for details." >&2
exit 1
fi
xcrun stapler staple "$APP_PATH"
- name: Build DMG
env:
VERSION: ${{ steps.parse.outputs.version }}
run: |
brew install create-dmg
DMG_PATH="$EXPORT_PATH/${APP_NAME}-${VERSION}.dmg"
create-dmg \
--volname "${APP_NAME} ${VERSION}" \
--window-size 600 400 \
--icon-size 100 \
--app-drop-link 450 200 \
"$DMG_PATH" \
"$EXPORT_PATH/${APP_NAME}.app"
# Re-staple the DMG itself for offline Gatekeeper checks.
xcrun stapler staple "$DMG_PATH" || true
echo "DMG=$DMG_PATH" >> "$GITHUB_ENV"
- name: Move tag onto merge commit
env:
VERSION: ${{ steps.parse.outputs.version }}
# The original tag pointed at the developer's pre-bump commit; we move
# it so the GitHub Release reflects the actually-released tree.
run: |
git config user.name "entangle-release-bot"
git config user.email "release-bot@users.noreply.github.com"
git tag -f "v${VERSION}-desktop"
git push -f origin "v${VERSION}-desktop"
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.parse.outputs.version }}
run: |
gh release create "v${VERSION}-desktop" "$DMG" \
--title "Desktop v${VERSION}" \
--generate-notes \
--verify-tag