diff --git a/forge.config.ts b/forge.config.ts index baec5bf970..42972fc317 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -7,6 +7,7 @@ import { MakerDMG } from '@electron-forge/maker-dmg'; import { MakerAppImage } from '@reforged/maker-appimage'; import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives'; import { WebpackPlugin } from '@electron-forge/plugin-webpack'; +import { ResourcePlugin } from 'electron-forge-resource-plugin'; import { mainConfig, rendererConfig } from './webpack.config'; @@ -29,13 +30,6 @@ const config: ForgeConfig = { // osxSign: {}, // osxNotarize: {}, - - // One or more files to be copied directly into the app's `Contents/Resources` directory - // for macOS target platforms, and the `resources` directory for other target platforms. - // The resources directory can be referenced in code via `process.resourcesPath`. - extraResource: [ - // TODO: child writer - ], }, rebuildConfig: {}, makers: [ @@ -152,8 +146,15 @@ const config: ForgeConfig = { ], }, }), + new ResourcePlugin({ + env: 'ETCHER_UTIL_BIN_PATH', + path: './out/sidecar/bin/etcher-util', + build: { + command: 'tsc --project tsconfig.sidecar.json && pkg out/sidecar/util/api.js -c pkg-sidecar.json --target node18 --output out/sidecar/bin/etcher-util', + sources: './lib/util/', + }, + }), ], }; export default config; - diff --git a/lib/gui/app/modules/api.ts b/lib/gui/app/modules/api.ts index a1ec7fcf7a..dd5fa90556 100644 --- a/lib/gui/app/modules/api.ts +++ b/lib/gui/app/modules/api.ts @@ -18,7 +18,6 @@ import * as os from 'os'; import * as path from 'path'; import * as packageJSON from '../../../../package.json'; import * as permissions from '../../../shared/permissions'; -import { getAppPath } from '../../../shared/get-app-path'; import * as errors from '../../../shared/errors'; const THREADS_PER_CPU = 16; @@ -27,8 +26,8 @@ const THREADS_PER_CPU = 16; // the stdout maxBuffer size to be exceeded when flashing ipc.config.silent = true; -function writerArgv(): string[] { - let entryPoint = path.join(getAppPath(), 'generated', 'etcher-util'); +async function writerArgv(): Promise { + let entryPoint = await window.etcher.getEtcherUtilPath(); // AppImages run over FUSE, so the files inside the mount point // can only be accessed by the user that mounted the AppImage. // This means we can't re-spawn Etcher as root from the same @@ -75,7 +74,7 @@ async function spawnChild({ IPC_SERVER_ID: string; IPC_SOCKET_ROOT: string; }) { - const argv = writerArgv(); + const argv = await writerArgv(); const env = writerEnv(IPC_CLIENT_ID, IPC_SERVER_ID, IPC_SOCKET_ROOT); if (withPrivileges) { return await permissions.elevateCommand(argv, { diff --git a/lib/gui/app/preload.ts b/lib/gui/app/preload.ts index 5e9d369cc9..ad1b81aaa4 100644 --- a/lib/gui/app/preload.ts +++ b/lib/gui/app/preload.ts @@ -1,2 +1,12 @@ // See the Electron documentation for details on how to use preload scripts: // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts + +import * as webapi from '../webapi'; + +declare global { + interface Window { + etcher: typeof webapi; + } +} + +window['etcher'] = webapi; diff --git a/lib/gui/etcher.ts b/lib/gui/etcher.ts index 7e2a224fc8..faf63aed9b 100644 --- a/lib/gui/etcher.ts +++ b/lib/gui/etcher.ts @@ -242,6 +242,27 @@ electron.app.on('before-quit', () => { process.exit(EXIT_CODES.SUCCESS); }); +// this is replaced at build-time with the path to etcher-util +declare const ETCHER_UTIL_BIN_PATH: string; + +electron.ipcMain.handle('get-util-path', () => { + const binPath = ETCHER_UTIL_BIN_PATH; + + // Workaround the fact that the Resource forge plugin hardcodes + // the resource path to `resources` which is only valid on Linux + // and Windows builds -- it fails on macOS. + // + // We also can't just fallback to `process.resourcesPath` because + // that will always point to Electron's resource folder, which in + // dev builds using the Electron binary from node modules is not + // at all what we want, since our build artifacts reside in `/out`. + if (process.env.NODE_ENV === 'development') { + return binPath; + } else { + return binPath.replace('resources', process.resourcesPath); + } +}); + async function main(): Promise { if (!electron.app.requestSingleInstanceLock()) { electron.app.quit(); diff --git a/lib/gui/webapi.ts b/lib/gui/webapi.ts new file mode 100644 index 0000000000..1bcfe21935 --- /dev/null +++ b/lib/gui/webapi.ts @@ -0,0 +1,13 @@ +// +// Anything exported from this module will become available to the +// renderer process via preload. They're accessible as `window.etcher.foo()`. +// + +import { ipcRenderer } from 'electron'; + +// FIXME: this is a workaround for the renderer to be able to find the etcher-util +// binary. We should instead export a function that asks the main process to launch +// the binary itself. +export async function getEtcherUtilPath(): Promise { + return await ipcRenderer.invoke('get-util-path'); +} diff --git a/lib/pkg-sidekick.json b/lib/pkg-sidekick.json deleted file mode 100644 index 45673835f7..0000000000 --- a/lib/pkg-sidekick.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bin": "build/util/child-writer.js", - "pkg": { - "assets": [ - "node_modules/usb/prebuilds/darwin-x64+arm64/node.napi.node", - "node_modules/lzma-native/prebuilds/darwin-arm64/node.napi.node", - "node_modules/drivelist/build/Release/drivelist.node" - ] - } -} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7edd1bb04c..80d201b93f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -66,6 +66,7 @@ "chai": "4.3.7", "css-loader": "5.2.7", "electron": "^25.8.2", + "electron-forge-resource-plugin": "^1.0.0-alpha.6", "electron-mocha": "^11.0.2", "electron-notarize": "1.2.2", "electron-rebuild": "^3.2.9", @@ -9273,6 +9274,15 @@ "node": ">= 12.20.55" } }, + "node_modules/electron-forge-resource-plugin": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/electron-forge-resource-plugin/-/electron-forge-resource-plugin-1.0.0-alpha.6.tgz", + "integrity": "sha512-idkQCDMIX5jfMwTcfKKK6uKasS5WwOwtdsv2CzYi/uAmIgHLGyxCCuZIt0RjLDQe5o9NMr0Cg9misHYu36b8zw==", + "dev": true, + "dependencies": { + "@electron-forge/plugin-base": "^6.0.0" + } + }, "node_modules/electron-installer-common": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", @@ -27568,6 +27578,15 @@ } } }, + "electron-forge-resource-plugin": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/electron-forge-resource-plugin/-/electron-forge-resource-plugin-1.0.0-alpha.6.tgz", + "integrity": "sha512-idkQCDMIX5jfMwTcfKKK6uKasS5WwOwtdsv2CzYi/uAmIgHLGyxCCuZIt0RjLDQe5o9NMr0Cg9misHYu36b8zw==", + "dev": true, + "requires": { + "@electron-forge/plugin-base": "^6.0.0" + } + }, "electron-installer-common": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", diff --git a/package.json b/package.json index ec268052b9..b971304c69 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,6 @@ "url": "git@github.com:balena-io/etcher.git" }, "scripts": { - "build:rebuild-mountutils": "cd node_modules/mountutils && npm rebuild", - "build:sidecar": "npm run build:rebuild-mountutils && tsc --project tsconfig.sidecar.json && pkg build/util/api.js -c pkg-sidecar.json --target node18 --output generated/etcher-util", "flowzone-preinstall-linux": "sudo apt-get update && sudo apt-get install -y xvfb libudev-dev && cat < electron-builder.yml | yq e .deb.depends[] - | xargs -L1 echo | sed 's/|//g' | xargs -L1 sudo apt-get --ignore-missing install || true", "flowzone-preinstall-macos": "true", "flowzone-preinstall-windows": "npx node-gyp install", @@ -106,6 +104,7 @@ "chai": "4.3.7", "css-loader": "5.2.7", "electron": "^25.8.2", + "electron-forge-resource-plugin": "^1.0.0-alpha.6", "electron-mocha": "^11.0.2", "electron-notarize": "1.2.2", "electron-rebuild": "^3.2.9", diff --git a/tsconfig.sidecar.json b/tsconfig.sidecar.json index e3c943a12b..9ce2d6e49b 100644 --- a/tsconfig.sidecar.json +++ b/tsconfig.sidecar.json @@ -12,7 +12,7 @@ "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, - "outDir": "build" + "outDir": "out/sidecar" }, "include": ["lib/util"] }