fix notarize

This commit is contained in:
4pmtong 2026-01-17 21:40:34 +08:00
parent b5849338ab
commit 233c8ac085
3 changed files with 373 additions and 2 deletions

View file

@ -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)) {

View file

@ -29,7 +29,7 @@
{
"from": "resources/prebuilt",
"to": "prebuilt",
"filter": ["**/*", "!cache/**/*"]
"filter": ["**/*", "!cache/**/*", "!**/.npm-cache/**/*"]
}
],
"protocols": [

318
scripts/test-signing.js Normal file
View file

@ -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();