UNPKG

@electron-forge/core

Version:

A complete tool for building modern Electron applications

212 lines (195 loc) 6.86 kB
import { PluginBase } from '@electron-forge/plugin-base'; import { ForgeListrTaskDefinition, ForgeMutatingHookFn, ForgeMutatingHookSignatures, ForgeSimpleHookFn, ForgeSimpleHookSignatures, IForgePlugin, IForgePluginInterface, ResolvedForgeConfig, StartResult, } from '@electron-forge/shared-types'; import { autoTrace } from '@electron-forge/tracer'; import chalk from 'chalk'; import debug from 'debug'; // eslint-disable-next-line n/no-missing-import import { StartOptions } from '../api'; import importSearch from './import-search'; const d = debug('electron-forge:plugins'); function isForgePlugin(plugin: IForgePlugin | unknown): plugin is IForgePlugin { return (plugin as IForgePlugin).__isElectronForgePlugin; } export default class PluginInterface implements IForgePluginInterface { private plugins: IForgePlugin[] = []; private _pluginPromise: Promise<void> = Promise.resolve(); private config: ResolvedForgeConfig; static async create( dir: string, forgeConfig: ResolvedForgeConfig, ): Promise<PluginInterface> { const int = new PluginInterface(dir, forgeConfig); await int._pluginPromise; return int; } private constructor(dir: string, forgeConfig: ResolvedForgeConfig) { this._pluginPromise = Promise.all( forgeConfig.plugins.map(async (plugin): Promise<IForgePlugin> => { if (isForgePlugin(plugin)) { return plugin; } if ( typeof plugin === 'object' && 'name' in plugin && 'config' in plugin ) { const { name: pluginName, config: opts } = plugin; if (typeof pluginName !== 'string') { throw new Error( `Expected plugin[0] to be a string but found ${pluginName}`, ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const Plugin = await importSearch<any>(dir, [pluginName]); if (!Plugin) { throw new Error( `Could not find module with name: ${pluginName}. Make sure it's listed in the devDependencies of your package.json`, ); } return new Plugin(opts); } throw new Error( `Expected plugin to either be a plugin instance or a { name, config } object but found ${JSON.stringify(plugin)}`, ); }), ).then((plugins) => { this.plugins = plugins; for (const plugin of this.plugins) { plugin.init(dir, forgeConfig); } return; }); // TODO: fix hack // eslint-disable-next-line @typescript-eslint/no-explicit-any this.config = null as any; Object.defineProperty(this, 'config', { value: forgeConfig, enumerable: false, configurable: false, writable: false, }); this.triggerHook = this.triggerHook.bind(this); this.overrideStartLogic = this.overrideStartLogic.bind(this); } async triggerHook<Hook extends keyof ForgeSimpleHookSignatures>( hookName: Hook, hookArgs: ForgeSimpleHookSignatures[Hook], ): Promise<void> { for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { let hooks = plugin.getHooks()[hookName] as | ForgeSimpleHookFn<Hook>[] | ForgeSimpleHookFn<Hook>; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { await hook(this.config, ...hookArgs); } } } } } async getHookListrTasks<Hook extends keyof ForgeSimpleHookSignatures>( childTrace: typeof autoTrace, hookName: Hook, hookArgs: ForgeSimpleHookSignatures[Hook], ): Promise<ForgeListrTaskDefinition[]> { const tasks: ForgeListrTaskDefinition[] = []; for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { let hooks = plugin.getHooks()[hookName] as | ForgeSimpleHookFn<Hook>[] | ForgeSimpleHookFn<Hook>; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { tasks.push({ title: `${chalk.cyan(`[plugin-${plugin.name}]`)} ${(hook as any).__hookName || `Running ${chalk.yellow(hookName)} hook`}`, task: childTrace( { name: 'forge-plugin-hook', category: '@electron-forge/hooks', extraDetails: { plugin: plugin.name, hook: hookName }, }, async (_, __, task) => { if ((hook as any).__hookName) { // Also give it the task return await (hook as any).call( task, this.config, ...(hookArgs as any[]), ); } else { await hook(this.config, ...hookArgs); } }, ), rendererOptions: {}, }); } } } } return tasks; } async triggerMutatingHook<Hook extends keyof ForgeMutatingHookSignatures>( hookName: Hook, ...item: ForgeMutatingHookSignatures[Hook] ): Promise<ForgeMutatingHookSignatures[Hook][0]> { let result: ForgeMutatingHookSignatures[Hook][0] = item[0]; for (const plugin of this.plugins) { if (typeof plugin.getHooks === 'function') { let hooks = plugin.getHooks()[hookName] as | ForgeMutatingHookFn<Hook>[] | ForgeMutatingHookFn<Hook>; if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { result = (await hook(this.config, ...item)) || result; } } } } return result; } async overrideStartLogic(opts: StartOptions): Promise<StartResult> { let newStartFn; const claimed: string[] = []; for (const plugin of this.plugins) { if ( typeof plugin.startLogic === 'function' && plugin.startLogic !== PluginBase.prototype.startLogic ) { claimed.push(plugin.name); newStartFn = plugin.startLogic.bind(plugin); } } if (claimed.length > 1) { throw new Error( `Multiple plugins tried to take control of the start command, please remove one of them\n --> ${claimed.join(', ')}`, ); } if (claimed.length === 1 && newStartFn) { d(`plugin: "${claimed[0]}" has taken control of the start command`); const result = await newStartFn(opts); if (typeof result === 'object' && 'tasks' in result) { result.tasks = result.tasks.map((task) => ({ ...task, title: `${chalk.cyan(`[plugin-${claimed[0]}]`)} ${task.title}`, })); } return result; } return false; } }