UNPKG

@electron-forge/core

Version:

A complete tool for building modern Electron applications

314 lines (287 loc) 9.63 kB
import { spawn, SpawnOptions } from 'node:child_process'; import readline from 'node:readline'; import { getElectronVersion, listrCompatibleRebuildHook, } from '@electron-forge/core-utils'; import { ElectronProcess, ForgeArch, ForgeListrOptions, ForgeListrTaskFn, ForgePlatform, ResolvedForgeConfig, StartOptions, } from '@electron-forge/shared-types'; import { autoTrace, delayTraceTillSignal } from '@electron-forge/tracer'; import chalk from 'chalk'; import debug from 'debug'; import { Listr, PRESET_TIMER } 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 autoTrace( { name: 'start()', category: '@electron-forge/core' }, async ( childTrace, { 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: ForgeListrOptions<StartContext> = { concurrent: false, registerSignalListeners: false, // Don't re-render on SIGINT rendererOptions: { collapseErrors: false, collapseSubtasks: false, }, silentRendererCondition: !interactive, fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI), }; const runner = new Listr<StartContext>( [ { title: 'Locating application', task: childTrace<Parameters<ForgeListrTaskFn<StartContext>>>( { name: 'locate-application', category: '@electron-forge/core' }, 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: childTrace<Parameters<ForgeListrTaskFn<StartContext>>>( { name: 'load-forge-config', category: '@electron-forge/core' }, 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: childTrace<Parameters<ForgeListrTaskFn<StartContext>>>( { name: 'prepare-native-dependencies', category: '@electron-forge/core', }, async (_, { dir, forgeConfig, packageJSON }, task) => { await listrCompatibleRebuildHook( dir, await getElectronVersion(dir, packageJSON), platform as ForgePlatform, arch as ForgeArch, forgeConfig.rebuildConfig, task as any, ); }, ), rendererOptions: { persistentOutput: true, bottomBar: Infinity, timer: { ...PRESET_TIMER }, }, }, { title: `Running ${chalk.yellow('generateAssets')} hook`, task: childTrace<Parameters<ForgeListrTaskFn<StartContext>>>( { name: 'run-generateAssets-hook', category: '@electron-forge/core', }, async (childTrace, { forgeConfig }, task) => { return delayTraceTillSignal( childTrace, task.newListr( await getHookListrTasks( childTrace, forgeConfig, 'generateAssets', platform, arch, ), ), 'run', ); }, ), }, { title: `Running ${chalk.yellow('preStart')} hook`, task: childTrace<Parameters<ForgeListrTaskFn<StartContext>>>( { name: 'run-preStart-hook', category: '@electron-forge/core' }, async (childTrace, { forgeConfig }, task) => { return delayTraceTillSignal( childTrace, task.newListr( await getHookListrTasks(childTrace, forgeConfig, 'preStart'), ), 'run', ); }, ), }, { task: (_ctx, task) => { task.title = `${chalk.dim(`Launched Electron app. Type`)} ${chalk.bold('rs')} ${chalk.dim(`in terminal to restart main process.`)}`; }, }, ], 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 as ForgeListrOptions<never>, ); 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', (data) => { if (data.toString().trim() === 'rs' && lastSpawned) { readline.moveCursor(process.stdout, 0, -1); readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); console.info( `${chalk.green('✔ ')}${chalk.dim('Restarting Electron app')}`, ); lastSpawned.restarted = true; lastSpawned.on('exit', async () => { lastSpawned!.emit('restarted', await forgeSpawnWrapper()); }); lastSpawned.kill('SIGTERM'); } }); process.stdin.resume(); } const spawned = await forgeSpawnWrapper(); if (interactive) console.log(''); return spawned; }, );