From 1915eb5a51cba946f4d4ce088283f1e853dc76e2 Mon Sep 17 00:00:00 2001 From: Nathan James Tindall Date: Fri, 25 Aug 2017 23:26:09 -0700 Subject: [PATCH] Implement `check` --- README.md | 12 ++++++++++ bin/cli.js | 47 ++++++++++++++++++++++++++++++++++++++ bin/usage.md | 12 ++++++++++ sync/force-install.js | 22 ++++++++++++++---- sync/index.js | 51 +++++++++++++++++++++++++++++++++++++++--- sync/install-module.js | 5 +++++ sync/purge-excess.js | 7 ++++++ sync/read.js | 9 +++++--- 8 files changed, 155 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 08cbb48..0c9972c 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,18 @@ Options: - `--dirname` defaults to `process.cwd()` +#### `npm-shrinkwrap check` + +Asserts that your `npm-shrinkwrap.json` file and node_modules + directory are in sync. If any excess modules are in your + node_modules folder, `check` will return an error and print + a list of the excess dependencies that are installed. + +Options: + --dirname sets the directory of the npm-shrinkwrap.json + + - `--dirname` defaults to `process.cwd()` + #### `npm-shrinkwrap install` Will write a `shrinkwrap` script to your `package.json` file. diff --git a/bin/cli.js b/bin/cli.js index 43fceb3..ad6bb22 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -74,6 +74,53 @@ function main(opts, callback) { console.log('synced npm-shrinkwrap.json ' + 'into node_modules'); }); + } else if (command === 'check') { + // Otherwise, we check to see if any erroneous dependencies are + // installed. + // + // We set opts.dry to true, which suppresses all side effects in the + // shrinkShrinkwrap routine. + opts.dry = true; + + return syncShrinkwrap(opts, function (err, errorReport) { + if (callback) { + return callback(err, errorReport); + } + + if (err) { + if (errorReport === undefined) { + console.log('error', err); + console.error('stack', new Error().stack); + throw err; + } + + if (errorReport.excessPackageJsonDependencies !== null && + errorReport.excessPackageJsonDependencies.length !== 0) { + console.error('package.json has dependencies that are ' + + 'not present in npm-shrinkwrap.json'); + console.log(errorReport.excessPackageJsonDependencies); + } + + if (errorReport.excessShrinkwrapDependencies !== null && + errorReport.excessShrinkwrapDependencies.length !== 0) { + console.error('npm-shrinkwrap.json has dependencies that are ' + + 'not present in package.json'); + console.log(errorReport.excessShrinkwrapDependencies); + } + + if (errorReport.erroneouslyInstalledDependencies !== null && + errorReport.erroneouslyInstalledDependencies.length !== 0) { + console.error('npm-shrinkwrap.json is out of sync ' + + 'with node_modules'); + console.log(errorReport.erroneouslyInstalledDependencies); + } + + return process.exit(1); + } + + console.log('npm-shrinkwrap.json is in sync ' + + 'with package.json'); + }); } shrinkwrap(opts, function (err, warnings) { diff --git a/bin/usage.md b/bin/usage.md index 8898d10..6e54271 100644 --- a/bin/usage.md +++ b/bin/usage.md @@ -34,6 +34,18 @@ Options: - `--dirname` defaults to `process.cwd()` +## `{cmd} check` + +Asserts that your `npm-shrinkwrap.json` file and node_modules + directory are in sync. If any excess modules are in your + node_modules folder, `check` will return an error and print + a list of the excess dependencies that are installed. + +Options: + --dirname sets the directory of the npm-shrinkwrap.json + + - `--dirname` defaults to `process.cwd()` + ## `{cmd} install` Will write a `shrinkwrap` script to your `package.json` file. diff --git a/sync/force-install.js b/sync/force-install.js index a96e7a3..fab5388 100644 --- a/sync/force-install.js +++ b/sync/force-install.js @@ -20,7 +20,9 @@ function forceInstall(nodeModules, shrinkwrap, opts, cb) { // if no dependencies object then terminate recursion if (shrinkwrap.name && !shrinkwrap.dependencies) { - return purgeExcess(nodeModules, shrinkwrap, opts, cb); + return purgeExcess(nodeModules, shrinkwrap, opts, function(err, results) { + cb(err, results || []); + }); } var deps = shrinkwrap.dependencies; @@ -46,7 +48,7 @@ function forceInstall(nodeModules, shrinkwrap, opts, cb) { opts.dev = false; // remove purgeExcess result - results.pop(); + var excess = results.pop(); var incorrects = results.filter(function (dep) { return !dep.correct; @@ -78,7 +80,6 @@ function forceInstall(nodeModules, shrinkwrap, opts, cb) { var name = correct.name; var folder = path.join(nodeModules, name, 'node_modules'); - return forceInstall.bind( null, folder, correct, opts); }); @@ -90,7 +91,20 @@ function forceInstall(nodeModules, shrinkwrap, opts, cb) { var tasks = [].concat(inCorrectTasks, correctTasks); - parallel(tasks, cb); + parallel(tasks, function(err, results) { + if (err) { + return cb(err); + } + + // Results is an array of arrays representing the excess + // installed dependencies of our children. + var flattened = [].concat.apply([], results); + + /* + return the excess dependencies that may have been purged + */ + cb(null, (excess || []).concat(flattened)); + }); }); } diff --git a/sync/index.js b/sync/index.js index 1082b5f..eb210b4 100644 --- a/sync/index.js +++ b/sync/index.js @@ -36,19 +36,64 @@ function syncShrinkwrap(opts, cb) { parallel({ shrinkwrap: read.shrinkwrap.bind(null, dirname), - devDependencies: read.devDependencies.bind(null, dirname) + dependencies: read.dependencies.bind(null, dirname) }, function (err, tuple) { if (err) { return cb(err); } var nodeModules = path.join(dirname, 'node_modules'); + var dependencies = tuple.dependencies; var shrinkwrap = tuple.shrinkwrap; - shrinkwrap.devDependencies = tuple.devDependencies; + + // first, we check that package.json dependencies and shrinkwrap + // top-level dependencies are in sync. this should cover the case + // where a dependency was added or removed to package.json, but + // shrinkwrap was not subsequently run. + var packageJsonDependencies = + Object.keys(dependencies.dependencies); + var shrinkwrapTopLevelDependencies = + Object.keys(shrinkwrap.dependencies); + + var excessPackageJsonDependencies = packageJsonDependencies + .filter(function (x) { + return shrinkwrapTopLevelDependencies.indexOf(x) === -1; + }); + var excessShrinkwrapDependencies = shrinkwrapTopLevelDependencies + .filter(function (x) { + return packageJsonDependencies.indexOf(x) === -1; + }); + + shrinkwrap.devDependencies = tuple.dependencies.devDependencies; opts.dev = true; - forceInstall(nodeModules, shrinkwrap, opts, cb); + forceInstall(nodeModules, shrinkwrap, opts, + function(err, erroneousDependencies) { + // If there is a legitimate error, or we are not running + // `check`, bubble it up immediately + if (err || opts.dry !== true) { + return cb(err); + } + + // Generate the error report + if (erroneousDependencies.length !== 0 || + excessPackageJsonDependencies.length !== 0 || + excessShrinkwrapDependencies.length !== 0 + ) { + return cb( + new Error('npm-shrinkwrap.json is out of sync'), + { + excessPackageJsonDependencies: + excessPackageJsonDependencies, + excessShrinkwrapDependencies: + excessShrinkwrapDependencies, + erroneouslyInstalledDependencies: + erroneousDependencies, + }); + } + cb(null); + }); }); }); } diff --git a/sync/install-module.js b/sync/install-module.js index 93957f0..69f147d 100644 --- a/sync/install-module.js +++ b/sync/install-module.js @@ -11,6 +11,11 @@ module.exports = installModule; */ function installModule(nodeModules, dep, opts, cb) { + // Suppress side-effects if running `check` + if (opts.dry === true) { + return cb(null, dep); + } + var where = path.join(nodeModules, '..'); console.log('installing ', where, dep.resolved); diff --git a/sync/purge-excess.js b/sync/purge-excess.js index b54c1df..9646431 100644 --- a/sync/purge-excess.js +++ b/sync/purge-excess.js @@ -28,6 +28,13 @@ function purgeExcess(dir, shrinkwrap, opts, cb) { var tasks = excessFiles.map(function (file) { var filePath = path.join(dir, file); + + // Suppress side-effects if running `check` + if (opts.dry === true) { + return function (cb) { + return cb(null, filePath); + }; + } console.log('removing', filePath); return rimraf.bind(null, filePath); }); diff --git a/sync/read.js b/sync/read.js index 4d95501..97c661f 100644 --- a/sync/read.js +++ b/sync/read.js @@ -12,7 +12,7 @@ var FileNotFound = TypedError({ module.exports = { shrinkwrap: readShrinkwrap, package: readPackage, - devDependencies: readDevDependencies + dependencies: readDependencies }; function readPackage(dirname, cb) { @@ -33,12 +33,15 @@ function readShrinkwrap(dirname, cb) { }); } -function readDevDependencies(dirname, cb) { +function readDependencies(dirname, cb) { readPackage(dirname, function (err, json) { if (err) { return cb(err); } - cb(null, json.devDependencies); + cb(null, { + dependencies: json.dependencies, + devDependencies: json.devDependencies + }); }); }