From 47c5572e7701aaa80644b063700a3f2ebc9e2d9b Mon Sep 17 00:00:00 2001 From: Ben Demboski Date: Mon, 27 Feb 2017 11:30:39 -0800 Subject: [PATCH] feat(generic): Support setting the Electron app path in start() The start command allows the caller to specify the directory for the Electron forge project, and then assumes that its main module is the Electron application. However, some projects may contain multiple Electron applications, or may want to specify an html file or a URL as the Electron application (see https://github.com/electron/electron/blob/cf694ef32b78f0904219c6c8ba554d3d5cbea037/default_app/main.js#L338). So, this adds an optional 'appPath' API option (and corresponding '-p'/'--app-path' CLI option) that is the path to the Electron application relative to the electron forge project (the 'dir' option). This also fixes the start command-line options parsing, which was passing all arguments through to the Electron application instead of parsing out the start command options. --- src/api/start.js | 10 +-- src/electron-forge-start.js | 39 +++++++++-- test/fast/electron_forge_start_spec.js | 89 ++++++++++++++++++++++++++ test/fast/start_spec.js | 21 ++++++ 4 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 test/fast/electron_forge_start_spec.js diff --git a/src/api/start.js b/src/api/start.js index 00f7e4d904..3c3da06359 100644 --- a/src/api/start.js +++ b/src/api/start.js @@ -9,7 +9,8 @@ import resolveDir from '../util/resolve-dir'; /** * @typedef {Object} StartOptions - * @property {string} [dir=process.cwd()] The path to the app to be run + * @property {string} [dir=process.cwd()] The path to the electron forge project to run + * @property {string} [appPath='.'] The path (relative to dir) to the electron app to run relative to the project directory * @property {boolean} [interactive=false] Whether to use sensible defaults or prompt the user visually * @property {boolean} [enableLogging=false] Enables advanced internal Electron debug calls * @property {Array} [args] Arguments to pass through to the launched Electron application @@ -23,8 +24,9 @@ import resolveDir from '../util/resolve-dir'; */ export default async (providedOptions = {}) => { // eslint-disable-next-line prefer-const, no-unused-vars - let { dir, interactive, enableLogging, args } = Object.assign({ + let { dir, interactive, enableLogging, appPath, args } = Object.assign({ dir: process.cwd(), + appPath: '.', interactive: false, enableLogging: false, args: [], @@ -56,9 +58,9 @@ export default async (providedOptions = {}) => { await asyncOra('Launching Application', async () => { /* istanbul ignore if */ if (process.platform === 'win32') { - spawned = spawn(path.resolve(dir, 'node_modules/.bin/electron.cmd'), ['.'].concat(args), spawnOpts); + spawned = spawn(path.resolve(dir, 'node_modules/.bin/electron.cmd'), [appPath].concat(args), spawnOpts); } else { - spawned = spawn(path.resolve(dir, 'node_modules/.bin/electron'), ['.'].concat(args), spawnOpts); + spawned = spawn(path.resolve(dir, 'node_modules/.bin/electron'), [appPath].concat(args), spawnOpts); } }); diff --git a/src/electron-forge-start.js b/src/electron-forge-start.js index d0eb51c8a1..bcfaafaefa 100644 --- a/src/electron-forge-start.js +++ b/src/electron-forge-start.js @@ -6,10 +6,21 @@ import './util/terminate'; import { start } from './api'; (async () => { + let commandArgs; + let appArgs; + const tripleDashIndex = process.argv.indexOf('---'); + if (tripleDashIndex === -1) { + commandArgs = process.argv; + } else { + commandArgs = process.argv.slice(0, tripleDashIndex); + appArgs = process.argv.slice(tripleDashIndex + 1); + } + let dir = process.cwd(); program .version(require('../package.json').version) .arguments('[cwd]') + .option('-p, --app-path ', "Override the path to the Electron app to launch (defaults to '.')") .option('-l, --enable-logging', 'Enable advanced logging. This will log internal Electron things') .action((cwd) => { if (!cwd) return; @@ -19,12 +30,30 @@ import { start } from './api'; dir = path.resolve(dir, cwd); } }) - .parse(process.argv.slice(0, 2)); + .parse(commandArgs); + + program.on('--help', () => { + console.log(" Any arguments found after '---' will be passed to the Electron app, e.g."); + console.log(''); + console.log(' $ electron-forge /path/to/project -l --- -d -f foo.txt'); + console.log(''); + console.log(" will pass the arguments '-d -f foo.txt' to the Electron app"); + }); - await start({ + const opts = { dir, interactive: true, - enableLogging: program.enableLogging, - args: process.argv.slice(2), - }); + }; + + if (program.appPath) { + opts.appPath = program.appPath; + } + if (program.enableLogging) { + opts.enableLogging = program.enableLogging; + } + if (appArgs) { + opts.args = appArgs; + } + + await start(opts); })(); diff --git a/test/fast/electron_forge_start_spec.js b/test/fast/electron_forge_start_spec.js new file mode 100644 index 0000000000..11359bfa6b --- /dev/null +++ b/test/fast/electron_forge_start_spec.js @@ -0,0 +1,89 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import proxyquire from 'proxyquire'; +import sinon from 'sinon'; +import { Command } from 'commander'; +import path from 'path'; + +chai.use(chaiAsPromised); + +describe('electron-forge start', () => { + let argv; + let startStub; + let runCommand; + + beforeEach(() => { + ({ argv } = process); + + startStub = sinon.stub(); + runCommand = async (args = []) => { + process.argv = ['node', 'electron-forge-start'].concat(args); + return proxyquire.noCallThru().load('../../src/electron-forge-start', { + commander: new Command(), + './api': { start: async opts => startStub(opts) }, + }); + }; + }); + + afterEach(() => { + process.argv = argv; + }); + + it('should pass through correct defaults', async () => { + await runCommand(); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: process.cwd(), + interactive: true, + }); + }); + + it('should handle an absolute project directory', async () => { + await runCommand([path.join(process.cwd(), 'test', 'fixture', 'dummy_app')]); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: path.join(process.cwd(), 'test', 'fixture', 'dummy_app'), + interactive: true, + }); + }); + + it('should handle a relative project directory', async () => { + await runCommand([path.join('test', 'fixture', 'dummy_app')]); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: path.join(process.cwd(), 'test', 'fixture', 'dummy_app'), + interactive: true, + }); + }); + + it('should handle an app path', async () => { + await runCommand(['-p', path.join('foo', 'electron.js')]); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: process.cwd(), + appPath: path.join('foo', 'electron.js'), + interactive: true, + }); + }); + + it('should be able to enable logging', async () => { + await runCommand(['-l']); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: process.cwd(), + enableLogging: true, + interactive: true, + }); + }); + + it('should handle app args', async () => { + await runCommand(['-l', '---', '-a', 'foo', '-l']); + expect(startStub.callCount).to.equal(1); + expect(startStub.firstCall.args[0]).to.deep.equal({ + dir: process.cwd(), + enableLogging: true, + interactive: true, + args: ['-a', 'foo', '-l'], + }); + }); +}); diff --git a/test/fast/start_spec.js b/test/fast/start_spec.js index 565fd0e548..2baceb4345 100644 --- a/test/fast/start_spec.js +++ b/test/fast/start_spec.js @@ -35,6 +35,27 @@ describe('start', () => { expect(spawnStub.firstCall.args[2].env).to.not.have.property('ELECTRON_ENABLE_LOGGING'); }); + it("should pass electron '.' as the app path if not specified", async () => { + resolveStub.returnsArg(0); + await start({ + dir: __dirname, + }); + expect(spawnStub.callCount).to.equal(1); + expect(spawnStub.firstCall.args[0]).to.contain('electron'); + expect(spawnStub.firstCall.args[1][0]).to.equal('.'); + }); + + it('should pass electron the app path if specified', async () => { + resolveStub.returnsArg(0); + await start({ + dir: __dirname, + appPath: '/path/to/app.js', + }); + expect(spawnStub.callCount).to.equal(1); + expect(spawnStub.firstCall.args[0]).to.contain('electron'); + expect(spawnStub.firstCall.args[1][0]).to.equal('/path/to/app.js'); + }); + it('should enable electron logging if enableLogging=true', async () => { resolveStub.returnsArg(0); await start({