1 import { PluginBase } from '@electron-forge/plugin-base';
5 } from '@electron-forge/shared-types';
6 import { WebpackPlugin } from '@electron-forge/plugin-webpack';
7 import { DefinePlugin } from 'webpack';
9 import { execFileSync } from 'child_process';
10 import * as fs from 'fs';
11 import * as path from 'path';
13 import * as d from 'debug';
15 const debug = d('sidecar');
17 function isStartScrpt(): boolean {
18 return process.env.npm_lifecycle_event === 'start';
21 function addWebpackDefine(
22 config: ResolvedForgeConfig,
26 ): ResolvedForgeConfig {
27 config.plugins.forEach((plugin) => {
28 if (plugin.name !== 'webpack' || !(plugin instanceof WebpackPlugin)) {
32 const { mainConfig } = plugin.config as any;
33 if (mainConfig.plugins == null) {
34 mainConfig.plugins = [];
37 const value = isStartScrpt()
38 ? // on `npm start`, point directly to the binary
39 path.resolve(binDir, binName)
40 : // otherwise point relative to the resources folder of the bundled app
43 debug(`define '${defineName}'='${value}'`);
45 mainConfig.plugins.push(
47 // expose path to helper via this webpack define
48 [defineName]: JSON.stringify(value),
58 buildForArchs: string,
62 const commands: Array<[string, string[], object?]> = [
63 ['tsc', ['--project', 'tsconfig.sidecar.json', '--outDir', sourcesDir]],
66 buildForArchs.split(',').forEach((arch) => {
67 const binPath = isStartScrpt()
68 ? // on `npm start`, we don't know the arch we're building for at the time we're
69 // adding the webpack define, so we just build under binDir
70 path.resolve(binDir, binName)
71 : // otherwise build in arch-specific directory within binDir
72 path.resolve(binDir, arch, binName);
74 // FIXME: rebuilding mountutils shouldn't be necessary, but it is.
75 // It's coming from etcher-sdk, a fix has been upstreamed but to use
76 // the latest etcher-sdk we need to upgrade axios at the same time.
77 commands.push(['npm', ['rebuild', 'mountutils', `--arch=${arch}`]]);
82 path.join(sourcesDir, 'util', 'api.js'),
85 // `--no-bytecode` so that we can cross-compile for arm64 on x64
90 // always build for host platform and node version
91 // https://github.com/vercel/pkg-fetch/releases
100 commands.forEach(([cmd, args, opt]) => {
101 debug('running command:', cmd, args.join(' '));
102 execFileSync(cmd, args, { shell: true, stdio: 'inherit', ...opt });
106 function copyArtifact(
112 const binPath = isStartScrpt()
113 ? // on `npm start`, we don't know the arch we're building for at the time we're
114 // adding the webpack define, so look for the binary directly under binDir
115 path.resolve(binDir, binName)
116 : // otherwise look into arch-specific directory within binDir
117 path.resolve(binDir, arch, binName);
119 // buildPath points to appPath, which is inside resources dir which is the one we actually want
120 const resourcesPath = path.dirname(buildPath);
121 const dest = path.resolve(resourcesPath, path.basename(binPath));
122 debug(`copying '${binPath}' to '${dest}'`);
123 fs.copyFileSync(binPath, dest);
126 export class SidecarPlugin extends PluginBase<void> {
131 this.getHooks = this.getHooks.bind(this);
132 debug('isStartScript:', isStartScrpt());
135 getHooks(): ForgeHookMap {
136 const DEFINE_NAME = 'ETCHER_UTIL_BIN_PATH';
137 const BASE_DIR = path.join('out', 'sidecar');
138 const SRC_DIR = path.join(BASE_DIR, 'src');
139 const BIN_DIR = path.join(BASE_DIR, 'bin');
140 const BIN_NAME = `etcher-util${process.platform === 'win32' ? '.exe' : ''}`;
143 resolveForgeConfig: async (currentConfig) => {
144 debug('resolveForgeConfig');
145 return addWebpackDefine(currentConfig, DEFINE_NAME, BIN_DIR, BIN_NAME);
147 generateAssets: async (_config, platform, arch) => {
148 debug('generateAssets', { platform, arch });
149 build(SRC_DIR, arch, BIN_DIR, BIN_NAME);
151 packageAfterCopy: async (
158 debug('packageAfterCopy', {
164 copyArtifact(buildPath, arch, BIN_DIR, BIN_NAME);