161 lines
4.4 KiB
JavaScript
161 lines
4.4 KiB
JavaScript
import { spawn, spawnSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const appRoot = path.resolve(__dirname, '..');
|
|
const brandName = 'TrueGrowth';
|
|
const bundleIdentifier = 'ai.truegrowth.desktop.dev';
|
|
const cacheRoot = path.join(appRoot, '.truegrowth-desktop');
|
|
const brandedAppPath = path.join(cacheRoot, `${brandName}.app`);
|
|
const mainEntry = path.join(appRoot, 'apps/electron/main.mjs');
|
|
const cacheFormatVersion = '2';
|
|
const appVersion =
|
|
JSON.parse(fs.readFileSync(path.join(appRoot, 'package.json'), 'utf8'))
|
|
.version || '0.0.0';
|
|
|
|
const isMac = process.platform === 'darwin';
|
|
const prepareOnly = process.argv.includes('--prepare-only');
|
|
|
|
function runPlistBuddy(args) {
|
|
const result = spawnSync('/usr/libexec/PlistBuddy', args, {
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
if (result.status !== 0) {
|
|
const details = result.stderr || result.stdout || args.join(' ');
|
|
throw new Error(`Failed to update Electron app metadata: ${details}`);
|
|
}
|
|
}
|
|
|
|
function setPlistValue(plistPath, key, value) {
|
|
const escapedValue = String(value).replace(/"/g, '\\"');
|
|
const setCommand = `Set :${key} "${escapedValue}"`;
|
|
const addCommand = `Add :${key} string "${escapedValue}"`;
|
|
const setResult = spawnSync(
|
|
'/usr/libexec/PlistBuddy',
|
|
['-c', setCommand, plistPath],
|
|
{
|
|
encoding: 'utf8',
|
|
}
|
|
);
|
|
|
|
if (setResult.status === 0) {
|
|
return;
|
|
}
|
|
|
|
runPlistBuddy(['-c', addCommand, plistPath]);
|
|
}
|
|
|
|
function signMacApp(appPath) {
|
|
const verifyResult = spawnSync(
|
|
'/usr/bin/codesign',
|
|
['--verify', '--deep', '--strict', appPath],
|
|
{
|
|
encoding: 'utf8',
|
|
}
|
|
);
|
|
|
|
if (verifyResult.status === 0) {
|
|
return;
|
|
}
|
|
|
|
const signResult = spawnSync(
|
|
'/usr/bin/codesign',
|
|
['--force', '--deep', '--sign', '-', appPath],
|
|
{
|
|
encoding: 'utf8',
|
|
}
|
|
);
|
|
|
|
if (signResult.status !== 0) {
|
|
const details = signResult.stderr || signResult.stdout || appPath;
|
|
throw new Error(`Failed to sign branded Electron app: ${details}`);
|
|
}
|
|
}
|
|
|
|
function getElectronExecutable() {
|
|
const result = spawnSync(process.execPath, ['-p', "require('electron')"], {
|
|
cwd: appRoot,
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
if (result.status !== 0) {
|
|
throw new Error(result.stderr || 'Unable to resolve Electron executable.');
|
|
}
|
|
|
|
return result.stdout.trim();
|
|
}
|
|
|
|
function ensureBrandedMacApp(electronExecutable) {
|
|
const sourceAppPath = electronExecutable.slice(
|
|
0,
|
|
electronExecutable.indexOf('.app') + '.app'.length
|
|
);
|
|
const sourceInfoPath = path.join(sourceAppPath, 'Contents/Info.plist');
|
|
const sourceVersion = `${fs.statSync(sourceInfoPath).mtimeMs.toFixed(
|
|
0
|
|
)}:${cacheFormatVersion}`;
|
|
const stampPath = path.join(cacheRoot, 'electron-source-version.txt');
|
|
const cachedVersion = fs.existsSync(stampPath)
|
|
? fs.readFileSync(stampPath, 'utf8')
|
|
: '';
|
|
|
|
if (!fs.existsSync(brandedAppPath) || cachedVersion !== sourceVersion) {
|
|
fs.rmSync(brandedAppPath, { recursive: true, force: true });
|
|
fs.mkdirSync(cacheRoot, { recursive: true });
|
|
fs.cpSync(sourceAppPath, brandedAppPath, {
|
|
recursive: true,
|
|
verbatimSymlinks: true,
|
|
});
|
|
fs.writeFileSync(stampPath, sourceVersion);
|
|
}
|
|
|
|
const infoPath = path.join(brandedAppPath, 'Contents/Info.plist');
|
|
setPlistValue(infoPath, 'CFBundleName', brandName);
|
|
setPlistValue(infoPath, 'CFBundleDisplayName', brandName);
|
|
setPlistValue(infoPath, 'CFBundleIdentifier', bundleIdentifier);
|
|
setPlistValue(infoPath, 'CFBundleShortVersionString', appVersion);
|
|
setPlistValue(infoPath, 'CFBundleVersion', appVersion);
|
|
setPlistValue(
|
|
infoPath,
|
|
'LSApplicationCategoryType',
|
|
'public.app-category.productivity'
|
|
);
|
|
signMacApp(brandedAppPath);
|
|
|
|
return path.join(brandedAppPath, 'Contents/MacOS/Electron');
|
|
}
|
|
|
|
function launch(executablePath) {
|
|
const child = spawn(executablePath, [mainEntry], {
|
|
cwd: appRoot,
|
|
env: {
|
|
...process.env,
|
|
TRUEGROWTH_BRANDED_ELECTRON_DEV: '1',
|
|
},
|
|
stdio: 'inherit',
|
|
});
|
|
|
|
child.on('exit', (code, signal) => {
|
|
if (typeof code === 'number') {
|
|
process.exit(code);
|
|
}
|
|
if (signal) {
|
|
process.kill(process.pid, signal);
|
|
}
|
|
});
|
|
}
|
|
|
|
const electronExecutable = getElectronExecutable();
|
|
const executablePath = isMac
|
|
? ensureBrandedMacApp(electronExecutable)
|
|
: electronExecutable;
|
|
|
|
if (prepareOnly) {
|
|
console.log(executablePath);
|
|
} else {
|
|
launch(executablePath);
|
|
}
|