Skip to content

Build / Notarize macOS & Windows → Release #17

Build / Notarize macOS & Windows → Release

Build / Notarize macOS & Windows → Release #17

name: Build / Notarize macOS & Windows → Release
on:
workflow_dispatch:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env: # shared across jobs
APP_NAME: Localforge
NODE_VERSION: '20'
jobs:
# ────────────────────────────────────────────────────────────────────────────────
# 1. Build matrix – macOS + Windows run in parallel
# ────────────────────────────────────────────────────────────────────────────────
build:
strategy:
matrix:
include:
- os: macos-14
arch: arm64
readable_arch: ARM64
package_script: npm run package:mac-arm64
app_dir_suffix: arm64
installer_dir_suffix: arm64
artifact_name_prefix: localforge-osx-arm64
create_dmg_script: npm run create-dmg:arm64
- os: macos-14
arch: x64 # electron-packager uses x64 for Intel
readable_arch: Intel
package_script: npm run package:mac-intel
app_dir_suffix: x64
installer_dir_suffix: intel
artifact_name_prefix: localforge-osx-intel
create_dmg_script: npm run create-dmg:intel
- os: windows-latest
arch: x64 # Assuming x64 for Windows
readable_arch: Windows
package_script: npm run package:win
app_dir_suffix: win32-x64 # from your original script
installer_dir_suffix: win # For consistency, though not used for DMG
artifact_name_prefix: localforge-windows
runs-on: ${{ matrix.os }}
steps:
# ---------- common ----------
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20' # Or your required version
cache: 'npm'
- name: Install dependencies
run: npm ci
# ---------- macOS-only : import cert & env ----------
- name: Import macOS code-sign cert
if: runner.os == 'macOS'
id: import_cert # Give this step an ID to reference its output
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_P12 }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
# ---------- build / package ----------
- name: Package Electron OSX App (${{ matrix.readable_arch }})
if: runner.os == 'macOS'
env:
MACOS_SIGNING_IDENTITY_FROM_ACTION: ${{ steps.import_cert.outputs.signing-identity }}
run: |
echo "Packaging for Mac..."
${{ matrix.package_script }}
- name: Package Electron Windows App
if: runner.os == 'Windows'
run: |
echo "Packaging for Windows..."
${{ matrix.package_script }}
# ---------- macOS-only : notarize, staple, dmg ----------
- name: Notarise & staple (macOS)
if: runner.os == 'macOS'
env:
APPLE_ID: ${{ secrets.APPLE_ID_EMAIL }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
APP_PATH="dist/Localforge-darwin-${{ matrix.app_dir_suffix }}/Localforge.app"
ZIP_PATH="dist/Localforge-mac-${{ matrix.arch }}.zip" # Arch specific zip for notarization
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ZIP_PATH"
echo "Submitting to notarisation…"
xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--output-format json --wait
xcrun stapler staple --verbose "$APP_PATH"
echo "Creating DMG using script: ${{ matrix.create_dmg_script }}"
${{ matrix.create_dmg_script }}
- name: Rename DMG so filenames are unique
if: runner.os == 'macOS'
run: |
DMG_DIR="dist/installers-${{ matrix.installer_dir_suffix }}"
mv "$DMG_DIR/Localforge.dmg" \
"$DMG_DIR/Localforge-${{ matrix.installer_dir_suffix }}.dmg"
# ---------- Windows portable build via electron-packager --------
- name: Package Windows (portable)
if: runner.os == 'Windows'
run: ${{ matrix.package_script }}
# Optional signing ------------------------------------------------- (120$, maybe later)
# - name: Sign main executable
# if: runner.os == 'Windows' && env.CSC_LINK != ''
# env:
# CSC_LINK: ${{ secrets.WIN_CODESIGN_PFX }}
# CSC_KEY_PASSWORD: ${{ secrets.WIN_CODESIGN_PFX_PASSWORD }}
# run: |
# echo "%CSC_LINK%" | base64 -d > code.pfx
# # Sign the main EXE
# "/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe" sign ^
# /f code.pfx ^
# /p "%CSC_KEY_PASSWORD%" ^
# /tr http://timestamp.digicert.com ^
# /td sha256 ^
# /fd sha256 ^
# "dist/Localforge-win32-x64/Localforge.exe"
# Zip the folder so users download one file -----------------------
- name: Zip portable build
if: runner.os == 'Windows'
run: |
7z a -r dist/Localforge-win32-x64.zip dist/Localforge-${{ matrix.app_dir_suffix }}\*
# ───────────── macOS artifact (.dmg) ─────────────
- name: Upload macOS artifact (${{ matrix.readable_arch }})
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name_prefix }}-installer
path: dist/installers-${{ matrix.installer_dir_suffix }}/*.dmg
if-no-files-found: error
# ───────────── Windows artifact (portable .zip or installer) ─────────────
- name: Upload Windows artifact
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name_prefix }}-installer
path: dist/Localforge-win32-x64.zip
if-no-files-found: error
# ────────────────────────────────────────────────────────────────────────────────
# 2. Release job – waits for both installers, attaches to one GitHub Release
# ────────────────────────────────────────────────────────────────────────────────
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout tags
uses: actions/checkout@v4
# we need the tags in order to figure out the latest one
with: { fetch-depth: 0 }
# ---------- decide which tag to use ----------
- name: Compute tag name
id: tag
run: |
# If this run was triggered by `git push --tag`, use that tag
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
TAG_NAME="${GITHUB_REF#refs/tags/}"
else
# Manual run → grab *latest* annotated or lightweight tag
git fetch --tags --force
TAG_NAME="$(git describe --tags --abbrev=0)"
fi
echo "Using tag: $TAG_NAME"
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
- name: download artifacts
uses: actions/download-artifact@v4
with:
pattern: '*-installer' # grabs macOS-installer & Windows-installer
path: dist-artifacts
# publish / update the release
- name: Publish GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
dist-artifacts/**/Localforge-*.dmg
dist-artifacts/localforge-windows-installer/*.zip
draft: true