Skip to content

Build, Sign, Notarize & Publish (non-MAS) #35

Build, Sign, Notarize & Publish (non-MAS)

Build, Sign, Notarize & Publish (non-MAS) #35

Workflow file for this run

name: Build, Sign, Notarize & Publish (non-MAS)
on:
workflow_dispatch:
inputs:
create_release:
description: 'Create a GitHub Release with the DMG'
type: boolean
default: true
release_tag:
description: 'Release tag (e.g. v1.0.33). Leave empty to auto-generate from package.json'
type: string
default: ''
release_notes:
description: 'Release notes. Leave empty to auto-read from CHANGELOG.md'
type: string
default: ''
jobs:
build-and-notarize:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Import certificate to keychain
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo -n "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode -o $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 "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Import Developer ID G2 intermediate certificate
curl -sO https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
security import DeveloperIDG2CA.cer -k $KEYCHAIN_PATH || true
rm DeveloperIDG2CA.cer
- name: Build app
run: yarn make
- name: Install create-dmg
run: brew install create-dmg
- name: Sign, notarize, and create DMG
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: sh ./sign-notarize.sh
- name: Get version from package.json
id: version
run: echo "version=$(node -p 'require("./package.json").version')" >> $GITHUB_OUTPUT
- name: Extract release notes from CHANGELOG.md
id: changelog
if: ${{ inputs.create_release && inputs.release_notes == '' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
# Find the last published release version to aggregate all unreleased entries
LAST_TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName' 2>/dev/null || echo "")
LAST_VER=$(echo "$LAST_TAG" | sed 's/^v//')
# Extract all changelog sections from current version down to (but not including) last release
if [ -n "$LAST_VER" ] && [ "$LAST_VER" != "$VERSION" ]; then
NOTES=$(awk -v cur="## ${VERSION}" -v prev="## ${LAST_VER}" \
'$0 == cur {found=1} found && $0 == prev {exit} found' CHANGELOG.md | sed '/^$/d')
else
NOTES=$(awk -v ver="## ${VERSION}" '$0 == ver {found=1; next} found && /^## / {exit} found' CHANGELOG.md | sed '/^$/d')
fi
if [ -z "$NOTES" ]; then
NOTES="Non-App Store notarized build v${VERSION}"
fi
# Write to file to avoid escaping issues
echo "$NOTES" > /tmp/release-notes.txt
echo "notes_file=/tmp/release-notes.txt" >> $GITHUB_OUTPUT
- name: Create signed ZIP for auto-updater
run: |
VERSION="${{ steps.version.outputs.version }}"
cd ./out/CodeV-darwin-arm64
zip -r -y "../CodeV-darwin-arm64-${VERSION}.zip" CodeV.app
echo "Created signed zip: ./out/CodeV-darwin-arm64-${VERSION}.zip"
- name: Upload DMG artifact
uses: actions/upload-artifact@v4
with:
name: CodeV-${{ steps.version.outputs.version }}.dmg
path: ./out/CodeV.dmg
- name: Upload ZIP artifact
uses: actions/upload-artifact@v4
with:
name: CodeV-${{ steps.version.outputs.version }}.zip
path: ./out/CodeV-darwin-arm64-${{ steps.version.outputs.version }}.zip
- name: Create GitHub Release
if: ${{ inputs.create_release }}
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ inputs.release_tag }}"
if [ -z "$TAG" ]; then
TAG="v${{ steps.version.outputs.version }}"
fi
VERSION="${{ steps.version.outputs.version }}"
ZIP_FILE="./out/CodeV-darwin-arm64-${VERSION}.zip"
NOTES="${{ inputs.release_notes }}"
if [ -z "$NOTES" ] && [ -f /tmp/release-notes.txt ]; then
gh release create "$TAG" ./out/CodeV.dmg "$ZIP_FILE" \
--title "$TAG" \
--notes-file /tmp/release-notes.txt
else
gh release create "$TAG" ./out/CodeV.dmg "$ZIP_FILE" \
--title "$TAG" \
--notes "${NOTES:-Non-App Store notarized build}"
fi
- name: Cleanup keychain
if: always()
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true