From 153868fb87b2ad297729ec0dca5bef388aae1d46 Mon Sep 17 00:00:00 2001 From: sanasol Date: Sun, 1 Feb 2026 03:00:47 +0100 Subject: [PATCH] fix(macos): improve notarization with timeout and graceful failure Changes: - Add 30 minute timeout for notarization (fail fast) - Add SKIP_NOTARIZE=true env var to skip notarization entirely - Don't fail build if notarization fails (app still code-signed) - Add NOTARIZE_FAIL_ON_ERROR=true to fail build on notarization error - Add forceCodeSigning, strictVerify, type=distribution to mac config - Disable electron-builder built-in notarize (using custom script) This prevents CI from hanging forever waiting for Apple's notarization service and reduces wasted GitHub Actions minutes. Co-Authored-By: Claude Opus 4.5 --- package.json | 6 +++++- scripts/notarize.js | 44 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index e67bc6f..303dd53 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,11 @@ "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "build/entitlements.mac.plist", - "entitlementsInherit": "build/entitlements.mac.plist" + "entitlementsInherit": "build/entitlements.mac.plist", + "forceCodeSigning": true, + "strictVerify": true, + "type": "distribution", + "notarize": false }, "afterSign": "scripts/notarize.js", "nsis": { diff --git a/scripts/notarize.js b/scripts/notarize.js index 2ae3890..7fb4035 100644 --- a/scripts/notarize.js +++ b/scripts/notarize.js @@ -11,6 +11,18 @@ try { const path = require('path'); +// Timeout for notarization (30 minutes max) +const NOTARIZE_TIMEOUT_MS = 30 * 60 * 1000; + +function withTimeout(promise, ms, message) { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(message)), ms) + ) + ]); +} + exports.default = async function notarizing(context) { console.log('[Notarize] afterSign hook called'); console.log('[Notarize] Context:', JSON.stringify({ @@ -27,6 +39,12 @@ exports.default = async function notarizing(context) { return; } + // Check if notarization is disabled via env var + if (process.env.SKIP_NOTARIZE === 'true') { + console.log('[Notarize] Skipping: SKIP_NOTARIZE=true'); + return; + } + // Check credentials const hasAppleId = !!process.env.APPLE_ID; const hasPassword = !!process.env.APPLE_APP_SPECIFIC_PASSWORD; @@ -45,18 +63,30 @@ exports.default = async function notarizing(context) { console.log('[Notarize] Starting notarization...'); console.log('[Notarize] App path:', appPath); console.log('[Notarize] Team ID:', process.env.APPLE_TEAM_ID); + console.log('[Notarize] Timeout:', NOTARIZE_TIMEOUT_MS / 1000, 'seconds'); try { - await notarize({ - appPath, - appleId: process.env.APPLE_ID, - appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD, - teamId: process.env.APPLE_TEAM_ID, - }); + await withTimeout( + notarize({ + appPath, + appleId: process.env.APPLE_ID, + appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD, + teamId: process.env.APPLE_TEAM_ID, + }), + NOTARIZE_TIMEOUT_MS, + `Notarization timed out after ${NOTARIZE_TIMEOUT_MS / 1000} seconds` + ); console.log('[Notarize] Notarization complete!'); } catch (error) { console.error('[Notarize] Notarization failed:', error.message); - console.error('[Notarize] Full error:', error); + + // Don't fail the build if notarization times out or fails + // The app will still be code-signed, just not notarized + if (process.env.NOTARIZE_FAIL_ON_ERROR !== 'true') { + console.warn('[Notarize] Continuing build without notarization (set NOTARIZE_FAIL_ON_ERROR=true to fail)'); + return; + } + throw error; } };