lambda-tools
Version:
Scripts for working with AWS Lambda backed microservices
140 lines (121 loc) • 4.9 kB
JavaScript
const Promise = require('bluebird');
const crypto = require('crypto');
const detective = require('detective');
const _ = require('lodash');
const fs = Promise.promisifyAll(require('graceful-fs'));
const path = require('path');
const pkgResolve = require('resolve');
// Logic for finding the entire dependency tree of a piece of code:
// 1. Derive all require statements with detective
// 2. For each of those statements, either:
// 2a. If local (starts with . or /), recurse to the same function
// 2b. Otherwise, recurse to a different function that grabs all nested dependencies
function pkgInfo(id, basedir) {
return new Promise(function(resolve) {
pkgResolve(id, { basedir: basedir }, function(error, result, pkg) {
if (pkgResolve.isCore(id)) {
resolve({
name: id,
core: true
});
} else if (pkg && pkgResolve.isCore(pkg.name)) {
resolve({
name: pkg.name,
core: true
});
} else if (pkg && !(_.startsWith(id, '.') || _.startsWith(id, '/'))) {
const pkgPath = path.resolve(path.dirname(result).split(pkg.name)[0] + '/', pkg.name);
resolve({
name: pkg.name,
version: pkg.version,
dependencies: pkg.dependencies,
path: pkgPath
});
} else {
resolve({
name: id,
path: result
});
}
});
});
}
function nestedDependencies(pkgName, basedir, ignore) {
ignore = ignore || [];
return pkgInfo(pkgName, basedir)
.then(function(pkg) {
const skip = _.findIndex(ignore, {
name: pkg.name,
version: pkg.version
}) !== -1;
if (skip) {
return pkg;
}
const deps = pkg.dependencies;
// Add ourselves to ignore list (cuts cyclical loops)
const newIgnore = ignore.concat({
name: pkg.name,
version: pkg.version
});
return Promise.map(_.keys(deps), function(nestedPkgName) {
return nestedDependencies(nestedPkgName, basedir, newIgnore);
}).then(_.compact).then(function(results) {
pkg.dependencies = results;
return pkg;
});
});
}
function recursiveDependencies(code, options) {
options = options || {};
const ignore = options.ignore || [];
const basedir = options.basedir || process.cwd();
return Promise.map(detective(code), function(requireStatement) {
// Add to the ignore list (the seen list)
const newIgnore = [].concat(ignore);
// Recurse
if (_.startsWith(requireStatement, '.') || _.startsWith(requireStatement, '/')) {
// Local file, recursively grab all other dependencies
return pkgInfo(requireStatement, basedir).then(function(pkg) {
if (!pkg.path) {
// No path for the package, which is strange, so skip
return [];
}
// If ignored, just return the package itself and not recurse
if (newIgnore.indexOf(pkg.path) !== -1) {
return [_.assign({}, pkg, {
duplicate: true
})];
}
// Make sure to ignore going forwards
newIgnore.push(pkg.path);
return fs.readFileAsync(pkg.path).then(function(data) {
const checksum = crypto.createHash('sha1').update(data).digest('hex');
return recursiveDependencies(data, _.assign({}, options, {
basedir: path.dirname(pkg.path),
ignore: newIgnore
})).then(function(results) {
return results.concat(_.assign({}, pkg, {
checksum: checksum
}));
});
});
});
} else if (options.deep) {
// Resolve package and store version info
return nestedDependencies(requireStatement, basedir);
} else {
// Resolve package and store version info
return pkgInfo(requireStatement, basedir).then(function(pkg) {
return _.omit(pkg, 'dependencies');
});
}
}).then(function(results) {
return _.unionWith(_.flatten(results), function(a, b) {
return a.path === b.path;
}).map(function(result) {
return _.pick(result, ['name', 'version', 'path', 'core', 'dependencies', 'checksum', 'duplicate']);
});
});
}
module.exports = recursiveDependencies;
;