|
1 |
| -const logicalTree = require('npm-logical-tree') |
| 1 | +const kleur = require('kleur') |
| 2 | +const Arborist = require('@npmcli/arborist') |
2 | 3 |
|
3 | 4 | /**
|
4 |
| - * Find who depend upon a target package. |
| 5 | + * Run npm-why in a directory |
5 | 6 | *
|
6 |
| - * @param {Object} pkg package.json |
7 |
| - * @param {Object} pkgLock package-lock.json |
8 |
| - * @param {String} target target package name to lookup |
| 7 | + * @param {String} dir Workding directory |
| 8 | + * @param {String} packageName The package to lookup |
9 | 9 | */
|
10 |
| -module.exports = function (pkg, pkgLock, name) { |
11 |
| - const tree = logicalTree(pkg, pkgLock) |
12 |
| - |
13 |
| - // Find target deps |
14 |
| - const targets = new Set() |
15 |
| - tree.forEach((dep, callback) => { |
16 |
| - dep.name === name && targets.add(dep) |
17 |
| - callback() |
18 |
| - }) |
19 |
| - |
20 |
| - // Find requiredBy for those targets |
21 |
| - const results = [] |
22 |
| - targets.forEach(t => { |
23 |
| - lookupDependents(t, tree, [], results) |
24 |
| - }) |
25 |
| - return results |
26 |
| -} |
| 10 | +async function collectReasons (dir, packageName) { |
| 11 | + const arb = new Arborist({ path: dir }) |
| 12 | + const tree = await arb.loadVirtual() |
| 13 | + const pkgs = tree.children.keys() |
27 | 14 |
|
28 |
| -function lookupDependents (target, root, breadcrumb = [], results) { |
29 |
| - breadcrumb = breadcrumb.concat(target) |
| 15 | + let reasons = [] |
30 | 16 |
|
31 |
| - if (target.requiredBy.size === 0) { |
32 |
| - return results.push(formatBreadcrumb(breadcrumb)) |
| 17 | + // find all versions of <packageName> |
| 18 | + for (const name of pkgs) { |
| 19 | + if (name === packageName) { |
| 20 | + const reasonChain = buildDependentsTree(tree.children.get(name)) |
| 21 | + reasons = reasons.concat(reasonChain.leafs) |
| 22 | + } |
33 | 23 | }
|
34 | 24 |
|
35 |
| - if (breadcrumb.includes(root)) { |
36 |
| - return results.push(formatBreadcrumb(breadcrumb)) |
| 25 | + // prepand root package info to every reason chain |
| 26 | + const root = { name: tree.name, version: tree.version } |
| 27 | + reasons = reasons.map(chain => chain.concat(root)) |
| 28 | + |
| 29 | + return reasons |
| 30 | +} |
| 31 | + |
| 32 | +function buildDependentsTree (node, paths = [], leafs = []) { |
| 33 | + const { name, version, edgesIn } = node |
| 34 | + |
| 35 | + // when leaf node |
| 36 | + if (edgesIn.size === 0) { |
| 37 | + leafs.push(paths) |
| 38 | + return { name, version, leafs } |
37 | 39 | }
|
38 | 40 |
|
39 |
| - if (breadcrumb.slice(0, -1).includes(target)) { |
40 |
| - return // Recursive dependency |
| 41 | + // prevent dead loops |
| 42 | + if (paths.includes(name)) { |
| 43 | + return { name, version, leafs } |
41 | 44 | }
|
42 | 45 |
|
43 |
| - target.requiredBy.forEach(dependent => { |
44 |
| - lookupDependents(dependent, root, breadcrumb, results) |
| 46 | + const dependents = Array.from(edgesIn).map(edge => { |
| 47 | + const pkg = { name, version } |
| 48 | + return buildDependentsTree(edge.from, paths.concat(pkg), leafs) |
45 | 49 | })
|
| 50 | + |
| 51 | + return { name, version, leafs, dependents } |
| 52 | +} |
| 53 | + |
| 54 | +function printReasons (reasons, rootPackage) { |
| 55 | + reasons.map(reason => { |
| 56 | + const versionTag = kleur.dim('@' + reason[0].version) |
| 57 | + |
| 58 | + const line = reason.reverse().map(pkg => { |
| 59 | + return kleur.blue(pkg.name || rootPackage) |
| 60 | + }).join(' > ') |
| 61 | + |
| 62 | + return line + versionTag |
| 63 | + }).sort().forEach(line => console.log(' ', line)) |
46 | 64 | }
|
47 | 65 |
|
48 |
| -function formatBreadcrumb (bc) { |
49 |
| - return bc.map(piece => ({ |
50 |
| - name: piece.name, |
51 |
| - version: piece.version |
52 |
| - })) |
| 66 | +module.exports = { |
| 67 | + collectReasons, |
| 68 | + printReasons |
53 | 69 | }
|
0 commit comments