/
dependencies.js
122 lines (108 loc) · 4.38 KB
/
dependencies.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'use strict';
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) {
return pkgInfo(pkgName, basedir)
.then(function(pkg) {
const names = _.keys(pkg.dependencies);
return Promise.map(names, function(nestedPkgName) {
return nestedDependencies(nestedPkgName, basedir);
}).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;