From 233c8ac08567bd5eee8ba74c12caae51b55922f0 Mon Sep 17 00:00:00 2001 From: 4pmtong Date: Sat, 17 Jan 2026 21:40:34 +0800 Subject: [PATCH] fix notarize --- config/before-sign.cjs | 55 ++++++- electron-builder.json | 2 +- scripts/test-signing.js | 318 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 scripts/test-signing.js diff --git a/config/before-sign.cjs b/config/before-sign.cjs index 3863cb58..f685ff1b 100644 --- a/config/before-sign.cjs +++ b/config/before-sign.cjs @@ -18,7 +18,7 @@ exports.default = async function afterPack(context) { return; } - console.log('๐Ÿงน Cleaning invalid symlinks before signing...'); + console.log('๐Ÿงน Cleaning invalid symlinks and cache directories before signing...'); const resourcesPath = path.join(appPath, 'Contents', 'Resources'); const prebuiltPath = path.join(resourcesPath, 'prebuilt'); @@ -27,6 +27,59 @@ exports.default = async function afterPack(context) { return; } + // Remove .npm-cache directories (should not be packaged) + function removeNpmCache(dir) { + if (!fs.existsSync(dir)) { + return; + } + + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + try { + if (entry.name === '.npm-cache' && entry.isDirectory()) { + console.log(`Removing .npm-cache directory: ${fullPath}`); + fs.rmSync(fullPath, { recursive: true, force: true }); + } else if (entry.isDirectory()) { + removeNpmCache(fullPath); + } + } catch (error) { + // Ignore errors + } + } + } catch (error) { + // Ignore errors + } + } + + removeNpmCache(prebuiltPath); + + // Remove flac-mac binary (uses outdated SDK, causes notarization issues) + const venvLibPath = path.join(prebuiltPath, 'venv', 'lib'); + if (fs.existsSync(venvLibPath)) { + try { + const entries = fs.readdirSync(venvLibPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory() && entry.name.startsWith('python')) { + const flacMacPath = path.join(venvLibPath, entry.name, 'site-packages', 'speech_recognition', 'flac-mac'); + if (fs.existsSync(flacMacPath)) { + console.log(`Removing flac-mac binary (outdated SDK): ${flacMacPath}`); + try { + fs.unlinkSync(flacMacPath); + } catch (error) { + console.warn(`Warning: Could not remove flac-mac: ${error.message}`); + } + } + } + } + } catch (error) { + // Ignore errors + } + } + // Clean Python symlinks in venv/bin const venvBinDir = path.join(prebuiltPath, 'venv', 'bin'); if (fs.existsSync(venvBinDir)) { diff --git a/electron-builder.json b/electron-builder.json index ca6c46b7..61000693 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -29,7 +29,7 @@ { "from": "resources/prebuilt", "to": "prebuilt", - "filter": ["**/*", "!cache/**/*"] + "filter": ["**/*", "!cache/**/*", "!**/.npm-cache/**/*"] } ], "protocols": [ diff --git a/scripts/test-signing.js b/scripts/test-signing.js new file mode 100644 index 00000000..0ed8171e --- /dev/null +++ b/scripts/test-signing.js @@ -0,0 +1,318 @@ +#!/usr/bin/env node +/** + * Test script for macOS code signing + * This script helps debug signing issues by: + * 1. Checking for invalid symlinks + * 2. Verifying bundle structure + * 3. Testing codesign verification (without actual signing) + */ + +import fs from 'fs'; +import path from 'path'; +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..'); + +const RELEASE_DIR = path.join(projectRoot, 'release'); +const APP_BUNDLE_PATTERN = /Eigent\.app$/; + +/** + * Find the app bundle in release directory + */ +function findAppBundle() { + if (!fs.existsSync(RELEASE_DIR)) { + console.log('โŒ Release directory does not exist. Please build the app first.'); + console.log(' Run: npm run build:mac'); + return null; + } + + const entries = fs.readdirSync(RELEASE_DIR, { withFileTypes: true }); + + for (const entry of entries) { + if (entry.isDirectory() && entry.name.match(APP_BUNDLE_PATTERN)) { + return path.join(RELEASE_DIR, entry.name); + } + + // Check subdirectories (e.g., mac-arm64/Eigent.app) + if (entry.isDirectory()) { + const subDir = path.join(RELEASE_DIR, entry.name); + const subEntries = fs.readdirSync(subDir, { withFileTypes: true }); + for (const subEntry of subEntries) { + if (subEntry.isDirectory() && subEntry.name.match(APP_BUNDLE_PATTERN)) { + return path.join(subDir, subEntry.name); + } + } + } + } + + return null; +} + +/** + * Check for invalid symlinks in the app bundle + */ +function checkSymlinks(bundlePath) { + console.log('\n๐Ÿ” Checking for invalid symlinks...'); + + const invalidSymlinks = []; + + function checkDir(dir) { + if (!fs.existsSync(dir)) return; + + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + try { + if (entry.isSymbolicLink()) { + const target = fs.readlinkSync(fullPath); + const resolvedPath = path.resolve(path.dirname(fullPath), target); + const bundlePathResolved = path.resolve(bundlePath); + + if (!fs.existsSync(resolvedPath)) { + invalidSymlinks.push({ + path: fullPath, + target: target, + reason: 'Target does not exist' + }); + } else if (!resolvedPath.startsWith(bundlePathResolved)) { + invalidSymlinks.push({ + path: fullPath, + target: target, + reason: 'Target is outside bundle' + }); + } + } else if (entry.isDirectory()) { + // Skip certain directories + if (entry.name === 'node_modules' || entry.name === '__pycache__') { + continue; + } + checkDir(fullPath); + } + } catch (error) { + // Ignore errors for individual files + } + } + } catch (error) { + // Ignore errors for directories + } + } + + checkDir(bundlePath); + + if (invalidSymlinks.length > 0) { + console.log(`โŒ Found ${invalidSymlinks.length} invalid symlink(s):`); + invalidSymlinks.forEach(symlink => { + console.log(` - ${symlink.path}`); + console.log(` โ†’ ${symlink.target}`); + console.log(` Reason: ${symlink.reason}`); + }); + return false; + } else { + console.log('โœ… No invalid symlinks found'); + return true; + } +} + +/** + * Check bundle structure + */ +function checkBundleStructure(bundlePath) { + console.log('\n๐Ÿ” Checking bundle structure...'); + + const requiredPaths = [ + 'Contents/Info.plist', + 'Contents/MacOS', + 'Contents/Resources' + ]; + + let allValid = true; + + for (const requiredPath of requiredPaths) { + const fullPath = path.join(bundlePath, requiredPath); + if (!fs.existsSync(fullPath)) { + console.log(`โŒ Missing: ${requiredPath}`); + allValid = false; + } else { + console.log(`โœ… Found: ${requiredPath}`); + } + } + + return allValid; +} + +/** + * Check entitlements file + */ +function checkEntitlements() { + console.log('\n๐Ÿ” Checking entitlements...'); + + const entitlementsPath = path.join(projectRoot, 'entitlements.mac.plist'); + + if (!fs.existsSync(entitlementsPath)) { + console.log('โŒ entitlements.mac.plist not found'); + return false; + } + + console.log('โœ… entitlements.mac.plist exists'); + + // Try to validate plist format + try { + execSync(`plutil -lint "${entitlementsPath}"`, { stdio: 'pipe' }); + console.log('โœ… Entitlements file is valid'); + return true; + } catch (error) { + console.log('โš ๏ธ Could not validate entitlements file format'); + return true; // Don't fail on this + } +} + +/** + * Test codesign verification (if app is already signed) + */ +function testCodesignVerification(bundlePath) { + console.log('\n๐Ÿ” Testing codesign verification...'); + + try { + // First check if app is signed at all + const signCheck = execSync( + `codesign -dv "${bundlePath}" 2>&1 || true`, + { encoding: 'utf-8' } + ); + + if (signCheck.includes('code object is not signed')) { + console.log('โ„น๏ธ App is not signed (this is expected for local testing without certificates)'); + return true; + } + + // If signed, verify it + const output = execSync( + `codesign --verify --deep --strict --verbose=2 "${bundlePath}" 2>&1 || true`, + { encoding: 'utf-8' } + ); + + if (output.includes('valid on disk') || output.includes('satisfies its Designated Requirement')) { + console.log('โœ… App is properly signed'); + return true; + } else if (output.includes('code has no resources but signature indicates they must be present')) { + // This error often means the app was signed incorrectly or needs to be re-signed + console.log('โš ๏ธ Codesign verification issue detected:'); + console.log(' This usually means the app needs to be re-signed.'); + console.log(' In CI/CD, this should be fixed by electron-builder during signing.'); + console.log(' For local testing, you can ignore this if you don\'t have signing certificates.'); + return true; // Don't fail local testing + } else { + console.log('โš ๏ธ Codesign verification issues:'); + console.log(output); + return true; // Don't fail local testing + } + } catch (error) { + console.log('โ„น๏ธ Could not verify signature (app may not be signed - expected for local testing)'); + return true; // Don't fail on this for local testing + } +} + +/** + * Check for common signing issues + */ +function checkCommonIssues(bundlePath) { + console.log('\n๐Ÿ” Checking for common signing issues...'); + + const issues = []; + + // Check for files that might cause issues + const problematicPatterns = [ + /\.DS_Store$/, + /\.git$/, + /node_modules/, + ]; + + function scanDir(dir, depth = 0) { + if (depth > 10) return; // Prevent infinite recursion + + try { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + const relativePath = path.relative(bundlePath, fullPath); + + // Check for problematic files + for (const pattern of problematicPatterns) { + if (pattern.test(relativePath)) { + issues.push(`Found ${relativePath} (may cause signing issues)`); + } + } + + if (entry.isDirectory() && !entry.isSymbolicLink()) { + scanDir(fullPath, depth + 1); + } + } + } catch (error) { + // Ignore errors + } + } + + scanDir(bundlePath); + + if (issues.length > 0) { + console.log('โš ๏ธ Potential issues found:'); + issues.forEach(issue => console.log(` - ${issue}`)); + } else { + console.log('โœ… No obvious issues found'); + } + + return issues.length === 0; +} + +/** + * Main function + */ +function main() { + console.log('๐Ÿงช macOS Code Signing Test Script\n'); + + const appBundle = findAppBundle(); + + if (!appBundle) { + console.log('\n๐Ÿ’ก To build the app for testing:'); + console.log(' npm run build:mac'); + process.exit(1); + } + + console.log(`๐Ÿ“ฆ Found app bundle: ${appBundle}\n`); + + const results = { + symlinks: checkSymlinks(appBundle), + structure: checkBundleStructure(appBundle), + entitlements: checkEntitlements(), + codesign: testCodesignVerification(appBundle), + commonIssues: checkCommonIssues(appBundle), + }; + + console.log('\n๐Ÿ“Š Summary:'); + console.log(` Symlinks: ${results.symlinks ? 'โœ…' : 'โŒ'}`); + console.log(` Structure: ${results.structure ? 'โœ…' : 'โŒ'}`); + console.log(` Entitlements: ${results.entitlements ? 'โœ…' : 'โŒ'}`); + console.log(` Codesign: ${results.codesign ? 'โœ…' : 'โš ๏ธ'}`); + console.log(` Common Issues: ${results.commonIssues ? 'โœ…' : 'โš ๏ธ'}`); + + // For local testing, only fail on critical issues (symlinks, structure, entitlements) + const criticalChecks = [results.symlinks, results.structure, results.entitlements]; + const allCriticalPassed = criticalChecks.every(r => r); + + if (allCriticalPassed) { + console.log('\nโœ… Critical checks passed! The app should be ready for signing.'); + console.log(' Note: Codesign warnings are expected for local testing without certificates.'); + console.log(' The app will be properly signed in CI/CD with the provided certificates.'); + } else { + console.log('\nโŒ Critical checks failed. Please fix the issues above before signing.'); + process.exit(1); + } +} + +main();