diff --git a/common.js b/common.js index b619db98..baca3c2e 100644 --- a/common.js +++ b/common.js @@ -2,7 +2,6 @@ const os = require('os') const path = require('path') -const pify = require('pify') const sanitize = require('sanitize-filename') const yargs = require('yargs-parser') @@ -144,14 +143,6 @@ module.exports = { generateFinalPath: generateFinalPath, sanitizeAppName: sanitizeAppName, - promisifyHooks: function promisifyHooks (hooks, args) { - if (!hooks || !Array.isArray(hooks)) { - return Promise.resolve() - } - - return Promise.all(hooks.map(hookFn => pify(hookFn).apply(this, args))) - }, - info: info, warning: warning } diff --git a/docs/api.md b/docs/api.md index df46e75c..6e8723f0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -41,6 +41,32 @@ An array of functions to be called after your app directory has been copied to a - `arch` (*String*): The target architecture you are packaging for - `callback` (*Function*): Must be called once you have completed your actions +By default, the functions are called in parallel (via +[`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)). +If you need the functions called serially, there is a utility function provided: + +```javascript +const packager = require('electron-packager') +const serialHooks = require('electron-packager/hooks').serialHooks + +packager({ + // ... + afterCopy: [serialHooks([ + (buildPath, electronVersion, platform, arch, callback) => { + setTimeout(() => { + console.log('first function') + callback() + }, 1000) + }, + (buildPath, electronVersion, platform, arch, callback) => { + console.log('second function') + callback() + } + ])], + // ... +}) +``` + ##### `afterExtract` *Array of Functions* @@ -53,6 +79,32 @@ An array of functions to be called after Electron has been extracted to a tempor - `arch` (*String*): The target architecture you are packaging for - `callback` (*Function*): Must be called once you have completed your actions +By default, the functions are called in parallel (via +[`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)). +If you need the functions called serially, there is a utility function provided: + +```javascript +const packager = require('electron-packager') +const serialHooks = require('electron-packager/hooks').serialHooks + +packager({ + // ... + afterExtract: [serialHooks([ + (buildPath, electronVersion, platform, arch, callback) => { + setTimeout(() => { + console.log('first function') + callback() + }, 1000) + }, + (buildPath, electronVersion, platform, arch, callback) => { + console.log('second function') + callback() + } + ])], + // ... +}) +``` + ##### `afterPrune` *Array of Functions* @@ -68,6 +120,32 @@ in the temporary directory. Each function is called with five parameters: **NOTE:** None of these functions will be called if the `prune` option is `false`. +By default, the functions are called in parallel (via +[`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)). +If you need the functions called serially, there is a utility function provided: + +```javascript +const packager = require('electron-packager') +const serialHooks = require('electron-packager/hooks').serialHooks + +packager({ + // ... + afterPrune: [serialHooks([ + (buildPath, electronVersion, platform, arch, callback) => { + setTimeout(() => { + console.log('first function') + callback() + }, 1000) + }, + (buildPath, electronVersion, platform, arch, callback) => { + console.log('second function') + callback() + } + ])], + // ... +}) +``` + ##### `all` *Boolean* diff --git a/hooks.js b/hooks.js new file mode 100644 index 00000000..72059347 --- /dev/null +++ b/hooks.js @@ -0,0 +1,25 @@ +'use strict' + +const pify = require('pify') + +module.exports = { + promisifyHooks: function promisifyHooks (hooks, args) { + if (!hooks || !Array.isArray(hooks)) { + return Promise.resolve() + } + + return Promise.all(hooks.map(hookFn => pify(hookFn).apply(this, args))) + }, + serialHooks: function serialHooks (hooks) { + return function () { + const args = Array.prototype.splice.call(arguments, 0, arguments.length - 1) + const done = arguments[arguments.length - 1] + let result = Promise.resolve() + for (const hook of hooks) { + result = result.then(() => hook.apply(this, args)) + } + + return result.then(() => done()) // eslint-disable-line promise/no-callback-in-promise + } + } +} diff --git a/index.js b/index.js index 2523f60c..5e2cd462 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const download = require('./download') const extract = require('extract-zip') const fs = require('fs-extra') const getMetadataFromPackageJSON = require('./infer') +const hooks = require('./hooks') const ignore = require('./ignore') const metadata = require('./package.json') const nodeify = require('nodeify') @@ -71,7 +72,7 @@ class Packager { extractElectronZip (comboOpts, zipPath, buildDir) { debug(`Extracting ${zipPath} to ${buildDir}`) return pify(extract)(zipPath, { dir: buildDir }) - .then(() => common.promisifyHooks(this.opts.afterExtract, [buildDir, comboOpts.electronVersion, comboOpts.platform, comboOpts.arch])) + .then(() => hooks.promisifyHooks(this.opts.afterExtract, [buildDir, comboOpts.electronVersion, comboOpts.platform, comboOpts.arch])) } createApp (comboOpts, zipPath) { diff --git a/platform.js b/platform.js index 1515bd45..6da0f226 100644 --- a/platform.js +++ b/platform.js @@ -6,6 +6,7 @@ const fs = require('fs-extra') const path = require('path') const pify = require('pify') +const hooks = require('./hooks') const ignore = require('./ignore') const pruneModules = require('./prune').pruneModules @@ -101,7 +102,7 @@ class App { return fs.copy(this.opts.dir, this.originalResourcesAppDir, { filter: ignore.userIgnoreFilter(this.opts), dereference: this.opts.derefSymlinks - }).then(() => common.promisifyHooks(this.opts.afterCopy, [ + }).then(() => hooks.promisifyHooks(this.opts.afterCopy, [ this.originalResourcesAppDir, this.opts.electronVersion, this.opts.platform, @@ -132,7 +133,7 @@ class App { prune () { if (this.opts.prune || this.opts.prune === undefined) { return pruneModules(this.opts, this.originalResourcesAppDir) - .then(() => common.promisifyHooks(this.opts.afterPrune, [this.originalResourcesAppDir, this.opts.electronVersion, this.opts.platform, this.opts.arch])) + .then(() => hooks.promisifyHooks(this.opts.afterPrune, [this.originalResourcesAppDir, this.opts.electronVersion, this.opts.platform, this.opts.arch])) } return Promise.resolve() diff --git a/test/hooks.js b/test/hooks.js index 91c159ae..01604884 100644 --- a/test/hooks.js +++ b/test/hooks.js @@ -1,7 +1,9 @@ 'use strict' const config = require('./config.json') +const hooks = require('../hooks') const packager = require('..') +const test = require('ava') const util = require('./_util') function createHookTest (hookName) { @@ -32,3 +34,39 @@ function createHookTest (hookName) { createHookTest('afterCopy') createHookTest('afterPrune') createHookTest('afterExtract') + +test('promisifyHooks executes functions in parallel', t => { + let output = '0' + const timeoutFunc = (number, msTimeout) => { + return done => { + setTimeout(() => { + output += ` ${number}` + done() + }, msTimeout) + } + } + const testHooks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(number => + timeoutFunc(number, number % 2 === 0 ? 1000 : 0) + ) + + return hooks.promisifyHooks(testHooks) + .then(() => t.not(output, '0 1 2 3 4 5 6 7 8 9 10', 'should not be in sequential order')) +}) + +test('serialHooks executes functions serially', t => { + let output = '0' + const timeoutFunc = (number, msTimeout) => { + return () => new Promise(resolve => { // eslint-disable-line promise/avoid-new + setTimeout(() => { + output += ` ${number}` + resolve() + }, msTimeout) + }) + } + const testHooks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(number => + timeoutFunc(number, number % 2 === 0 ? 1000 : 0) + ) + + return hooks.serialHooks(testHooks)(() => output) + .then(result => t.is(result, '0 1 2 3 4 5 6 7 8 9 10', 'should be in sequential order')) +})