mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-25 14:02:22 +02:00
101 lines
3.5 KiB
JavaScript
101 lines
3.5 KiB
JavaScript
// Hand-rolled notarization: `notarytool submit --wait` SIGBUSes on this macOS (its progress
|
|
// printer dies ~30s after upload, crash dumps 2026-07-13), so we submit WITHOUT --wait and poll
|
|
// `notarytool info` with short-lived calls that never reach the crashing code path. We staple
|
|
// here too because @electron/notarize (which normally staples) is out of the loop.
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const fs = require('fs');
|
|
const { execFile } = require('child_process');
|
|
const { promisify } = require('util');
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const POLL_INTERVAL_MS = 30 * 1000;
|
|
const POLL_DEADLINE_MS = 90 * 60 * 1000;
|
|
|
|
function delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function xcrun(args) {
|
|
const { stdout } = await execFileAsync('xcrun', args, { maxBuffer: 32 * 1024 * 1024 });
|
|
return stdout;
|
|
}
|
|
|
|
exports.default = async function notarizing(context) {
|
|
const { electronPlatformName, appOutDir } = context;
|
|
if (electronPlatformName !== 'darwin') return;
|
|
|
|
if (process.env.CSC_IDENTITY_AUTO_DISCOVERY === 'false') {
|
|
console.log('Skipping notarization (CSC_IDENTITY_AUTO_DISCOVERY=false)');
|
|
return;
|
|
}
|
|
|
|
// Prefer a stored notarytool keychain profile: an app-specific password in env
|
|
// silently 401s the day Apple rotates it; the keychain profile is durable.
|
|
const keychainProfile = process.env.APPLE_KEYCHAIN_PROFILE;
|
|
const hasEnvCreds =
|
|
process.env.APPLE_ID && process.env.APPLE_APP_SPECIFIC_PASSWORD && process.env.APPLE_TEAM_ID;
|
|
|
|
if (!keychainProfile && !hasEnvCreds) {
|
|
console.log('Skipping notarization (no APPLE_KEYCHAIN_PROFILE and no APPLE_ID/password/team)');
|
|
return;
|
|
}
|
|
|
|
const authArgs = keychainProfile
|
|
? ['--keychain-profile', keychainProfile]
|
|
: [
|
|
'--apple-id',
|
|
process.env.APPLE_ID,
|
|
'--password',
|
|
process.env.APPLE_APP_SPECIFIC_PASSWORD,
|
|
'--team-id',
|
|
process.env.APPLE_TEAM_ID,
|
|
];
|
|
|
|
const appName = context.packager.appInfo.productFilename;
|
|
const appPath = `${appOutDir}/${appName}.app`;
|
|
const zipPath = path.join(os.tmpdir(), `openswarm-notarize-${Date.now()}.zip`);
|
|
|
|
console.log(`Notarizing ${appPath} (submit, then poll)...`);
|
|
try {
|
|
await execFileAsync('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', appPath, zipPath]);
|
|
|
|
const submitted = JSON.parse(
|
|
await xcrun(['notarytool', 'submit', zipPath, ...authArgs, '--output-format', 'json'])
|
|
);
|
|
if (!submitted.id) {
|
|
throw new Error(`notarytool submit returned no id: ${JSON.stringify(submitted)}`);
|
|
}
|
|
console.log(`Notarization submitted (id ${submitted.id}); polling until Apple decides...`);
|
|
|
|
const deadline = Date.now() + POLL_DEADLINE_MS;
|
|
let status = 'In Progress';
|
|
while (status === 'In Progress' && Date.now() < deadline) {
|
|
await delay(POLL_INTERVAL_MS);
|
|
try {
|
|
const info = JSON.parse(
|
|
await xcrun(['notarytool', 'info', submitted.id, ...authArgs, '--output-format', 'json'])
|
|
);
|
|
status = info.status;
|
|
} catch {
|
|
// One flaky poll is fine; the next tick asks again.
|
|
}
|
|
}
|
|
|
|
if (status !== 'Accepted') {
|
|
let notaryLog = '';
|
|
try {
|
|
notaryLog = await xcrun(['notarytool', 'log', submitted.id, ...authArgs]);
|
|
} catch {}
|
|
throw new Error(`Notarization ended ${status} (id ${submitted.id})\n${notaryLog}`);
|
|
}
|
|
|
|
await xcrun(['stapler', 'staple', appPath]);
|
|
console.log('Notarization complete (Accepted + stapled).');
|
|
} finally {
|
|
try {
|
|
fs.unlinkSync(zipPath);
|
|
} catch {}
|
|
}
|
|
};
|