Skip to content
Permalink
Browse files

feat(core): allow mutating packageJSON on load

In order to override fields like "version" at build time this provides the ability
to intercept forge loading your package.json file and modify the JS files we read
  • Loading branch information
MarshallOfSound authored and malept committed May 26, 2018
1 parent ce36356 commit 1b7e4117ae41e2695e2209ae31e9a1389ee7fabc
@@ -9,7 +9,7 @@ import { deps, devDeps, exactDevDeps } from './init-scripts/init-npm';
import { setInitialForgeConfig } from '../util/forge-config';
import { info, warn } from '../util/messages';
import installDepList from '../util/install-dependencies';
import readPackageJSON from '../util/read-package-json';
import { readRawPackageJson } from '../util/read-package-json';

const d = debug('electron-forge:import');

@@ -72,7 +72,7 @@ export default async ({

await initGit(dir);

let packageJSON = await readPackageJSON(dir);
let packageJSON = await readRawPackageJson(dir);
if (packageJSON.config && packageJSON.config.forge) {
warn(interactive, 'It looks like this project is already configured for "electron-forge"'.green);
if (typeof shouldContinueOnExisting === 'function') {
@@ -161,14 +161,14 @@ export default async ({
await installDepList(dir, exactDevDeps, true, true);
});

packageJSON = await readPackageJSON(dir);
packageJSON = await readRawPackageJson(dir);

if (!packageJSON.version) {
warn(interactive, 'Please set the "version" in your application\'s package.json'.yellow);
}

packageJSON.config = packageJSON.config || {};
const templatePackageJSON = await readPackageJSON(path.resolve(__dirname, '../../tmpl'));
const templatePackageJSON = await readRawPackageJson(path.resolve(__dirname, '../../tmpl'));
packageJSON.config.forge = templatePackageJSON.config.forge;
setInitialForgeConfig(packageJSON);

@@ -6,7 +6,7 @@ import username from 'username';

import { setInitialForgeConfig } from '../../util/forge-config';
import installDepList from '../../util/install-dependencies';
import readPackageJSON from '../../util/read-package-json';
import { readRawPackageJson } from '../../util/read-package-json';

const d = debug('electron-forge:init:npm');

@@ -22,7 +22,7 @@ export const exactDevDeps = ['electron'];

export default async (dir: string) => {
await asyncOra('Initializing NPM Module', async () => {
const packageJSON = await readPackageJSON(path.resolve(__dirname, '../../../tmpl'));
const packageJSON = await readRawPackageJson(path.resolve(__dirname, '../../../tmpl'));
packageJSON.productName = packageJSON.name = path.basename(dir).toLowerCase();
packageJSON.author = await username();
setInitialForgeConfig(packageJSON);
@@ -6,10 +6,10 @@ import fs from 'fs-extra';
import path from 'path';

import getForgeConfig from '../util/forge-config';
import runHook from '../util/hook';
import { runHook, runMutatingHook } from '../util/hook';
import { info, warn } from '../util/messages';
import parseArchs from '../util/parse-archs';
import readPackageJSON from '../util/read-package-json';
import { readMutatedPackageJson } from '../util/read-package-json';
import resolveDir from '../util/resolve-dir';
import getCurrentOutDir from '../util/out-dir';
import getElectronVersion from '../util/electron-version';
@@ -147,9 +147,9 @@ export default async ({

info(interactive, `Making for the following targets: ${`${targets.map((t, i) => makers[i].name).join(', ')}`.cyan}`);

const packageJSON = await readPackageJSON(dir);
const packageJSON = await readMutatedPackageJson(dir, forgeConfig);
const appName = forgeConfig.packagerConfig.name || packageJSON.productName || packageJSON.name;
let outputs: ForgeMakeResult[] = [];
const outputs: ForgeMakeResult[] = [];

await runHook(forgeConfig, 'preMake');

@@ -197,12 +197,7 @@ export default async ({
}
}

const result = await runHook(forgeConfig, 'postMake', outputs) as ForgeMakeResult[] | undefined;
// If the postMake hooks modifies the locations / names of the outputs it must return
// the new locations so that the publish step knows where to look
if (Array.isArray(result)) {
outputs = result;
}

return outputs;
return await runMutatingHook(forgeConfig, 'postMake', outputs);
};
@@ -9,9 +9,9 @@ import pify from 'pify';
import packager from 'electron-packager';

import getForgeConfig from '../util/forge-config';
import runHook from '../util/hook';
import { runHook } from '../util/hook';
import { warn } from '../util/messages';
import readPackageJSON from '../util/read-package-json';
import { readMutatedPackageJson } from '../util/read-package-json';
import rebuildHook from '../util/rebuild';
import requireSearch from '../util/require-search';
import resolveDir from '../util/resolve-dir';
@@ -96,13 +96,13 @@ export default async ({
}
dir = resolvedDir;

const packageJSON = await readPackageJSON(dir);
const forgeConfig = await getForgeConfig(dir);
const packageJSON = await readMutatedPackageJson(dir, forgeConfig);

if (!packageJSON.main) {
throw 'packageJSON.main must be set to a valid entry point for your Electron app';
}

const forgeConfig = await getForgeConfig(dir);
const calculatedOutDir = outDir || getCurrentOutDir(dir, forgeConfig);
let packagerSpinner: OraImpl | null = null;

@@ -133,7 +133,7 @@ export default async ({
];

afterCopyHooks.push(async (buildPath, electronVersion, pPlatform, pArch, done) => {
const copiedPackageJSON = await readPackageJSON(buildPath);
const copiedPackageJSON = await readMutatedPackageJson(buildPath, forgeConfig);
if (copiedPackageJSON.config && copiedPackageJSON.config.forge) {
delete copiedPackageJSON.config.forge;
}
@@ -7,7 +7,7 @@ import fs from 'fs-extra';
import path from 'path';

import getForgeConfig from '../util/forge-config';
import readPackageJSON from '../util/read-package-json';
import { readMutatedPackageJson } from '../util/read-package-json';
import resolveDir from '../util/resolve-dir';
import PublishState from '../util/publish-state';
import getCurrentOutDir from '../util/out-dir';
@@ -73,9 +73,9 @@ const publish = async ({
throw 'Can\'t resume a dry run and use the provided makeResults at the same time';
}

let packageJSON = await readPackageJSON(dir);

const forgeConfig = await getForgeConfig(dir);
let packageJSON = await readMutatedPackageJson(dir, forgeConfig);

const calculatedOutDir = outDir || getCurrentOutDir(dir, forgeConfig);
const dryRunDir = path.resolve(calculatedOutDir, 'publish-dry-run');

@@ -4,11 +4,11 @@ import { StartOptions, ForgePlatform, ForgeArch } from '@electron-forge/shared-t
import { spawn, ChildProcess } from 'child_process';
import path from 'path';

import readPackageJSON from '../util/read-package-json';
import { readMutatedPackageJson } from '../util/read-package-json';
import rebuild from '../util/rebuild';
import resolveDir from '../util/resolve-dir';
import getForgeConfig from '../util/forge-config';
import runHook from '../util/hook';
import { runHook } from '../util/hook';
import getElectronVersion from '../util/electron-version';

export { StartOptions };
@@ -32,14 +32,13 @@ export default async ({
dir = resolvedDir;
});

const packageJSON = await readPackageJSON(dir);
const forgeConfig = await getForgeConfig(dir);
const packageJSON = await readMutatedPackageJson(dir, forgeConfig);

if (!packageJSON.version) {
throw `Please set your application's 'version' in '${dir}/package.json'.`;
}

const forgeConfig = await getForgeConfig(dir);

await rebuild(
dir,
getElectronVersion(packageJSON),
@@ -3,9 +3,9 @@ import fs from 'fs-extra';
import path from 'path';
import _template from 'lodash.template';

import readPackageJSON from './read-package-json';
import { readRawPackageJson } from './read-package-json';
import PluginInterface from './plugin-interface';
import runHook from './hook';
import { runMutatingHook } from './hook';

const underscoreCase = (str: string) => str.replace(/(.)([A-Z][a-z]+)/g, '$1_$2').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();

@@ -70,7 +70,7 @@ export function fromBuildIdentifier<T>(map: { [key: string]: T | undefined }) {
}

export default async (dir: string) => {
const packageJSON = await readPackageJSON(dir);
const packageJSON = await readRawPackageJson(dir);
let forgeConfig: ForgeConfig | string = packageJSON.config.forge;

if (typeof forgeConfig === 'string' && (await fs.pathExists(path.resolve(dir, forgeConfig)) || await fs.pathExists(path.resolve(dir, `${forgeConfig}.js`)))) {
@@ -109,7 +109,7 @@ export default async (dir: string) => {

forgeConfig.pluginInterface = new PluginInterface(dir, forgeConfig);

await runHook(forgeConfig, 'resolveForgeConfig', forgeConfig);
forgeConfig = await runMutatingHook(forgeConfig, 'resolveForgeConfig', forgeConfig);

return proxify<ForgeConfig>(forgeConfig.buildIdentifier || '', forgeConfig, 'ELECTRON_FORGE');
};
@@ -3,7 +3,7 @@ import debug from 'debug';

const d = debug('electron-forge:hook');

export default async (forgeConfig: ForgeConfig, hookName: string, ...hookArgs: any[]) => {
export const runHook = async (forgeConfig: ForgeConfig, hookName: string, ...hookArgs: any[]) => {
const hooks = forgeConfig.hooks;
if (hooks) {
d(`hook triggered: ${hookName}`);
@@ -14,3 +14,18 @@ export default async (forgeConfig: ForgeConfig, hookName: string, ...hookArgs: a
}
await forgeConfig.pluginInterface.triggerHook(hookName, hookArgs);
};

export const runMutatingHook = async <T>(forgeConfig: ForgeConfig, hookName: string, item: T): Promise<T> => {
const hooks = forgeConfig.hooks;
if (hooks) {
d(`hook triggered: ${hookName}`);
if (typeof hooks[hookName] === 'function') {
d('calling mutating hook:', hookName, 'with item:', item);
const result = await hooks[hookName](forgeConfig, item);
if (typeof result !== 'undefined') {
item = result;
}
}
}
return await forgeConfig.pluginInterface.triggerMutatingHook(hookName, item);
};
@@ -54,6 +54,18 @@ export default class PluginInterface implements IForgePluginInterface {
}
}

async triggerMutatingHook<T>(hookName: string, item: T) {
for (const plugin of this.plugins) {
if (typeof plugin.getHook === 'function') {
const hook = plugin.getHook(hookName);
if (hook) {
item = await hook(this.config, item);
}
}
}
return item;
}

async overrideStartLogic(opts: StartOptions) {
let newStartFn;
const claimed = [];
@@ -1,5 +1,11 @@
import { ForgeConfig } from '@electron-forge/shared-types';
import fs from 'fs-extra';
import path from 'path';

export default async (dir: string) =>
import { runMutatingHook } from './hook';

export const readRawPackageJson = async (dir: string) =>
await fs.readJson(path.resolve(dir, 'package.json'));

export const readMutatedPackageJson = async (dir: string, forgeConfig: ForgeConfig) =>
runMutatingHook(forgeConfig, 'readPackageJson', await readRawPackageJson(dir));
@@ -1,11 +1,14 @@
import debug from 'debug';
import fs from 'fs-extra';
import path from 'path';
import readPackageJSON from './read-package-json';
import { readRawPackageJson } from './read-package-json';
import getElectronVersion from './electron-version';

const d = debug('electron-forge:project-resolver');

// FIXME: If we want getElectronVersion to be overridable by plugins
// and / or forge config then we need to be able to resolve
// the dir without calling getElectronVersion
export default async (dir: string) => {
let mDir = dir;
let prevDir;
@@ -14,8 +17,10 @@ export default async (dir: string) => {
const testPath = path.resolve(mDir, 'package.json');
d('searching for project in:', mDir);
if (await fs.pathExists(testPath)) {
const packageJSON = await readPackageJSON(mDir);
const packageJSON = await readRawPackageJson(mDir);

// TODO: Move this check to inside the forge config resolver and use
// mutatedPackageJson reader
const electronVersion = getElectronVersion(packageJSON);
if (electronVersion) {
if (!/[0-9]/.test(electronVersion[0])) {
@@ -1,28 +1,46 @@
import { ForgeConfig } from '@electron-forge/shared-types';
import { expect } from 'chai';
import { stub } from 'sinon';
import { stub, SinonStub } from 'sinon';

import runHook from '../../src/util/hook';
import { runHook, runMutatingHook } from '../../src/util/hook';

const fakeConfig = {
pluginInterface: {
triggerHook: async () => false,
triggerMutatingHook: async (_: any, item: any) => item,
},
} as any as ForgeConfig;

describe('runHook', () => {
it('should not error when running non existent hooks', async () => {
await runHook(Object.assign({}, fakeConfig), 'magic');
describe('hooks', () => {
describe('runHook', () => {
it('should not error when running non existent hooks', async () => {
await runHook(Object.assign({}, fakeConfig), 'magic');
});

it('should not error when running a hook that is not a function', async () => {
await runHook(Object.assign({ hooks: { myHook: 'abc' } }, fakeConfig), 'abc');
});

it('should run the hook if it is provided as a function', async () => {
const myStub = stub();
myStub.returns(Promise.resolve());
await runHook(Object.assign({ hooks: { myHook: myStub } }, fakeConfig), 'myHook');
expect(myStub.callCount).to.equal(1);
});
});

it('should not error when running a hook that is not a function', async () => {
await runHook(Object.assign({ hooks: { myHook: 'abc' } }, fakeConfig), 'abc');
});
describe('runMutatingHook', () => {
it('should return the input when running non existent hooks', async () => {
expect(await runMutatingHook(Object.assign({}, fakeConfig), 'magic', 'input')).to.equal('input');
});

it('should run the hook if it is provided as a function', async () => {
const myStub = stub();
myStub.returns(Promise.resolve());
await runHook(Object.assign({ hooks: { myHook: myStub } }, fakeConfig), 'myHook');
expect(myStub.callCount).to.equal(1);
it('should return the mutated input when returned from a hook', async () => {
fakeConfig.pluginInterface.triggerMutatingHook = stub().returnsArg(1);
const myStub = stub();
myStub.returns(Promise.resolve('magneto'));
const output = await runMutatingHook(Object.assign({ hooks: { myHook: myStub } }, fakeConfig), 'myHook', 'input');
expect(output).to.equal('magneto');
expect((fakeConfig.pluginInterface.triggerMutatingHook as SinonStub).firstCall.args[1]).to.equal('magneto');
});
});
});
@@ -33,7 +33,9 @@ describe('publish', () => {
publish = proxyquire.noCallThru().load('../../src/api/publish', {
'./make': async (...args: any[]) => makeStub(...args),
'../util/resolve-dir': async (dir: string) => resolveStub(dir),
'../util/read-package-json': () => Promise.resolve(require('../fixture/dummy_app/package.json')),
'../util/read-package-json': {
readMutatedPackageJson: () => Promise.resolve(require('../fixture/dummy_app/package.json')),
},
'../util/forge-config': async () => {
const config = await (require('../../src/util/forge-config').default(path.resolve(__dirname, '../fixture/dummy_app')));

0 comments on commit 1b7e411

Please sign in to comment.
You can’t perform that action at this time.