diff --git a/.eslintrc.json b/.eslintrc.json index e3578aa..024f6d9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "standard" + "extends": [ + "standard", + "plugin:promise/recommended" + ] } diff --git a/.travis.yml b/.travis.yml index 263a982..a622061 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,16 @@ addons: - wine env: - - DEBUG="electron-installer-windows" NODE_VERSION="10" + - DEBUG="electron-installer-windows" NODE_VERSION="12" - DEBUG="electron-installer-windows" NODE_VERSION="8" - - DEBUG="electron-installer-windows" NODE_VERSION="6" branches: only: - master - /^v\d+\.\d+\.\d+/ +cache: npm + before_install: nvm install $NODE_VERSION install: npm install script: npm test diff --git a/README.md b/README.md index 7ad575f..d7c5d02 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This tool relies on the awesome [Squirrel.Windows](https://github.com/Squirrel/S ## Requirements -This tool requires Node 6 or greater. +This tool requires Node 8 or greater. I'd recommend building your packages on your target platform, but if you have to run this on Mac OS X or Linux, you will need to install `mono` and `wine` through your package manager. @@ -20,13 +20,13 @@ You won't get an `.msi` installer though, only `.nupkg` and `.exe` installers. T For use from command-line: -``` +```shell $ npm install -g electron-installer-windows ``` For use in npm scripts or programmatically: -``` +```shell $ npm install --save-dev electron-installer-windows ``` @@ -58,7 +58,7 @@ Say your Electron app lives in `path/to/app` and has a structure like this: You now run `electron-packager` to build the app for Windows: -``` +```shell $ electron-packager . app --platform win32 --arch x64 --out dist/ ``` @@ -97,13 +97,13 @@ How do you turn that into a Windows package that your users can install? If you want to run `electron-installer-windows` straight from the command-line, install the package globally: -``` +```shell $ npm install -g electron-installer-windows ``` And point it to your built app: -``` +```shell $ electron-installer-windows --src dist/app-win32-x64/ --dest dist/installers/ ``` @@ -113,13 +113,13 @@ You'll end up with the package at `dist/installers/app-0.0.1-setup.exe`. If you want to run `electron-installer-windows` through npm, install the package locally: -``` +```shell $ npm install --save-dev electron-installer-windows ``` Edit the `scripts` section of your `package.json`: -```javascript +```json { "name": "app", "description": "An awesome app!", @@ -139,7 +139,7 @@ Edit the `scripts` section of your `package.json`: And run the script: -``` +```shell $ npm run setup ``` @@ -149,7 +149,7 @@ You'll end up with the package at `dist/installers/app-0.0.1-setup.exe`. Install the package locally: -``` +```shell $ npm install --save-dev electron-installer-windows ``` @@ -163,34 +163,29 @@ const options = { dest: 'dist/installers/' } -console.log('Creating package (this may take a while)') - -installer(options) - .then(() => console.log(`Successfully created package at ${options.dest}`)) - .catch(err => { - console.error(err, err.stack) - process.exit(1) - }) -``` -Alternatively, it is possible to use the callback pattern: -```javascript -installer(options, (err) => { - if (err) { +async function main (options) { + console.log('Creating package (this may take a while)') + try { + await installer(options) + console.log(`Successfully created package at ${options.dest}`) + } catch (err) { console.error(err, err.stack) process.exit(1) } - - console.log(`Successfully created package at ${options.dest}`) -}) +} +main(options) ``` You'll end up with the package at `dist/installers/app-0.0.1-setup.exe`. +_Note: As of 2.0.0, the Node-style callback pattern is no longer available. You can use [`util.callbackify`](https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_callbackify_original) if this is required for your use case._ + + ### Options Even though you can pass most of these options through the command-line interface, it may be easier to create a configuration file: -```javascript +```json { "dest": "dist/installers/", "icon": "resources/Icon.ico", @@ -202,7 +197,7 @@ Even though you can pass most of these options through the command-line interfac And pass that instead with the `config` option: -``` +```shell $ electron-installer-windows --src dist/app-win32-x64/ --config config.json ``` @@ -351,14 +346,14 @@ Internet Explorer's SmartScreen Filter and antivirus programs may flag your pack To generate the certificate, open the *Developer Command Prompt for Visual Studio* and execute the following: -``` +```shell $ makecert -sv my_private_key.pvk -n "CN=MyTestCertificate" my_test_certificate.cer -b 01/01/2016 -e 01/01/2026 -r $ pvk2pfx -pvk my_private_key.pvk -spc my_test_certificate.cer -pfx my_signing_key.pfx -po my_password ``` Now we can tell `electron-installer-windows` to sign the packages that it generates with that certificate: -``` +```shell $ electron-installer-windows --src dist/app-win32-x64/ --dest dist/installers/ --certificateFile my_signing_key.pfx --certificatePassword my_password ``` diff --git a/appveyor.yml b/appveyor.yml index f83fe2b..078bb16 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,27 +5,20 @@ init: environment: DEBUG: 'electron-installer-windows' - matrix: - - platform: x86 - nodejs_version: '10' - - platform: x64 - nodejs_version: '10' - - platform: x86 - nodejs_version: '8' - - platform: x64 - nodejs_version: '8' - - platform: x86 - nodejs_version: '6' - - platform: x64 - nodejs_version: '6' + - nodejs_version: "12" + - nodejs_version: "8" + +platform: + - x86 + - x64 branches: only: - master install: - - ps: Install-Product node $env:nodejs_version + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:PLATFORM - npm install build: off diff --git a/example/README.md b/example/README.md index 79940ae..fc10acd 100644 --- a/example/README.md +++ b/example/README.md @@ -2,7 +2,7 @@ From this directory, run the following commands to build the Windows packages for the app: -``` +```shell $ npm install $ npm run build ``` diff --git a/package.json b/package.json index d28bde5..42f8cf8 100644 --- a/package.json +++ b/package.json @@ -26,32 +26,30 @@ "test": "npm run lint && npm run spec" }, "engines": { - "node": ">=6.0.0" + "node": ">=8.0.0" }, "dependencies": { - "debug": "^4.1.0", - "electron-installer-common": "^0.6.1", - "fs-extra": "^7.0.1", - "glob": "^7.1.3", - "glob-promise": "^3.4.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.7.0", + "fs-extra": "^8.0.1", + "glob": "^7.1.4", "lodash": "^4.17.11", - "nodeify": "^1.0.1", "parse-author": "^2.0.0", "which": "^1.3.1", - "yargs": "^13.2.2" + "yargs": "^13.2.4" }, "devDependencies": { "chai": "^4.2.0", - "eslint": "^5.9.0", + "eslint": "^5.16.0", "eslint-config-standard": "^12.0.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^8.0.0", - "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-import": "^2.17.3", + "eslint-plugin-node": "^9.1.0", + "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0", - "finalhandler": "^1.1.1", - "mocha": "^6.1.2", + "finalhandler": "^1.1.2", + "mocha": "^6.1.4", "promise-retry": "^1.1.1", - "serve-static": "^1.13.2", - "tmp-promise": "^1.0.5" + "serve-static": "^1.14.1", + "tmp-promise": "^2.0.1" } } diff --git a/src/installer.js b/src/installer.js index e29ae25..6eb2da4 100644 --- a/src/installer.js +++ b/src/installer.js @@ -3,10 +3,10 @@ const common = require('electron-installer-common') const debug = require('debug') const fs = require('fs-extra') -const glob = require('glob-promise') -const nodeify = require('nodeify') const parseAuthor = require('parse-author') const path = require('path') +const { promisify } = require('util') +const glob = promisify(require('glob')) const spawn = require('./spawn') @@ -52,16 +52,15 @@ class SquirrelInstaller extends common.ElectronInstaller { /** * Copy the application into the package. */ - copyApplication () { - return super.copyApplication() - .then(() => this.copySquirrelUpdater()) + async copyApplication () { + await super.copyApplication() + return this.copySquirrelUpdater() } copySquirrelUpdater () { const updateSrc = path.join(this.vendorDir, 'squirrel', 'Squirrel.exe') const updateDest = path.join(this.stagingAppDir, 'Update.exe') - return fs.copy(updateSrc, updateDest) - .catch(common.wrapError('copying Squirrel updater')) + return common.wrapError('copying Squirrel updater', async () => fs.copy(updateSrc, updateDest)) } /** @@ -81,8 +80,7 @@ class SquirrelInstaller extends common.ElectronInstaller { '-NoDefaultExcludes' ] - return spawn(cmd, args, this.options.logger) - .catch(common.wrapError('creating package with NuGet')) + return common.wrapError('creating package with NuGet', async () => spawn(cmd, args, this.options.logger)) } /** @@ -94,8 +92,7 @@ class SquirrelInstaller extends common.ElectronInstaller { const src = path.resolve(__dirname, '../resources/spec.ejs') this.options.logger(`Creating spec file at ${this.specPath}`) - return this.createTemplatedFile(src, this.specPath) - .catch(common.wrapError('creating spec file')) + return common.wrapError('creating spec file', async () => this.createTemplatedFile(src, this.specPath)) } /** @@ -105,48 +102,46 @@ class SquirrelInstaller extends common.ElectronInstaller { const packagePattern = path.join(this.stagingDir, 'nuget', '*.nupkg') this.options.logger(`Finding package with pattern ${packagePattern}`) - return glob(packagePattern) - .then(files => files[0]) - .catch(common.wrapError('finding package with pattern')) + return common.wrapError('finding package with pattern', async () => { + const files = await glob(packagePattern) + return files[0] + }) } /** * Get the hash of default options for the installer. Some come from the info * read from `package.json`, and some are hardcoded. */ - generateDefaults () { - return common.readMetadata(this.userSupplied) - .then(pkg => { - pkg = pkg || {} + async generateDefaults () { + const pkg = (await common.readMetadata(this.userSupplied)) || {} - const authors = pkg.author ? [typeof pkg.author === 'string' ? parseAuthor(pkg.author).name : pkg.author.name] : undefined + const authors = pkg.author ? [typeof pkg.author === 'string' ? parseAuthor(pkg.author).name : pkg.author.name] : undefined - this.defaults = Object.assign(common.getDefaultsFromPackageJSON(pkg), { - version: pkg.version || '0.0.0', + this.defaults = Object.assign(common.getDefaultsFromPackageJSON(pkg), { + version: pkg.version || '0.0.0', - copyright: pkg.copyright || (authors && `Copyright \u00A9 ${new Date().getFullYear()} ${authors.join(', ')}`), - authors: authors, - owners: authors, + copyright: pkg.copyright || (authors && `Copyright \u00A9 ${new Date().getFullYear()} ${authors.join(', ')}`), + authors: authors, + owners: authors, - exe: pkg.name ? `${pkg.name}.exe` : 'electron.exe', - icon: path.resolve(__dirname, '../resources/icon.ico'), - animation: path.resolve(__dirname, '../resources/animation.gif'), + exe: pkg.name ? `${pkg.name}.exe` : 'electron.exe', + icon: path.resolve(__dirname, '../resources/icon.ico'), + animation: path.resolve(__dirname, '../resources/animation.gif'), - iconUrl: undefined, + iconUrl: undefined, - tags: [], + tags: [], - certificateFile: undefined, - certificatePassword: undefined, - signWithParams: undefined, + certificateFile: undefined, + certificatePassword: undefined, + signWithParams: undefined, - remoteReleases: undefined, + remoteReleases: undefined, - noMsi: false - }) + noMsi: false + }) - return this.defaults - }) + return this.defaults } /** @@ -164,12 +159,14 @@ class SquirrelInstaller extends common.ElectronInstaller { if (!this.options.authors) { throw new Error(`No Authors provided. Please set an author in the app's package.json or provide it in the this.options.`) } + + return this.options } /** * Releasify everything using `squirrel`. */ - releasifyPackage () { + async releasifyPackage () { this.options.logger(`Releasifying package at ${this.stagingDir}`) const cmd = path.join(this.vendorDir, 'squirrel', process.platform === 'win32' ? 'Squirrel.com' : 'Squirrel-Mono.exe') @@ -200,17 +197,17 @@ class SquirrelInstaller extends common.ElectronInstaller { args.push('--no-msi') } - return this.findPackage() - .then(pkg => { - args.unshift('--releasify', pkg) - return spawn(cmd, args, this.options.logger) - }).catch(common.wrapError('releasifying package')) + return common.wrapError('releasifying package', async () => { + const pkg = await this.findPackage() + args.unshift('--releasify', pkg) + return spawn(cmd, args, this.options.logger) + }) } /** * Sync remote releases. */ - syncRemoteReleases () { + async syncRemoteReleases () { if (!this.options.remoteReleases) { return } @@ -225,43 +222,32 @@ class SquirrelInstaller extends common.ElectronInstaller { this.squirrelDir ] - return fs.ensureDir(this.squirrelDir, '0755') - .then(() => spawn(cmd, args, this.options.logger)) - .catch(common.wrapError('syncing remote releases')) + return common.wrapError('syncing remote releases', async () => { + await fs.ensureDir(this.squirrelDir, '0755') + return spawn(cmd, args, this.options.logger) + }) } } /* ************************************************************************** */ -module.exports = (data, callback) => { +module.exports = async data => { data.rename = data.rename || defaultRename data.logger = data.logger || defaultLogger - if (callback) { - console.warn('The node-style callback is deprecated. In a future major version, it will be' + - 'removed in favor of a Promise-based async style.') - } - const installer = new SquirrelInstaller(data) - const promise = installer.generateDefaults() - .then(() => installer.generateOptions()) - .then(() => data.logger(`Creating package with options\n${JSON.stringify(installer.options, null, 2)}`)) - .then(() => installer.createStagingDir()) - .then(() => installer.createContents()) - .then(() => installer.createPackage()) - .then(() => installer.syncRemoteReleases()) - .then(() => installer.releasifyPackage()) - .then(() => installer.movePackage()) - .then(() => { - data.logger(`Successfully created package at ${installer.options.dest}`) - return installer.options - }).catch(err => { - data.logger(common.errorMessage('creating package', err)) - throw err - }) - - return nodeify(promise, callback) + await installer.generateDefaults() + await installer.generateOptions() + data.logger(`Creating package with options\n${JSON.stringify(installer.options, null, 2)}`) + await installer.createStagingDir() + await installer.createContents() + await installer.createPackage() + await installer.syncRemoteReleases() + await installer.releasifyPackage() + await installer.movePackage() + data.logger(`Successfully created package at ${installer.options.dest}`) + return installer.options } module.exports.Installer = SquirrelInstaller diff --git a/src/spawn.js b/src/spawn.js index d320473..5c7dc1f 100644 --- a/src/spawn.js +++ b/src/spawn.js @@ -23,7 +23,7 @@ function updateExecutableMissingException (err, updateError) { } } -module.exports = function (cmd, args, logger) { +module.exports = async function (cmd, args, logger) { if (process.platform !== 'win32') { args.unshift(cmd) cmd = 'mono' diff --git a/test/cli.js b/test/cli.js index ca34b58..c54f14f 100644 --- a/test/cli.js +++ b/test/cli.js @@ -26,9 +26,9 @@ describe('cli', function () { describe('with a releases server', function (test) { const server = new Server('test/fixtures/releases/', 3000) - before(() => server.runServer()) + before(async () => server.runServer()) - after(() => server.closeServer()) + after(async () => server.closeServer()) describeCLI('with an app with asar with the same remote release', true, { remoteReleases: 'http://localhost:3000/foo/' diff --git a/test/helpers/access_helper.js b/test/helpers/access_helper.js index cf71a6f..64d1424 100644 --- a/test/helpers/access_helper.js +++ b/test/helpers/access_helper.js @@ -5,24 +5,34 @@ const path = require('path') const retry = require('promise-retry') // `fs.access` which retries three times. -module.exports.testAccess = (path) => retry((retry, number) => { - return fs.access(path) - .then(() => 'done accessing') - .catch(retry) -}, { retries: 3, minTimeout: 500 }) +async function testAccess (path) { + return retry((retry, number) => { + return fs.access(path) + .catch(retry) + }, { + retries: 3, + minTimeout: 500 + }) +} -module.exports.access = (desc, dir, filename) => { - it(desc, () => module.exports.testAccess(path.join(dir, filename))) +function access (desc, dir, filename) { + it(desc, async () => testAccess(path.join(dir, filename))) } -module.exports.accessAll = (appName, dir, cli) => { +function accessAll (appName, dir, cli) { let test cli ? test = 'setup' : test = 'installer' - module.exports.access('generates a `RELEASES` manifest', dir, 'RELEASES') - module.exports.access('generates a `.nupkg` package', dir, `${appName}-0.0.1-full.nupkg`) - module.exports.access('generates a `.exe` package', dir, `${appName}-0.0.1-${test}.exe`) + access('generates a `RELEASES` manifest', dir, 'RELEASES') + access('generates a `.nupkg` package', dir, `${appName}-0.0.1-full.nupkg`) + access('generates a `.exe` package', dir, `${appName}-0.0.1-${test}.exe`) if (process.platform === 'win32') { - module.exports.access('generates a `.msi` package', dir, `${appName}-0.0.1-${test}.msi`) + access('generates a `.msi` package', dir, `${appName}-0.0.1-${test}.msi`) } } + +module.exports = { + testAccess, + access, + accessAll +} diff --git a/test/helpers/describe_cli.js b/test/helpers/describe_cli.js index 56e7e16..81e4bfe 100644 --- a/test/helpers/describe_cli.js +++ b/test/helpers/describe_cli.js @@ -4,9 +4,7 @@ const chai = require('chai') const fs = require('fs-extra') const { spawn } = require('electron-installer-common') const tmp = require('tmp-promise') -const access = require('./access_helper').access -const testAccess = require('./access_helper').testAccess -const accessAll = require('./access_helper').accessAll +const { access, accessAll } = require('./access_helper') function printLogs (logs) { if (process.env.DEBUG === 'electron-installer-windows') { @@ -40,22 +38,18 @@ module.exports = function (desc, asar, options) { if (options.remoteReleases) args.push('--remoteReleases', options.remoteReleases) describe(desc, test => { - before((done) => { - spawn('./src/cli.js', args, null, null) - .then(logs => printLogs(logs)) - .then(() => done()) + before(async () => { + const logs = await spawn('./src/cli.js', args, null, null) + printLogs(logs) }) - after(() => fs.remove(options.dest)) + after(async () => fs.remove(options.dest)) accessAll(appName, options.dest, true) if (options.remoteReleases && asar) { - it('does not generate a delta `.nupkg` package', () => { - return testAccess(`${options.dest}/${appName}-0.0.1-delta.nupkg'`) - .then(() => { - throw new Error('delta `.nupkg` was created') - }).catch(error => chai.expect(error.message).to.have.string('no such file or directory')) + it('does not generate a delta `.nupkg` package', async () => { + return chai.expect(await fs.pathExists(`${options.dest}/${appName}-0.0.1-delta.nupkg'`)).to.be.false }) } diff --git a/test/helpers/describe_installer.js b/test/helpers/describe_installer.js index a346796..163a1e1 100644 --- a/test/helpers/describe_installer.js +++ b/test/helpers/describe_installer.js @@ -4,9 +4,7 @@ const chai = require('chai') const fs = require('fs-extra') const path = require('path') const tmp = require('tmp-promise') -const access = require('./access_helper').access -const testAccess = require('./access_helper').testAccess -const accessAll = require('./access_helper').accessAll +const { access, testAccess, accessAll } = require('./access_helper') const installer = require('../..') @@ -14,18 +12,20 @@ module.exports = function describeInstaller (desc, asar, testOptions) { const [appName, options] = installerOptions(asar, testOptions) describe(desc, test => { - before(() => installer(options)) + before(async () => installer(options)) - after(() => fs.remove(options.dest)) + after(async () => fs.remove(options.dest)) accessAll(appName, options.dest, false) if (testOptions.remoteReleases && asar) { - it('does not generate a delta `.nupkg` package', () => { - return testAccess(`${options.dest}/${appName}-0.0.1-delta.nupkg`) - .then(() => { - throw new Error('delta `.nupkg` was created') - }).catch(error => chai.expect(error.message).to.have.string('no such file or directory')) + it('does not generate a delta `.nupkg` package', async () => { + try { + await testAccess(`${options.dest}/${appName}-0.0.1-delta.nupkg`) + throw new Error('delta `.nupkg` was created') + } catch (error) { + chai.expect(error.message).to.have.string('no such file or directory') + } }) } @@ -40,12 +40,15 @@ module.exports.describeInstallerWithException = function describeInstallerWithEx options.src = testOptions.src describe(desc, test => { - it('throws an error', () => { - return installer(options) - .catch(error => chai.expect(error.message).to.match(errorRegex)) + it('throws an error', async () => { + try { + await installer(options) + } catch (error) { + chai.expect(error.message).to.match(errorRegex) + } }) - after(() => fs.remove(options.dest)) + after(async () => fs.remove(options.dest)) }) } diff --git a/test/installer.js b/test/installer.js index b468578..d184c94 100644 --- a/test/installer.js +++ b/test/installer.js @@ -57,9 +57,9 @@ describe('module', function () { describe('with a releases server', function (test) { const server = new Server('test/fixtures/releases/', 3000) - before(() => server.runServer()) + before(async () => server.runServer()) - after(() => server.closeServer()) + after(async () => server.closeServer()) describeInstaller('with an app with asar with the same remote release', true, { productDescription: 'Just a test.',