diff --git a/package.json b/package.json index e0ccf02c0c3..7952bba6f66 100644 --- a/package.json +++ b/package.json @@ -19,21 +19,17 @@ "scripts": { "test": "make test", "start": "electron lib/start.js", - "preshrinkwrap": "node ./scripts/clean-shrinkwrap.js", + "postshrinkwrap": "node ./scripts/clean-shrinkwrap.js", "configure": "node-gyp configure", "build": "node-gyp build", "install": "node-gyp rebuild" }, "author": "Resin Inc. ", "license": "Apache-2.0", - "shrinkwrapIgnore": [ - "macos-alias", - "fs-xattr", - "ds-store", - "appdmg", - "7zip-bin-mac", - "7zip-bin-win", - "7zip-bin-linux" + "platformSpecificDependencies": [ + [ "7zip-bin-mac" ], + [ "7zip-bin-win" ], + [ "7zip-bin-linux" ] ], "dependencies": { "angular": "1.6.3", diff --git a/scripts/ci/build-installers.sh b/scripts/ci/build-installers.sh index fa00c2d3387..f1d80e84ebb 100755 --- a/scripts/ci/build-installers.sh +++ b/scripts/ci/build-installers.sh @@ -48,8 +48,8 @@ if [ "$ARGV_OPERATING_SYSTEM" == "linux" ]; then ./scripts/build/docker/run-command.sh \ -r "$TARGET_ARCH" \ -s "$(pwd)" \ - -c 'make electron-develop installers-all' + -c 'make installers-all' else ./scripts/build/check-dependency.sh make - make electron-develop installers-all + make installers-all fi diff --git a/scripts/clean-shrinkwrap.js b/scripts/clean-shrinkwrap.js index 8fe9fdb38a1..1354fa5dad8 100644 --- a/scripts/clean-shrinkwrap.js +++ b/scripts/clean-shrinkwrap.js @@ -15,29 +15,222 @@ 'use strict'; +const _ = require('lodash'); +const fs = require('fs'); const path = require('path'); -const os = require('os'); const packageJSON = require('../package.json'); -const spawn = require('child_process').spawn; -const shrinkwrapIgnore = packageJSON.shrinkwrapIgnore; +const NPM_SHRINKWRAP_FILE_PATH = path.join(__dirname, '..', 'npm-shrinkwrap.json'); +const shrinkwrapFile = require(NPM_SHRINKWRAP_FILE_PATH); +const platformSpecificDependencies = packageJSON.platformSpecificDependencies; -console.log('Removing:', shrinkwrapIgnore.join(', ')); +console.log('Removing:', platformSpecificDependencies.join(', ')); /** - * Run an npm command - * @param {Array} command - list of arguments - * @returns {ChildProcess} + * @summary Get a shrinkwrap dependency object + * @function + * @private + * + * @param {Object} shrinkwrap - the shrinkwrap file contents + * @param {String[]} shrinkwrapPath - path to shrinkwrap dependency + * @returns {Object} shrinkwrap object + * + * @example + * const object = getShrinkwrapDependencyObject(require('npm-shrinkwrap.json'), [ + * 'drivelist', + * 'lodash' + * ]); */ -const npm = (command) => { - const npmBinary = os.platform() === 'win32' ? 'npm.cmd' : 'npm'; - return spawn(npmBinary, command, { - cwd: path.join(__dirname, '..'), - env: process.env, - stdio: [ process.stdin, process.stdout, process.stderr ] - }); +const getShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => { + return _.reduce(shrinkwrapPath, (accumulator, dependency) => { + return _.get(accumulator, [ 'dependencies', dependency ]); + }, shrinkwrap); +}; + +/** + * @summary Get a cleaned shrinkwrap dependency object + * @function + * @private + * + * @description + * This function transforms the output of `getShrinkwrapDependencyObject()` to + * omit unnecessary properties such as `from`, or `dependencies`. + * + * @param {Object} shrinkwrap - the shrinkwrap file contents + * @param {String[]} shrinkwrapPath - path to shrinkwrap dependency + * @returns {Object} shrinkwrap object + * + * @example + * const object = getPrettyShrinkwrapDependencyObject(require('npm-shrinkwrap.json'), [ + * 'drivelist', + * 'lodash' + * ]); + */ +const getPrettyShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => { + const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath); + + if (_.isNil(object)) { + return null; + } + + return { + name: _.last(shrinkwrapPath), + path: shrinkwrapPath, + version: object.version, + development: Boolean(object.dev), + optional: Boolean(object.optional) + }; }; -npm([ 'rm', '--ignore-scripts' ].concat(shrinkwrapIgnore)) - .once('close', () => { - console.log('Done.'); +/** + * @summary Get the manifest of a shrinkwrap dependency + * @function + * @private + * + * @param {String[]} shrinkwrapPath - path to shrinkwrap dependency + * @returns {Object} dependency manifest + * + * @example + * const manifest = getShrinkwrapDependencyManifest([ 'bluebird' ]); + */ +const getShrinkwrapDependencyManifest = (shrinkwrapPath) => { + const manifestPath = _.chain(shrinkwrapPath) + .flatMap((dependency) => { + return [ 'node_modules', dependency ]; + }) + .concat([ 'package.json' ]) + .reduce((accumulator, file) => { + return path.join(accumulator, file); + }, '.') + .value(); + + try { + return require(`.${path.sep}${manifestPath}`); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + return null; + } + + throw error; + } +}; + +/** + * @summary Get the top level dependencies of a shrinkwrap object + * @function + * @private + * + * @param {String[]} shrinkwrapPath - path to shrinkwrap dependency + * @returns {Object} top level dependencies + * + * @example + * const dependencies = getTopLevelDependenciesFromShrinkwrapObject([ 'debug' ]); + */ +const getTopLevelDependenciesFromShrinkwrapObject = (shrinkwrapPath) => { + return _.get(getShrinkwrapDependencyManifest(shrinkwrapPath), [ 'dependencies' ], {}); +}; + +/** + * @summary Get the dependency tree of a shrinkwrap dependency + * @function + * @private + * + * @param {Object} shrinkwrap - the shrinkwrap file contents + * @param {String[]} shrinkwrapPath - path to shrinkwrap dependency + * @returns {Object[]} dependency tree + * + * @example + * const dependencyTree = getDependencyTree(require('./npm-shrinkwrap.json'), [ 'drivelist' ]); + */ +const getDependencyTree = (shrinkwrap, shrinkwrapPath) => { + const dependencies = getTopLevelDependenciesFromShrinkwrapObject(shrinkwrapPath); + + if (_.isEmpty(dependencies)) { + return []; + } + + const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath); + const result = _.map(dependencies, (version, name) => { + const dependencyPath = _.has(object.dependencies, name) ? _.concat(shrinkwrapPath, [ name ]) : [ name ]; + return getPrettyShrinkwrapDependencyObject(shrinkwrap, dependencyPath); }); + + return _.concat(result, _.flatMapDeep(result, (dependency) => { + return getDependencyTree(shrinkwrap, dependency.path); + })); +}; + +/** + * @summary Remove certain dependencies from a shrinkwrap object + * @function + * @private + * + * @description + * A shrinkwrap object is a recursive data structure, that apart + * from some extra metadata, has the following structure: + * + * { + * ... + * "dependencies": { + * "": , + * "": , + * "": , + * ... + * } + * } + * + * The purpose of this function is to remove certain dependencies + * that match a blacklist. In order to do so, we start from the top + * level object, remove the blacklisted dependencies, and recurse + * if possible. + * + * @param {Object} shrinkwrap - the shrinkwrap file contents + * @param {String[]} blacklist - dependency blacklist + * @param {Object} qualifiers - qualifiers + * @param {Boolean} qualifiers.development - development dependency + * @param {Boolean} qualifiers.optional - optional dependency + * @returns {Object} filtered shrinkwrap object + * + * @example + * const shrinkwrapFile = require('./npm-shrinkwrap.json'); + * const dependencyTree = getDependencyTree(shrinkwrapFile, [ 'drivelist' ]); + * const filteredShrinkwrap = removeDependencies(shrinkwrapFile, dependencyTree, { + * optional: true, + * development: true + * }); + */ +const removeDependencies = (shrinkwrap, blacklist, qualifiers) => { + if (!_.isEmpty(shrinkwrap.dependencies)) { + shrinkwrap.dependencies = _.chain(shrinkwrap.dependencies) + .omitBy((dependency, name) => { + return _.every([ + _.find(blacklist, { + name, + version: dependency.version + }), + dependency.dev === qualifiers.development, + dependency.optional === qualifiers.optional + ]); + }) + .mapValues((dependency) => { + return removeDependencies(dependency, blacklist, qualifiers); + }) + .value(); + } + + return shrinkwrap; +}; + +const blacklist = _.reduce(platformSpecificDependencies, (accumulator, dependencyPath) => { + return _.compact(_.concat(accumulator, [ + getPrettyShrinkwrapDependencyObject(shrinkwrapFile, dependencyPath) + ], getDependencyTree(shrinkwrapFile, dependencyPath))); +}, []); + +const JSON_INDENTATION_SPACES = 2; +const result = JSON.stringify(removeDependencies(shrinkwrapFile, blacklist, { + development: true, + optional: true +}), null, JSON_INDENTATION_SPACES); + +fs.writeFileSync(NPM_SHRINKWRAP_FILE_PATH, `${result}\n`); +console.log('Done');