@electron-forge/core
Version:
A complete tool for building modern Electron applications
221 lines (194 loc) • 6.51 kB
text/typescript
import { spawn, SpawnOptions } from 'child_process';
import { getElectronVersion, listrCompatibleRebuildHook } from '@electron-forge/core-utils';
import { ElectronProcess, ForgeArch, ForgeListrTask, ForgePlatform, ResolvedForgeConfig, StartOptions } from '@electron-forge/shared-types';
import chalk from 'chalk';
import debug from 'debug';
import { Listr } from 'listr2';
import locateElectronExecutable from '../util/electron-executable';
import getForgeConfig from '../util/forge-config';
import { getHookListrTasks, runHook } from '../util/hook';
import { readMutatedPackageJson } from '../util/read-package-json';
import resolveDir from '../util/resolve-dir';
const d = debug('electron-forge:start');
export { StartOptions };
type StartContext = {
dir: string;
forgeConfig: ResolvedForgeConfig;
packageJSON: any;
spawned: ElectronProcess;
};
export default async ({
dir: providedDir = process.cwd(),
appPath = '.',
interactive = false,
enableLogging = false,
args = [],
runAsNode = false,
inspect = false,
inspectBrk = false,
}: StartOptions): Promise<ElectronProcess> => {
const platform = process.env.npm_config_platform || process.platform;
const arch = process.env.npm_config_arch || process.arch;
const listrOptions = {
concurrent: false,
rendererOptions: {
collapseErrors: false,
},
rendererSilent: !interactive,
rendererFallback: Boolean(process.env.DEBUG && process.env.DEBUG.includes('electron-forge')),
};
const runner = new Listr<StartContext>(
[
{
title: 'Locating application',
task: async (ctx) => {
const resolvedDir = await resolveDir(providedDir);
if (!resolvedDir) {
throw new Error('Failed to locate startable Electron application');
}
ctx.dir = resolvedDir;
},
},
{
title: 'Loading configuration',
task: async (ctx) => {
const { dir } = ctx;
ctx.forgeConfig = await getForgeConfig(dir);
ctx.packageJSON = await readMutatedPackageJson(dir, ctx.forgeConfig);
if (!ctx.packageJSON.version) {
throw new Error(`Please set your application's 'version' in '${dir}/package.json'.`);
}
},
},
{
title: 'Preparing native dependencies',
task: async ({ dir, forgeConfig, packageJSON }, task) => {
await listrCompatibleRebuildHook(
dir,
await getElectronVersion(dir, packageJSON),
platform as ForgePlatform,
arch as ForgeArch,
forgeConfig.rebuildConfig,
task as ForgeListrTask<never>
);
},
options: {
persistentOutput: true,
bottomBar: Infinity,
showTimer: true,
},
},
{
title: `Running ${chalk.yellow('generateAssets')} hook`,
task: async ({ forgeConfig }, task) => {
return task.newListr(await getHookListrTasks(forgeConfig, 'generateAssets', platform, arch));
},
},
],
listrOptions
);
await runner.run();
const { dir, forgeConfig, packageJSON } = runner.ctx;
let lastSpawned: ElectronProcess | null = null;
const forgeSpawn = async () => {
let electronExecPath: string | null = null;
// If a plugin has taken over the start command let's stop here
let spawnedPluginChild = await forgeConfig.pluginInterface.overrideStartLogic({
dir,
appPath,
interactive,
enableLogging,
args,
runAsNode,
inspect,
inspectBrk,
});
if (typeof spawnedPluginChild === 'object' && 'tasks' in spawnedPluginChild) {
const innerRunner = new Listr<never>([], listrOptions);
for (const task of spawnedPluginChild.tasks) {
innerRunner.add(task);
}
await innerRunner.run();
spawnedPluginChild = spawnedPluginChild.result;
}
let prefixArgs: string[] = [];
if (typeof spawnedPluginChild === 'string') {
electronExecPath = spawnedPluginChild;
} else if (Array.isArray(spawnedPluginChild)) {
[electronExecPath, ...prefixArgs] = spawnedPluginChild;
} else if (spawnedPluginChild) {
await runHook(forgeConfig, 'postStart', spawnedPluginChild);
return spawnedPluginChild;
}
if (!electronExecPath) {
electronExecPath = await locateElectronExecutable(dir, packageJSON);
}
d('Electron binary path:', electronExecPath);
const spawnOpts = {
cwd: dir,
stdio: 'inherit',
env: {
...process.env,
...(enableLogging
? {
ELECTRON_ENABLE_LOGGING: 'true',
ELECTRON_ENABLE_STACK_DUMPING: 'true',
}
: {}),
} as NodeJS.ProcessEnv,
};
if (runAsNode) {
spawnOpts.env.ELECTRON_RUN_AS_NODE = 'true';
} else {
delete spawnOpts.env.ELECTRON_RUN_AS_NODE;
}
if (inspect) {
args = ['--inspect' as string | number].concat(args);
}
if (inspectBrk) {
args = ['--inspect-brk' as string | number].concat(args);
}
const spawned = spawn(
electronExecPath!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
prefixArgs.concat([appPath]).concat(args as string[]),
spawnOpts as SpawnOptions
) as ElectronProcess;
await runHook(forgeConfig, 'postStart', spawned);
return spawned;
};
const forgeSpawnWrapper = async () => {
const spawned = await forgeSpawn();
// When the child app is closed we should stop listening for stdin
if (spawned) {
if (interactive && process.stdin.isPaused()) {
process.stdin.resume();
}
spawned.on('exit', () => {
if (spawned.restarted) {
return;
}
if (interactive && !process.stdin.isPaused()) {
process.stdin.pause();
}
});
} else if (interactive && !process.stdin.isPaused()) {
process.stdin.pause();
}
lastSpawned = spawned;
return lastSpawned;
};
if (interactive) {
process.stdin.on('data', async (data) => {
if (data.toString().trim() === 'rs' && lastSpawned) {
console.info(chalk.cyan('\nRestarting App\n'));
lastSpawned.restarted = true;
lastSpawned.kill('SIGTERM');
lastSpawned.emit('restarted', await forgeSpawnWrapper());
}
});
process.stdin.resume();
}
const spawned = await forgeSpawnWrapper();
if (interactive) console.log('');
return spawned;
};