From 4fd4be3e072b75da6dd88dc241beb0fc461ee9c5 Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Sun, 23 Apr 2017 15:53:22 -0700 Subject: [PATCH] Add cnpm support and refactor --- .travis.yml | 2 - NEWS.md | 4 ++ common.js | 15 +----- docs/api.md | 17 ++++++- package.json | 3 +- prune.js | 38 +++++++++++++++ test/basic.js | 44 ------------------ test/ci/before_install.sh | 8 +++- test/index.js | 1 + test/prune.js | 97 +++++++++++++++++++++++++++++++++++++++ usage.txt | 4 +- 11 files changed, 169 insertions(+), 64 deletions(-) create mode 100644 prune.js create mode 100644 test/prune.js diff --git a/.travis.yml b/.travis.yml index 9fb3fd76..60b63759 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,5 +32,3 @@ env: # prevent wine popup dialogs about installing additional packages - WINEDLLOVERRIDES="mscoree,mshtml=" - WINEDEBUG="-all" - # temporarily enable debug logging to diagnose flaky spec failures - - DEBUG=electron-packager diff --git a/NEWS.md b/NEWS.md index 223e23ee..0716dfbc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +* `packageManager` (`--package-manager` via CLI) option (#618) + ## [8.6.0] - 2017-03-14 ### Added diff --git a/common.js b/common.js index 8371fe22..058f2e8f 100644 --- a/common.js +++ b/common.js @@ -1,7 +1,6 @@ 'use strict' const asar = require('asar') -const child = require('child_process') const debug = require('debug')('electron-packager') const download = require('electron-download') const fs = require('fs-extra') @@ -9,6 +8,7 @@ const ignore = require('./ignore') const minimist = require('minimist') const os = require('os') const path = require('path') +const pruneModules = require('./prune').pruneModules const sanitize = require('sanitize-filename') const semver = require('semver') const series = require('run-series') @@ -88,18 +88,6 @@ function asarApp (appPath, asarOptions, cb) { }) } -function pruneModules (opts, appPath, cb) { - var pmName = opts['package-manager'] - if (pmName !== undefined && pmName === 'yarn') { - child.exec('yarn install --production ', { cwd: appPath }, cb) - } else if (pmName !== undefined && pmName !== 'npm') { - warning(`specified package-manager "${pmName}" is not available, use {"npm", "yarn"} instead`) - } else { - // defaults to npm - child.exec('npm prune --production', { cwd: appPath }, cb) - } -} - function isPlatformMac (platform) { return platform === 'darwin' || platform === 'mas' } @@ -210,6 +198,7 @@ module.exports = { 'app-copyright': 'appCopyright', 'app-version': 'appVersion', 'build-version': 'buildVersion', + 'package-manager': 'packageManager', 'app-bundle-id': 'appBundleId', 'app-category-type': 'appCategoryType', 'extend-info': 'extendInfo', diff --git a/docs/api.md b/docs/api.md index 19a79195..556dcbb9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -211,7 +211,19 @@ The base directory where the finished package(s) are created. Whether to replace an already existing output directory for a given platform (`true`) or skip recreating it (`false`). -#### `platform` +##### `packageManager` + +*String* (default: `npm`) + +The package manager used to [prune](#prune) `devDependencies` modules from the outputted Electron +app. Supported package managers: + +* [`npm`](https://npmjs.com/) +* [`cnpm`](https://github.com/cnpm/cnpm) (Does not currently work with Windows, see + [GitHub issue](https://github.com/electron-userland/electron-packager/issues/515#issuecomment-297604044)) +* [`yarn`](https://yarnpkg.com/) + +##### `platform` *String* (default: the arch of the host computer running Node) @@ -227,7 +239,8 @@ The non-`all` values correspond to the platform names used by [Electron releases *Boolean* (default: `true`) -Runs [`npm prune --production`](https://docs.npmjs.com/cli/prune) before starting to package the app. +Runs the [package manager](#packagemanager) command to remove all of the packages specified in the +`devDependencies` section of `package.json` from the outputted Electron app. ##### `quiet` diff --git a/package.json b/package.json index fc5318b0..b9cebfb3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "pkg-up": "^1.0.0", "rimraf": "^2.3.2", "run-waterfall": "^1.1.1", - "tape": "^4.0.0" + "tape": "^4.0.0", + "which": "^1.2.14" }, "engines": { "node": ">= 4.0" diff --git a/prune.js b/prune.js new file mode 100644 index 00000000..2ffd02a7 --- /dev/null +++ b/prune.js @@ -0,0 +1,38 @@ +'use strict' + +const child = require('child_process') +const debug = require('debug')('electron-packager') + +const knownPackageManagers = ['npm', 'cnpm', 'yarn'] + +function pruneCommand (packageManager) { + switch (packageManager) { + case 'npm': + case 'cnpm': + return `${packageManager} prune --production` + case 'yarn': + return `${packageManager} install --production` + } +} + +function pruneModules (opts, appPath, cb) { + const packageManager = opts.packageManager || 'npm' + + if (packageManager === 'cnpm' && process.platform === 'win32') { + return cb(new Error('cnpm support does not currently work with Windows, see: https://github.com/electron-userland/electron-packager/issues/515#issuecomment-297604044')) + } + + const command = pruneCommand(packageManager) + + if (command) { + debug(`Pruning modules via: ${command}`) + child.exec(command, { cwd: appPath }, cb) + } else { + cb(new Error(`Unknown package manager "${packageManager}". Known package managers: ${knownPackageManagers.join(', ')}`)) + } +} + +module.exports = { + pruneCommand: pruneCommand, + pruneModules: pruneModules +} diff --git a/test/basic.js b/test/basic.js index 14d7a750..9faf67f7 100644 --- a/test/basic.js +++ b/test/basic.js @@ -122,44 +122,6 @@ function createOutTest (opts) { } } -function createPruneOptionTest (baseOpts, prune, testMessage) { - return (t) => { - t.timeoutAfter(config.timeout) - - let opts = Object.create(baseOpts) - opts.name = 'basicTest' - opts.dir = path.join(__dirname, 'fixtures', 'basic') - opts.prune = prune - - let finalPath - let resourcesPath - - waterfall([ - (cb) => { - packager(opts, cb) - }, (paths, cb) => { - finalPath = paths[0] - fs.stat(finalPath, cb) - }, (stats, cb) => { - t.true(stats.isDirectory(), 'The expected output directory should exist') - resourcesPath = path.join(finalPath, util.generateResourcesPath(opts)) - fs.stat(resourcesPath, cb) - }, (stats, cb) => { - t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory') - fs.stat(path.join(resourcesPath, 'app', 'node_modules', 'run-series'), cb) - }, (stats, cb) => { - t.true(stats.isDirectory(), 'npm dependency should exist under app/node_modules') - fs.exists(path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'), (exists) => { - t.equal(!prune, exists, testMessage) - cb() - }) - } - ], (err) => { - t.end(err) - }) - } -} - function createOverwriteTest (opts) { return function (t) { t.timeoutAfter(config.timeout * 2) // Multiplied since this test packages the application twice @@ -370,12 +332,6 @@ test('cannot build apps where the name ends in " Helper"', (t) => { util.testSinglePlatform('defaults test', createDefaultsTest) util.testSinglePlatform('out test', createOutTest) -util.testSinglePlatform('prune test', (baseOpts) => { - return createPruneOptionTest(baseOpts, true, 'npm devDependency should NOT exist under app/node_modules') -}) -util.testSinglePlatform('prune=false test', (baseOpts) => { - return createPruneOptionTest(baseOpts, false, 'npm devDependency should exist under app/node_modules') -}) util.testSinglePlatform('overwrite test', createOverwriteTest) util.testSinglePlatform('tmpdir test', createTmpdirTest) util.testSinglePlatform('tmpdir test', createDisableTmpdirUsingTest) diff --git a/test/ci/before_install.sh b/test/ci/before_install.sh index 2ccd96b6..6ee9068f 100755 --- a/test/ci/before_install.sh +++ b/test/ci/before_install.sh @@ -6,9 +6,11 @@ case "$TRAVIS_OS_NAME" in # Not using Trusty containers because it can't install wine1.6(-i386), # see: https://github.com/travis-ci/travis-ci/issues/6460 sudo rm /etc/apt/sources.list.d/google-chrome.list + sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg + echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y wine1.6 + sudo apt-get install -y wine1.6 yarn ;; "osx") # Create CA @@ -29,5 +31,9 @@ case "$TRAVIS_OS_NAME" in npm install wine-darwin@1.9.17-1 # Setup ~/.wine by running a command ./node_modules/.bin/wine hostname + # Install yarn + npm install -g yarn ;; esac + +npm install -g cnpm diff --git a/test/index.js b/test/index.js index 580148ad..3f51dd30 100644 --- a/test/index.js +++ b/test/index.js @@ -53,6 +53,7 @@ series(setupFuncs, (error) => { require('./infer') require('./hooks') require('./multitarget') + require('./prune') require('./win32') if (process.platform !== 'win32') { diff --git a/test/prune.js b/test/prune.js new file mode 100644 index 00000000..5dcc95be --- /dev/null +++ b/test/prune.js @@ -0,0 +1,97 @@ +'use strict' + +const config = require('./config.json') +const fs = require('fs-extra') +const packager = require('..') +const path = require('path') +const prune = require('../prune') +const test = require('tape') +const util = require('./util') +const waterfall = require('run-waterfall') +const which = require('which') + +function createPruneOptionTest (baseOpts, prune, testMessage) { + return (t) => { + t.timeoutAfter(config.timeout) + + let opts = Object.create(baseOpts) + opts.name = 'basicTest' + opts.dir = path.join(__dirname, 'fixtures', 'basic') + opts.prune = prune + + let finalPath + let resourcesPath + + waterfall([ + (cb) => { + packager(opts, cb) + }, (paths, cb) => { + finalPath = paths[0] + fs.stat(finalPath, cb) + }, (stats, cb) => { + t.true(stats.isDirectory(), 'The expected output directory should exist') + resourcesPath = path.join(finalPath, util.generateResourcesPath(opts)) + fs.stat(resourcesPath, cb) + }, (stats, cb) => { + t.true(stats.isDirectory(), 'The output directory should contain the expected resources subdirectory') + fs.stat(path.join(resourcesPath, 'app', 'node_modules', 'run-series'), cb) + }, (stats, cb) => { + t.true(stats.isDirectory(), 'package.json dependency should exist under app/node_modules') + fs.exists(path.join(resourcesPath, 'app', 'node_modules', 'run-waterfall'), (exists) => { + t.equal(!prune, exists, testMessage) + cb() + }) + } + ], (err) => { + t.end(err) + }) + } +} + +test('pruneCommand returns the correct command when passing a known package manager', (t) => { + t.equal(prune.pruneCommand('npm'), 'npm prune --production', 'passing npm gives the npm prune command') + t.equal(prune.pruneCommand('cnpm'), 'cnpm prune --production', 'passing cnpm gives the cnpm prune command') + t.equal(prune.pruneCommand('yarn'), 'yarn install --production', 'passing yarn gives the yarn "prune command"') + t.end() +}) + +test('pruneCommand returns null when the package manager is unknown', (t) => { + t.notOk(prune.pruneCommand('unknown-package-manager')) + t.end() +}) + +test('pruneModules returns an error when the package manager is unknown', (t) => { + prune.pruneModules({packageManager: 'unknown-package-manager'}, '/tmp/app-path', (err) => { + t.ok(err, 'error returned') + t.end() + }) +}) + +if (process.platform === 'win32') { + test('pruneModules returns an error when trying to use cnpm on Windows', (t) => { + prune.pruneModules({packageManager: 'cnpm'}, '/tmp/app-path', (err) => { + t.ok(err, 'error returned') + t.end() + }) + }) +} + +// This is not in the below loop so that it tests the default packageManager option. +util.testSinglePlatform('prune test with npm', (baseOpts) => { + return createPruneOptionTest(baseOpts, true, 'package.json devDependency should NOT exist under app/node_modules') +}) + +for (const packageManager of ['cnpm', 'yarn']) { + which(packageManager, (err, resolvedPath) => { + if (err) return + + util.testSinglePlatform(`prune test with ${packageManager}`, (baseOpts) => { + const opts = Object.assign({packageManager: packageManager}, baseOpts) + return createPruneOptionTest(opts, true, 'package.json devDependency should NOT exist under app/node_modules') + }) + }) +} + +util.testSinglePlatform('prune=false test', (baseOpts) => { + return createPruneOptionTest(baseOpts, false, 'npm devDependency should exist under app/node_modules') +}) diff --git a/usage.txt b/usage.txt index a85a1ed8..f22f4076 100644 --- a/usage.txt +++ b/usage.txt @@ -46,10 +46,12 @@ icon the local path to an icon file to use as the icon for the app ignore do not copy files into app whose filenames regex .match this string. See also: https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#ignore and --no-prune. -no-prune do not run `npm prune --production` on the app +no-prune do not prune devDependencies from the packaged app out the dir to put the app into at the end. defaults to current working dir overwrite if output directory for a platform already exists, replaces it rather than skipping it +package-manager the package manager to use when pruning devDependencies. Supported package + managers: npm (default), cnpm, yarn platform all, or one or more of: darwin, linux, mas, win32 (comma-delimited if multiple). Defaults to the host platform quiet Do not print informational or warning messages