v1.19.16
[etcher.git] / forge.sidecar.ts
blobfc348095ea000493f6d3dff7f857cee20bccad02
1 import { PluginBase } from '@electron-forge/plugin-base';
2 import type {
3         ForgeHookMap,
4         ResolvedForgeConfig,
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,
23         defineName: string,
24         binDir: string,
25         binName: string,
26 ): ResolvedForgeConfig {
27         config.plugins.forEach((plugin) => {
28                 if (plugin.name !== 'webpack' || !(plugin instanceof WebpackPlugin)) {
29                         return;
30                 }
32                 const { mainConfig } = plugin.config as any;
33                 if (mainConfig.plugins == null) {
34                         mainConfig.plugins = [];
35                 }
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
41                                 binName;
43                 debug(`define '${defineName}'='${value}'`);
45                 mainConfig.plugins.push(
46                         new DefinePlugin({
47                                 // expose path to helper via this webpack define
48                                 [defineName]: JSON.stringify(value),
49                         }),
50                 );
51         });
53         return config;
56 function build(
57         sourcesDir: string,
58         buildForArchs: string,
59         binDir: string,
60         binName: string,
61 ) {
62         const commands: Array<[string, string[], object?]> = [
63                 ['tsc', ['--project', 'tsconfig.sidecar.json', '--outDir', sourcesDir]],
64         ];
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}`]]);
79                 commands.push([
80                         'pkg',
81                         [
82                                 path.join(sourcesDir, 'util', 'api.js'),
83                                 '-c',
84                                 'pkg-sidecar.json',
85                                 // `--no-bytecode` so that we can cross-compile for arm64 on x64
86                                 '--no-bytecode',
87                                 '--public',
88                                 '--public-packages',
89                                 '"*"',
90                                 // always build for host platform and node version
91                                 // https://github.com/vercel/pkg-fetch/releases
92                                 '--target',
93                                 `node20-${arch}`,
94                                 '--output',
95                                 binPath,
96                         ],
97                 ]);
98         });
100         commands.forEach(([cmd, args, opt]) => {
101                 debug('running command:', cmd, args.join(' '));
102                 execFileSync(cmd, args, { shell: true, stdio: 'inherit', ...opt });
103         });
106 function copyArtifact(
107         buildPath: string,
108         arch: string,
109         binDir: string,
110         binName: string,
111 ) {
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> {
127         name = 'sidecar';
129         constructor() {
130                 super();
131                 this.getHooks = this.getHooks.bind(this);
132                 debug('isStartScript:', isStartScrpt());
133         }
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' : ''}`;
142                 return {
143                         resolveForgeConfig: async (currentConfig) => {
144                                 debug('resolveForgeConfig');
145                                 return addWebpackDefine(currentConfig, DEFINE_NAME, BIN_DIR, BIN_NAME);
146                         },
147                         generateAssets: async (_config, platform, arch) => {
148                                 debug('generateAssets', { platform, arch });
149                                 build(SRC_DIR, arch, BIN_DIR, BIN_NAME);
150                         },
151                         packageAfterCopy: async (
152                                 _config,
153                                 buildPath,
154                                 electronVersion,
155                                 platform,
156                                 arch,
157                         ) => {
158                                 debug('packageAfterCopy', {
159                                         buildPath,
160                                         electronVersion,
161                                         platform,
162                                         arch,
163                                 });
164                                 copyArtifact(buildPath, arch, BIN_DIR, BIN_NAME);
165                         },
166                 };
167         }