diff --git a/packages/api/core/src/api/start.ts b/packages/api/core/src/api/start.ts index 491bb06ad4..31357787f3 100644 --- a/packages/api/core/src/api/start.ts +++ b/packages/api/core/src/api/start.ts @@ -11,7 +11,7 @@ import rebuild from '../util/rebuild'; import resolveDir from '../util/resolve-dir'; import getForgeConfig from '../util/forge-config'; import { runHook } from '../util/hook'; -import { getElectronVersion } from '../util/electron-version'; +import { getElectronModulePath, getElectronVersion } from '../util/electron-version'; export { StartOptions }; @@ -78,7 +78,7 @@ export default async ({ if (!electronExecPath) { // eslint-disable-next-line import/no-dynamic-require, global-require - electronExecPath = require(path.resolve(dir, 'node_modules/electron')); + electronExecPath = require((await getElectronModulePath(dir, packageJSON)) || path.resolve(dir, 'node_modules/electron')); } const spawnOpts = { diff --git a/packages/api/core/src/util/electron-version.ts b/packages/api/core/src/util/electron-version.ts index d933e88091..46202ee1f0 100644 --- a/packages/api/core/src/util/electron-version.ts +++ b/packages/api/core/src/util/electron-version.ts @@ -46,23 +46,55 @@ export class PackageNotFoundError extends Error { } } -export async function getElectronVersion(dir: string, packageJSON: any): Promise { +function getElectronModuleName(packageJSON: any): string { if (!packageJSON.devDependencies) { throw new Error('package.json for app does not have any devDependencies'.red); } + const packageName = electronPackageNames.find((pkg) => packageJSON.devDependencies[pkg]); if (packageName === undefined) { throw new Error('Could not find any Electron packages in devDependencies'); } + return packageName; +} + +async function getElectronPackageJSONPath( + dir: string, + packageName: string, +): Promise { + const nodeModulesPath = await determineNodeModulesPath(dir); + if (!nodeModulesPath) { + throw new PackageNotFoundError(packageName, dir); + } + const electronPackageJSONPath = path.join(nodeModulesPath, packageName, 'package.json'); + if (await fs.pathExists(electronPackageJSONPath)) { + return electronPackageJSONPath; + } + + return undefined; +} + +export async function getElectronModulePath( + dir: string, + packageJSON: any, +): Promise { + const moduleName = await getElectronModuleName(packageJSON); + const packageJSONPath = await getElectronPackageJSONPath(dir, moduleName); + if (packageJSONPath) { + return path.dirname(packageJSONPath); + } + + return undefined; +} + +export async function getElectronVersion(dir: string, packageJSON: any): Promise { + const packageName = getElectronModuleName(packageJSON); + let version = packageJSON.devDependencies[packageName]; if (!semver.valid(version)) { // It's not an exact version, find it in the actual module - const nodeModulesPath = await determineNodeModulesPath(dir); - if (!nodeModulesPath) { - throw new PackageNotFoundError(packageName, dir); - } - const electronPackageJSONPath = path.join(nodeModulesPath, packageName, 'package.json'); - if (await fs.pathExists(electronPackageJSONPath)) { + const electronPackageJSONPath = await getElectronPackageJSONPath(dir, packageName); + if (electronPackageJSONPath) { const electronPackageJSON = await fs.readJson(electronPackageJSONPath); // eslint-disable-next-line prefer-destructuring version = electronPackageJSON.version; diff --git a/packages/api/core/test/fast/electron-version_spec.ts b/packages/api/core/test/fast/electron-version_spec.ts index b667840a2f..138abde897 100644 --- a/packages/api/core/test/fast/electron-version_spec.ts +++ b/packages/api/core/test/fast/electron-version_spec.ts @@ -1,6 +1,8 @@ import { expect } from 'chai'; +import fs from 'fs-extra'; +import os from 'os'; import path from 'path'; -import { getElectronVersion, updateElectronDependency } from '../../src/util/electron-version'; +import { getElectronModulePath, getElectronVersion, updateElectronDependency } from '../../src/util/electron-version'; import { devDeps, exactDevDeps } from '../../src/api/init-scripts/init-npm'; import { hasYarn } from '../../src/util/yarn-or-npm'; @@ -86,3 +88,42 @@ describe('getElectronVersion', () => { } }); }); + +describe('getElectronModulePath', () => { + let tempDir: string; + before(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'electron-forge-test-')); + }); + + it('fails without devDependencies', () => expect(getElectronModulePath('', {})).to.eventually.be.rejectedWith('does not have any devDependencies')); + + it('fails without electron devDependencies', () => expect(getElectronModulePath('', { devDependencies: {} })).to.eventually.be.rejectedWith('Electron packages in devDependencies')); + + it('fails with no electron installed', async () => { + const fixtureDir = path.resolve(__dirname, '..', 'fixture', 'dummy_app'); + await fs.copy(fixtureDir, tempDir); + return expect(getElectronModulePath(tempDir, { devDependencies: { electron: '^4.0.2' } })).to.eventually.be.rejectedWith('Cannot find the package'); + }); + + it('works with electron', () => { + const fixtureDir = path.resolve(__dirname, '..', 'fixture', 'non-exact'); + return expect(getElectronModulePath(fixtureDir, { devDependencies: { electron: '^4.0.2' } })).to.eventually.equal(path.join(fixtureDir, 'node_modules', 'electron')); + }); + + it('works with yarn workspaces', async () => { + const workspaceDir = path.resolve(__dirname, '..', 'fixture', 'yarn-workspace'); + const fixtureDir = path.join(workspaceDir, 'packages', 'subpackage'); + const packageJSON = { + devDependencies: { electron: '^4.0.4' }, + }; + if (hasYarn()) { + expect(await getElectronModulePath(fixtureDir, packageJSON)).to.be.equal(path.join(workspaceDir, 'node_modules', 'electron')); + } else { + expect(getElectronModulePath(fixtureDir, packageJSON)).to.eventually.be.rejectedWith('Cannot find the package'); + } + }); + + after(async () => { + await fs.remove(tempDir); + }); +}); diff --git a/packages/api/core/test/fast/start_spec.ts b/packages/api/core/test/fast/start_spec.ts index 52044a389a..89c4050089 100644 --- a/packages/api/core/test/fast/start_spec.ts +++ b/packages/api/core/test/fast/start_spec.ts @@ -19,15 +19,20 @@ describe('start', () => { spawnStub = sinon.stub(); shouldOverride = false; packageJSON = require('../fixture/dummy_app/package.json'); + const electronPath = path.resolve(__dirname, 'node_modules/electron'); start = proxyquire.noCallThru().load('../../src/api/start', { + '../util/electron-version': { + getElectronModulePath: () => Promise.resolve(electronPath), + getElectronVersion: () => Promise.resolve('1.0.0'), + }, '../util/forge-config': async () => ({ pluginInterface: { overrideStartLogic: async () => shouldOverride, triggerHook: async () => false, }, }), - [path.resolve(__dirname, 'node_modules/electron')]: 'fake_electron_path', + [electronPath]: 'fake_electron_path', '../util/resolve-dir': async (dir: string) => resolveStub(dir), '../util/read-package-json': { readMutatedPackageJson: () => Promise.resolve(packageJSON),