diff --git a/.resinci.json b/.resinci.json index 4f85d729..56dc4038 100644 --- a/.resinci.json +++ b/.resinci.json @@ -62,6 +62,12 @@ "depends": [ "polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1" ] + }, + "protocols": { + "name": "etcher", + "schemes": [ + "etcher" + ] } } } diff --git a/Makefile b/Makefile index 17c738c0..b313533a 100644 --- a/Makefile +++ b/Makefile @@ -164,10 +164,8 @@ lint: lint-ts lint-sass lint-cpp lint-spell MOCHA_OPTIONS=--recursive --reporter spec --require ts-node/register --require-main "tests/gui/allow-renderer-process-reuse.ts" -# See https://github.com/electron/spectron/issues/127 -ETCHER_SPECTRON_ENTRYPOINT ?= $(shell node -e 'console.log(require("electron"))') test-spectron: - ETCHER_SPECTRON_ENTRYPOINT="$(ETCHER_SPECTRON_ENTRYPOINT)" mocha $(MOCHA_OPTIONS) tests/spectron/runner.spec.ts + mocha $(MOCHA_OPTIONS) tests/spectron/runner.spec.ts test-gui: electron-mocha $(MOCHA_OPTIONS) --full-trace --no-sandbox --renderer tests/gui/**/*.ts diff --git a/afterSignHook.js b/afterSignHook.js index 1a281cca..d071ea85 100644 --- a/afterSignHook.js +++ b/afterSignHook.js @@ -1,10 +1,11 @@ 'use strict' const { notarize } = require('electron-notarize') +const { ELECTRON_SKIP_NOTARIZATION } = process.env async function main(context) { const { electronPlatformName, appOutDir } = context - if (electronPlatformName !== 'darwin') { + if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') { return } diff --git a/beforeBuild.js b/beforeBuild.js index c08296a9..d63b3609 100644 --- a/beforeBuild.js +++ b/beforeBuild.js @@ -1,17 +1,26 @@ 'use strict' -const cp = require('child_process') +const cp = require('child_process'); +const rimraf = require('rimraf'); +const process = require('process'); // Rebuild native modules for ia32 and run webpack again for the ia32 part of windows packages exports.default = function(context) { if (context.platform.name === 'windows') { cp.execFileSync( 'bash', - ['./node_modules/.bin/electron-rebuild', '--types', 'dev', '--arch', context.arch] + ['./node_modules/.bin/electron-rebuild', '--types', 'dev', '--arch', context.arch], ); + rimraf.sync('generated'); cp.execFileSync( 'bash', - ['./node_modules/.bin/webpack'] + ['./node_modules/.bin/webpack'], + { + env: { + ...process.env, + npm_config_target_arch: context.arch, + }, + }, ); } } diff --git a/electron-builder.yml b/electron-builder.yml index 180bb7fd..c75fb5e4 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -91,3 +91,7 @@ deb: rpm: depends: - util-linux +protocols: + name: etcher + schemes: + - etcher diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index 42d06a6f..f3d053ed 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -17,6 +17,7 @@ import { faFile, faLink } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { sourceDestination } from 'etcher-sdk'; +import { ipcRenderer, IpcRendererEvent } from 'electron'; import * as _ from 'lodash'; import { GPTPartition, MBRPartition } from 'partitioninfo'; import * as path from 'path'; @@ -237,6 +238,7 @@ export class SourceSelector extends React.Component< this.openImageSelector = this.openImageSelector.bind(this); this.openURLSelector = this.openURLSelector.bind(this); this.reselectImage = this.reselectImage.bind(this); + this.onSelectImage = this.onSelectImage.bind(this); this.onDrop = this.onDrop.bind(this); this.showSelectedImageDetails = this.showSelectedImageDetails.bind(this); this.afterSelected = props.afterSelected.bind(this); @@ -246,10 +248,22 @@ export class SourceSelector extends React.Component< this.unsubscribe = observe(() => { this.setState(getState()); }); + ipcRenderer.on('select-image', this.onSelectImage); + ipcRenderer.send('source-selector-ready'); } public componentWillUnmount() { this.unsubscribe(); + ipcRenderer.removeListener('select-image', this.onSelectImage); + } + + private async onSelectImage(_event: IpcRendererEvent, imagePath: string) { + const isURL = + _.startsWith(imagePath, 'https://') || _.startsWith(imagePath, 'http://'); + await this.selectImageByPath({ + imagePath, + SourceType: isURL ? sourceDestination.Http : sourceDestination.File, + }); } private reselectImage() { diff --git a/lib/gui/etcher.ts b/lib/gui/etcher.ts index e98b475e..76cf50b5 100644 --- a/lib/gui/etcher.ts +++ b/lib/gui/etcher.ts @@ -17,6 +17,7 @@ import { delay } from 'bluebird'; import * as electron from 'electron'; import { autoUpdater } from 'electron-updater'; +import { platform } from 'os'; import * as _ from 'lodash'; import * as path from 'path'; import * as semver from 'semver'; @@ -28,6 +29,8 @@ import * as settings from './app/models/settings'; import * as analytics from './app/modules/analytics'; import { buildWindowMenu } from './menu'; +const customProtocol = 'etcher'; +const scheme = `${customProtocol}://`; const updatablePackageTypes = ['appimage', 'nsis', 'dmg']; const packageUpdatable = _.includes(updatablePackageTypes, packageType); let packageUpdated = false; @@ -54,6 +57,44 @@ async function checkForUpdates(interval: number) { } } +function getCommandLineURL(argv: string[]): string | undefined { + argv = argv.slice(electron.app.isPackaged ? 1 : 2); + if (argv.length) { + const value = argv[argv.length - 1]; + // Take into account electron arguments + if (value.startsWith('--')) { + return; + } + // https://stackoverflow.com/questions/10242115/os-x-strange-psn-command-line-parameter-when-launched-from-finder + if (platform() === 'darwin' && value.startsWith('-psn_')) { + return; + } + return value; + } +} + +const sourceSelectorReady = new Promise((resolve) => { + electron.ipcMain.on('source-selector-ready', resolve); +}); + +async function selectImageURL(url?: string) { + // 'data:,' is the default chromedriver url that is passed as last argument when running spectron tests + if (url !== undefined && url !== 'data:,') { + url = url.startsWith(scheme) ? url.slice(scheme.length) : url; + await sourceSelectorReady; + electron.BrowserWindow.getAllWindows().forEach((window) => { + window.webContents.send('select-image', url); + }); + } +} + +// This will catch clicks on links such as Open in Etcher +// We need to listen to the event before everything else otherwise the event won't be fired +electron.app.on('open-url', async (event, data) => { + event.preventDefault(); + await selectImageURL(data); +}); + async function createMainWindow() { const fullscreen = Boolean(await settings.get('fullscreen')); const defaultWidth = 800; @@ -87,6 +128,8 @@ async function createMainWindow() { }, }); + electron.app.setAsDefaultProtocolClient(customProtocol); + buildWindowMenu(mainWindow); mainWindow.setFullScreen(true); @@ -133,6 +176,7 @@ async function createMainWindow() { } } }); + return mainWindow; } electron.app.allowRendererProcessReuse = false; @@ -145,14 +189,24 @@ electron.app.on('window-all-closed', electron.app.quit); // make use of it to ensure the browser window is completely destroyed. // See https://github.com/electron/electron/issues/5273 electron.app.on('before-quit', () => { + electron.app.releaseSingleInstanceLock(); process.exit(EXIT_CODES.SUCCESS); }); async function main(): Promise { - if (electron.app.isReady()) { - await createMainWindow(); + if (!electron.app.requestSingleInstanceLock()) { + electron.app.quit(); } else { - electron.app.on('ready', createMainWindow); + await electron.app.whenReady(); + const window = await createMainWindow(); + electron.app.on('second-instance', async (_event, argv) => { + if (window.isMinimized()) { + window.restore(); + } + window.focus(); + await selectImageURL(getCommandLineURL(argv)); + }); + await selectImageURL(getCommandLineURL(process.argv)); } } diff --git a/lib/gui/modules/child-writer.ts b/lib/gui/modules/child-writer.ts index 390a931d..108932e8 100644 --- a/lib/gui/modules/child-writer.ts +++ b/lib/gui/modules/child-writer.ts @@ -246,7 +246,7 @@ ipc.connectTo(IPC_SERVER_ID, () => { path: options.imagePath, }); } else { - source = new Http({ url: options.imagePath }); + source = new Http({ url: options.imagePath, avoidRandomAccess: true }); } try { const results = await writeAndValidate({ diff --git a/lib/shared/permissions.ts b/lib/shared/permissions.ts index a3c93665..51580a35 100755 --- a/lib/shared/permissions.ts +++ b/lib/shared/permissions.ts @@ -29,7 +29,10 @@ import { tmpFileDisposer } from './utils'; const execAsync = promisify(childProcess.exec); const execFileAsync = promisify(childProcess.execFile); -const sudoExecAsync = promisify(sudoPrompt.exec); +// sudo-prompt's exec callback is function(error, stdout, stderr) so we need multiArgs +const sudoExecAsync = Bluebird.promisify(sudoPrompt.exec, { + multiArgs: true, +}) as (cmd: string, options: any) => Bluebird<[string, string]>; /** * @summary The user id of the UNIX "superuser" @@ -123,10 +126,7 @@ async function elevateScriptUnix( name: string, ): Promise<{ cancelled: boolean }> { const cmd = ['bash', escapeSh(path)].join(' '); - const [, stderr] = await sudoExecAsync(cmd, { name }); - if (!_.isEmpty(stderr)) { - throw errors.createError({ title: stderr }); - } + await sudoExecAsync(cmd, { name }); return { cancelled: false }; } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index aa65efc4..4b02ed7c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -677,6 +677,16 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/copy-webpack-plugin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-6.0.0.tgz", + "integrity": "sha512-Ousy+sNap1j44eG+C9FZvTUybpp9lFmKjBRF7L0NDs/+SDA9OXKo2OpsHJfD/LMWflz+uvfTCBXH1CgdL6AW/g==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/webpack": "*" + } + }, "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", @@ -965,13 +975,13 @@ } }, "@types/terser-webpack-plugin": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-2.2.0.tgz", - "integrity": "sha512-ywqEfTm7KdKoX9aYx0zYtiFU1z6IHrIYW9FJqeay2Ea58rTPML1J0hvoztGal2Jow3bkgGKcAmEZNL+8LqUVrA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-K5C7izOT8rR4qiE2vfXcQNEJN4lT9cq/2qJgpMUWR2HsjDW/KVrHx2CaHuaXvaqDNsRmdELPLaxeJHiI4GjVrA==", "dev": true, "requires": { "@types/webpack": "*", - "terser": "^4.3.9" + "terser": "^4.6.13" } }, "@types/tmp": { @@ -4325,9 +4335,9 @@ "dev": true }, "drivelist": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/drivelist/-/drivelist-9.0.0.tgz", - "integrity": "sha512-DNQ1oFAv5p1+UKVkQHCYQFHolFbItxSnjcJchxrhlEkW4RSuLfC0xOnq87uM8dcMzOuPfA37SKX7HsUIpw14uA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/drivelist/-/drivelist-9.0.2.tgz", + "integrity": "sha512-B68AttNDXsew8M2viM99I4sNMJ2T5/lZ2fDlVsEAAzaJ1pyZN2Ibhhd37LAqsNRPhhPtpyrCi9+C6AfJ2kwo4g==", "dev": true, "requires": { "bindings": "^1.3.0", @@ -4402,9 +4412,9 @@ } }, "electron": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0.tgz", - "integrity": "sha512-JsaSQNPh+XDYkLj8APtVKTtvpb86KIG57W5OOss4TNrn8L3isC9LsCITwfnVmGIXHhvX6oY/weCtN5hAAytjVg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.2.tgz", + "integrity": "sha512-+a3KegLvQXVjC3b6yBWwZmtWp3tHf9ut27yORAWHO9JRFtKfNf88fi1UvTPJSW8R0sUH7ZEdzN6A95T22KGtlA==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -4756,6 +4766,25 @@ "path-exists": "^3.0.0" } }, + "node-gyp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-6.1.0.tgz", + "integrity": "sha512-h4A2zDlOujeeaaTx06r4Vy+8MZ1679lU+wbCKDS4ZtvY2A37DESo37oejIw0mtmR3+rvNwts5B6Kpt1KrNYdNw==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -4771,6 +4800,12 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -5320,9 +5355,9 @@ "dev": true }, "etcher-sdk": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.4.tgz", - "integrity": "sha512-s9KXeLOtwrOxZs6F2VpGdS2VARwgJzmQp7/Xh4DG0yTAIR58djzZ2+cmUQQN16ztFMiQdCgTWWnOUJV1eVtO4g==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.8.tgz", + "integrity": "sha512-fSNwpqeCdc75xNIKwf+At8+r/EQ+/X/IV7gUYFa/wKXtnFHHxYJor6VfPdELUbtDDBWWa2cneJvtwGmcDPBAvA==", "dev": true, "requires": { "@ronomon/direct-io": "^3.0.1", @@ -9454,9 +9489,9 @@ } }, "node-addon-api": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz", - "integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", "dev": true }, "node-environment-flags": { @@ -9488,28 +9523,90 @@ } }, "node-gyp": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-6.1.0.tgz", - "integrity": "sha512-h4A2zDlOujeeaaTx06r4Vy+8MZ1679lU+wbCKDS4ZtvY2A37DESo37oejIw0mtmR3+rvNwts5B6Kpt1KrNYdNw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.0.0.tgz", + "integrity": "sha512-ZW34qA3CJSPKDz2SJBHKRvyNQN0yWO5EGKKksJc+jElu9VA468gwJTyTArC1iOXU7rN3Wtfg/CMt/dBAOFIjvg==", "dev": true, "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", + "graceful-fs": "^4.2.3", + "nopt": "^4.0.3", "npmlog": "^4.1.2", - "request": "^2.88.0", + "request": "^2.88.2", "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" + "semver": "^7.3.2", + "tar": "^6.0.1", + "which": "^2.0.2" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } @@ -14963,4 +15060,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 311a8d80..cc51030c 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@fortawesome/react-fontawesome": "^0.1.7", "@types/bluebird": "^3.5.30", "@types/chai": "^4.2.7", + "@types/copy-webpack-plugin": "^6.0.0", "@types/mime-types": "^2.1.0", "@types/mini-css-extract-plugin": "^0.9.1", "@types/mocha": "^7.0.2", @@ -60,7 +61,7 @@ "@types/request": "^2.48.4", "@types/semver": "^7.1.0", "@types/sinon": "^9.0.0", - "@types/terser-webpack-plugin": "^2.2.0", + "@types/terser-webpack-plugin": "^3.0.0", "@types/tmp": "^0.2.0", "@types/webpack-node-externals": "^1.7.0", "bluebird": "^3.7.2", @@ -70,13 +71,13 @@ "css-loader": "^3.5.3", "d3": "^4.13.0", "debug": "^4.2.0", - "electron": "9.0.0", + "electron": "9.0.2", "electron-builder": "^22.7.0", "electron-mocha": "^8.2.0", "electron-notarize": "^0.3.0", "electron-rebuild": "^1.11.0", "electron-updater": "^4.3.2", - "etcher-sdk": "^4.1.4", + "etcher-sdk": "^4.1.8", "file-loader": "^6.0.0", "flexboxgrid": "^6.3.0", "husky": "^4.2.5", @@ -89,7 +90,7 @@ "mocha": "^7.0.1", "nan": "^2.14.0", "native-addon-loader": "^2.0.1", - "node-gyp": "^6.1.0", + "node-gyp": "^7.0.0", "node-ipc": "^9.1.1", "omit-deep-lodash": "1.1.4", "path-is-inside": "^1.0.2", diff --git a/tests/spectron/runner.spec.ts b/tests/spectron/runner.spec.ts index e693b50e..98f7c3e5 100644 --- a/tests/spectron/runner.spec.ts +++ b/tests/spectron/runner.spec.ts @@ -16,41 +16,29 @@ import { expect } from 'chai'; import { Application } from 'spectron'; - -import * as EXIT_CODES from '../../lib/shared/exit-codes'; - -const entrypoint = process.env.ETCHER_SPECTRON_ENTRYPOINT; - -if (!entrypoint) { - console.error('You need to properly configure ETCHER_SPECTRON_ENTRYPOINT'); - process.exit(EXIT_CODES.GENERAL_ERROR); -} +import * as electronPath from 'electron'; describe('Spectron', function () { // Mainly for CI jobs this.timeout(40000); - let app: Application; - - before('app:start', function () { - app = new Application({ - path: entrypoint, - args: ['--no-sandbox', '.'], - }); - - return app.start(); + const app = new Application({ + path: (electronPath as unknown) as string, + args: ['--no-sandbox', '.'], }); - after('app:stop', function () { + before('app:start', async () => { + await app.start(); + }); + + after('app:stop', async () => { if (app && app.isRunning()) { - return app.stop(); + await app.stop(); } - - return Promise.resolve(); }); - describe('Browser Window', function () { - it('should open a browser window', async function () { + describe('Browser Window', () => { + it('should open a browser window', async () => { // We can't use `isVisible()` here as it won't work inside // a Windows Docker container, but we can approximate it // with these set of checks: @@ -61,7 +49,7 @@ describe('Spectron', function () { expect(await app.browserWindow.isFocused()).to.be.true; }); - it('should set a proper title', async function () { + it('should set a proper title', async () => { // @ts-ignore (SpectronClient.getTitle exists) return expect(await app.client.getTitle()).to.equal('Etcher'); }); diff --git a/webpack.config.ts b/webpack.config.ts index 67561937..0badecc7 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -// @ts-ignore @types for copy-webpack-plugin@6.0.1 not released yet import * as CopyPlugin from 'copy-webpack-plugin'; import { readdirSync } from 'fs'; import * as _ from 'lodash'; @@ -22,6 +21,7 @@ import * as MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as os from 'os'; import outdent from 'outdent'; import * as path from 'path'; +import { env } from 'process'; import * as SimpleProgressWebpackPlugin from 'simple-progress-webpack-plugin'; import * as TerserPlugin from 'terser-webpack-plugin'; import { BannerPlugin, NormalModuleReplacementPlugin } from 'webpack'; @@ -77,7 +77,11 @@ function renameNodeModules(resourcePath: string) { function findLzmaNativeBindingsFolder(): string { const files = readdirSync(path.join('node_modules', 'lzma-native')); - const bindingsFolder = files.find((f) => f.startsWith('binding-')); + const bindingsFolder = files.find( + (f) => + f.startsWith('binding-') && + f.endsWith(env.npm_config_target_arch || os.arch()), + ); if (bindingsFolder === undefined) { throw new Error('Could not find lzma_native binding'); } @@ -91,11 +95,15 @@ interface ReplacementRule { replace: string | (() => string); } +function slashOrAntislash(pattern: RegExp): RegExp { + return new RegExp(pattern.source.replace(/\\\//g, '(\\/|\\\\)')); +} + function replace(test: RegExp, ...replacements: ReplacementRule[]) { return { loader: 'string-replace-loader', // Handle windows path separators - test: new RegExp(test.source.replace(/\\\//g, '(\\/|\\\\)')), + test: slashOrAntislash(test), options: { multiple: replacements.map((r) => ({ ...r, strict: true })) }, }; } @@ -218,7 +226,7 @@ const commonConfig = { // Force axios to use http.js, not xhr.js as we need stream support // (it's package.json file replaces http with xhr for browser targets). new NormalModuleReplacementPlugin( - /node_modules\/axios\/lib\/adapters\/xhr\.js/, + slashOrAntislash(/node_modules\/axios\/lib\/adapters\/xhr\.js/), './http.js', ), ],