Skip to content

Voice input

Voice input #32

name: Release macOS Apple Silicon
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-macos-arm64:
runs-on: macos-14
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
HAS_APPLE_SIGNING_CERTIFICATE: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 != '' && secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD != '' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Import Apple signing certificate
if: ${{ env.HAS_APPLE_SIGNING_CERTIFICATE == 'true' }}
env:
APPLE_SIGNING_CERTIFICATE_P12: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 }}
APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }}
shell: bash
run: |
set -euo pipefail
KEYCHAIN_PATH="${RUNNER_TEMP}/app-signing.keychain-db"
CERT_PATH="${RUNNER_TEMP}/app-signing-cert.p12"
echo "${APPLE_SIGNING_CERTIFICATE_P12}" | base64 --decode > "${CERT_PATH}"
security create-keychain -p "${APPLE_SIGNING_CERTIFICATE_PASSWORD}" "${KEYCHAIN_PATH}"
security set-keychain-settings -lut 21600 "${KEYCHAIN_PATH}"
security unlock-keychain -p "${APPLE_SIGNING_CERTIFICATE_PASSWORD}" "${KEYCHAIN_PATH}"
security import "${CERT_PATH}" \
-k "${KEYCHAIN_PATH}" \
-P "${APPLE_SIGNING_CERTIFICATE_PASSWORD}" \
-T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list \
-S apple-tool:,apple:,codesign: \
-s \
-k "${APPLE_SIGNING_CERTIFICATE_PASSWORD}" \
"${KEYCHAIN_PATH}"
EXISTING_KEYCHAINS="$(security list-keychains -d user | tr -d '"')"
security list-keychains -d user -s "${KEYCHAIN_PATH}" ${EXISTING_KEYCHAINS}
security default-keychain -d user -s "${KEYCHAIN_PATH}"
security find-identity -v -p codesigning "${KEYCHAIN_PATH}"
- name: Build macOS arm64 package
run: npm run make:mac:arm64
- name: Smoke test packaged app
shell: bash
run: |
set -euo pipefail
APP_PATH="$(find out -type d -path '*/Zero-darwin-arm64/Zero.app' | head -n 1)"
if [[ -z "${APP_PATH}" ]]; then
APP_PATH="$(find out -type d -name 'Zero.app' | head -n 1)"
fi
if [[ -z "${APP_PATH}" ]]; then
echo "Packaged Zero.app not found under out/." >&2
exit 1
fi
APP_BIN="${APP_PATH}/Contents/MacOS/Zero"
RESOURCES_DIR="${APP_PATH}/Contents/Resources"
CODEX_BIN="${RESOURCES_DIR}/app.asar.unpacked/node_modules/@zed-industries/codex-acp-darwin-arm64/bin/codex-acp"
CLAUDE_ENTRY="${RESOURCES_DIR}/app.asar.unpacked/node_modules/@zed-industries/claude-agent-acp/dist/index.js"
NODE_PTY_BIN="${RESOURCES_DIR}/app.asar.unpacked/node_modules/node-pty/build/Release/pty.node"
REQUIRED_PATHS=(
"${APP_BIN}"
"${RESOURCES_DIR}/app.asar"
"${CODEX_BIN}"
"${CLAUDE_ENTRY}"
"${NODE_PTY_BIN}"
)
for required_path in "${REQUIRED_PATHS[@]}"; do
if [[ ! -e "${required_path}" ]]; then
echo "Missing required runtime file: ${required_path}" >&2
exit 1
fi
done
if [[ ! -x "${APP_BIN}" ]]; then
echo "App binary is not executable: ${APP_BIN}" >&2
exit 1
fi
if [[ ! -x "${CODEX_BIN}" ]]; then
echo "Codex ACP binary is not executable: ${CODEX_BIN}" >&2
exit 1
fi
"${CODEX_BIN}" --help >/dev/null
ELECTRON_RUN_AS_NODE=1 "${APP_BIN}" -e "console.log('smoke-node-ok')" | grep -q "smoke-node-ok"
ELECTRON_RUN_AS_NODE=1 "${APP_BIN}" "${CLAUDE_ENTRY}" --help >/dev/null
if [[ -n "${APPLE_SIGNING_IDENTITY:-}" ]]; then
codesign --verify --deep --strict --verbose=2 "${APP_PATH}"
codesign --display --verbose=2 "${APP_PATH}" >/dev/null
spctl --assess --type execute --verbose=2 "${APP_PATH}"
fi
- name: Prepare release artifacts
id: prepare
shell: bash
run: |
set -euo pipefail
ZIP_PATH="$(find out/make -type f -path '*/zip/darwin/arm64/*.zip' | head -n 1)"
if [[ -z "${ZIP_PATH}" ]]; then
echo "No macOS arm64 zip artifact found under out/make." >&2
exit 1
fi
DMG_PATH="$(find out/make -type f -path '*/dmg/*arm64*.dmg' | head -n 1)"
if [[ -z "${DMG_PATH}" ]]; then
DMG_PATH="$(find out/make -type f -path '*/dmg/*.dmg' | head -n 1)"
fi
if [[ -z "${DMG_PATH}" ]]; then
echo "No macOS arm64 dmg artifact found under out/make; creating fallback DMG from packaged app."
APP_PATH="$(find out -type d -path '*/Zero-darwin-arm64/Zero.app' | head -n 1)"
if [[ -z "${APP_PATH}" ]]; then
APP_PATH="$(find out -type d -name 'Zero.app' | head -n 1)"
fi
if [[ -z "${APP_PATH}" ]]; then
echo "Packaged Zero.app not found under out/ for fallback DMG creation." >&2
exit 1
fi
FALLBACK_DMG_DIR="out/make/dmg-fallback"
FALLBACK_DMG_PATH="${FALLBACK_DMG_DIR}/Zero-darwin-arm64.dmg"
mkdir -p "${FALLBACK_DMG_DIR}"
hdiutil create \
-volname "Zero" \
-srcfolder "${APP_PATH}" \
-ov \
-format UDZO \
"${FALLBACK_DMG_PATH}"
DMG_PATH="${FALLBACK_DMG_PATH}"
fi
RELEASE_DIR="out/release-assets"
mkdir -p "${RELEASE_DIR}"
STABLE_ZIP="${RELEASE_DIR}/Zero-darwin-arm64.zip"
STABLE_DMG="${RELEASE_DIR}/Zero-darwin-arm64.dmg"
cp "${ZIP_PATH}" "${STABLE_ZIP}"
cp "${DMG_PATH}" "${STABLE_DMG}"
VERSION="$(node -p "require('./package.json').version")"
node scripts/generate-latest-mac-yml.mjs "${STABLE_ZIP}" "${VERSION}" latest-mac.yml
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "zip_file=${STABLE_ZIP}" >> "${GITHUB_OUTPUT}"
echo "dmg_file=${STABLE_DMG}" >> "${GITHUB_OUTPUT}"
echo "update_file=${RELEASE_DIR}/latest-mac.yml" >> "${GITHUB_OUTPUT}"
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
with:
name: zero-macos-arm64
path: out/release-assets
if-no-files-found: error
- name: Publish GitHub release assets
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
prerelease: false
make_latest: true
generate_release_notes: true
fail_on_unmatched_files: true
target_commitish: ${{ github.sha }}
files: |
${{ steps.prepare.outputs.dmg_file }}
${{ steps.prepare.outputs.zip_file }}
${{ steps.prepare.outputs.update_file }}