api/make.js
import 'colors';
import fs from 'fs-extra';
import path from 'path';
import asyncOra from '../util/ora-handler';
import electronHostArch from '../util/electron-host-arch';
import getForgeConfig from '../util/forge-config';
import runHook from '../util/hook';
import { info, warn } from '../util/messages';
import readPackageJSON from '../util/read-package-json';
import { requireSearchRaw } from '../util/require-search';
import resolveDir from '../util/resolve-dir';
import packager from './package';
/**
* @typedef {Object} MakeOptions
* @property {string} [dir=process.cwd()] The path to the app from which distributables are generated
* @property {boolean} [interactive=false] Whether to use sensible defaults or prompt the user visually
* @property {boolean} [skipPackage=false] Whether to skip the pre-make packaging step
* @property {Array<string>} [overrideTargets] An array of make targets to override your forge config
* @property {string} [arch=host architecture] The target architecture
* @property {string} [platform=process.platform] The target platform.
* @property {string} [outDir=`${dir}/out`] The path to the directory containing generated distributables
*/
/**
* @typedef {Object} MakeResult
* @property {Array<string>} artifacts An array of paths to artifacts generated for this make run
* @property {Object} packageJSON The state of the package.json file when the make happened
* @property {string} platform The platform this make run was for
* @property {string} arch The arch this make run was for
*/
/**
* Make distributables for an Electron application.
*
* @param {MakeOptions} providedOptions - Options for the make method
* @return {Promise<Array<MakeResult>>} Will resolve when the make process is complete
*/
export default async (providedOptions = {}) => {
// eslint-disable-next-line prefer-const, no-unused-vars
let { dir, interactive, skipPackage, overrideTargets, arch, platform } = Object.assign({
dir: process.cwd(),
interactive: false,
skipPackage: false,
arch: electronHostArch(),
platform: process.platform,
}, providedOptions);
const outDir = providedOptions.outDir || path.resolve(dir, 'out');
asyncOra.interactive = interactive;
let forgeConfig;
await asyncOra('Resolving Forge Config', async () => {
dir = await resolveDir(dir);
if (!dir) {
throw 'Failed to locate makeable Electron application';
}
forgeConfig = await getForgeConfig(dir);
});
if (!['darwin', 'win32', 'linux'].includes(platform)) {
throw new Error(`'${platform}' is an invalid platform. Choices are 'darwin', 'win32' or 'linux'`);
}
const makers = {};
const targets = overrideTargets || forgeConfig.make_targets[platform];
for (const target of targets) {
const maker = requireSearchRaw(__dirname, [
`../makers/${platform}/${target}.js`,
`../makers/generic/${target}.js`,
`electron-forge-maker-${target}`,
target,
path.resolve(dir, target),
path.resolve(dir, 'node_modules', target),
]);
if (!maker) {
throw new Error([
'Could not find a build target with the name: ',
`${target} for the platform: ${platform}`,
].join(''));
}
if (!maker.isSupportedOnCurrentPlatform) {
throw new Error([
`Maker for target ${target} is incompatible with this version of `,
'electron-forge, please upgrade or contact the maintainer ',
'(needs to implement \'isSupportedOnCurrentPlatform)\')',
].join(''));
}
if (!await maker.isSupportedOnCurrentPlatform()) {
throw new Error([
`Cannot build for ${platform} target ${target}: the maker declared `,
`that it cannot run on ${process.platform}`,
].join(''));
}
makers[target] = maker.default || maker;
}
if (!skipPackage) {
info(interactive, 'We need to package your application before we can make it'.green);
await packager({
dir,
interactive,
arch,
platform,
outDir,
});
} else {
warn(interactive, 'WARNING: Skipping the packaging step, this could result in an out of date build'.red);
}
const declaredArch = arch;
info(interactive, 'Making for the following targets:', `${targets.join(', ')}`.cyan);
let targetArchs = declaredArch.split(',');
if (declaredArch === 'all') {
switch (platform) {
case 'darwin':
targetArchs = ['x64'];
break;
case 'linux':
targetArchs = ['ia32', 'x64', 'armv7l'];
break;
case 'win32':
default:
targetArchs = ['ia32', 'x64'];
break;
}
}
const packageJSON = await readPackageJSON(dir);
const appName = forgeConfig.electronPackagerConfig.name || packageJSON.productName || packageJSON.name;
let outputs = [];
await runHook(forgeConfig, 'preMake');
for (const targetArch of targetArchs) {
const packageDir = path.resolve(outDir, `${appName}-${platform}-${targetArch}`);
if (!(await fs.pathExists(packageDir))) {
throw new Error(`Couldn't find packaged app at: ${packageDir}`);
}
for (const target of targets) {
const maker = makers[target];
// eslint-disable-next-line no-loop-func
await asyncOra(`Making for target: ${target.cyan} - On platform: ${platform.cyan} - For arch: ${targetArch.cyan}`, async () => {
try {
const artifacts = await maker({
dir: packageDir,
appName,
targetPlatform: platform,
targetArch,
forgeConfig,
packageJSON,
});
outputs.push({
artifacts,
packageJSON,
platform,
arch: targetArch,
});
} catch (err) {
if (err) {
throw {
message: `An error occured while making for target: ${target}`,
stack: `${err.message}\n${err.stack}`,
};
} else {
throw new Error(`An unknown error occured while making for target: ${target}`);
}
}
});
}
}
const result = await runHook(forgeConfig, 'postMake', outputs);
// 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;
};