Add 'npm explain' command · npm/cli@5e49bda (original) (raw)

``

1

`+

const usageUtil = require('./utils/usage.js')

`

``

2

`+

const npm = require('./npm.js')

`

``

3

`+

const { explainNode } = require('./utils/explain-dep.js')

`

``

4

`+

const completion = require('./utils/completion/installed-deep.js')

`

``

5

`+

const output = require('./utils/output.js')

`

``

6

`+

const Arborist = require('@npmcli/arborist')

`

``

7

`+

const npa = require('npm-package-arg')

`

``

8

`+

const semver = require('semver')

`

``

9

`+

const { relative, resolve } = require('path')

`

``

10

`+

const validName = require('validate-npm-package-name')

`

``

11

+

``

12

`+

const usage = usageUtil('explain', 'npm explain <folder | specifier>')

`

``

13

+

``

14

`+

const cmd = (args, cb) => explain(args).then(() => cb()).catch(cb)

`

``

15

+

``

16

`+

const explain = async (args) => {

`

``

17

`+

if (!args.length) {

`

``

18

`+

throw usage

`

``

19

`+

}

`

``

20

+

``

21

`+

const arb = new Arborist({ path: npm.prefix, ...npm.flatOptions })

`

``

22

`+

const tree = await arb.loadActual()

`

``

23

+

``

24

`+

const nodes = new Set()

`

``

25

`+

for (const arg of args) {

`

``

26

`+

for (const node of getNodes(tree, arg)) {

`

``

27

`+

nodes.add(node)

`

``

28

`+

}

`

``

29

`+

}

`

``

30

`+

if (nodes.size === 0) {

`

``

31

`` +

throw No dependencies found matching ${args.join(', ')}

``

``

32

`+

}

`

``

33

+

``

34

`+

const expls = []

`

``

35

`+

for (const node of nodes) {

`

``

36

`+

const { extraneous, dev, optional, devOptional, peer } = node

`

``

37

`+

const expl = node.explain()

`

``

38

`+

if (extraneous) {

`

``

39

`+

expl.extraneous = true

`

``

40

`+

} else {

`

``

41

`+

expl.dev = dev

`

``

42

`+

expl.optional = optional

`

``

43

`+

expl.devOptional = devOptional

`

``

44

`+

expl.peer = peer

`

``

45

`+

}

`

``

46

`+

expls.push(expl)

`

``

47

`+

}

`

``

48

+

``

49

`+

if (npm.flatOptions.json) {

`

``

50

`+

output(JSON.stringify(expls, null, 2))

`

``

51

`+

} else {

`

``

52

`+

output(expls.map(expl => {

`

``

53

`+

return explainNode(expl, Infinity, npm.color)

`

``

54

`+

}).join('\n\n'))

`

``

55

`+

}

`

``

56

`+

}

`

``

57

+

``

58

`+

const getNodes = (tree, arg) => {

`

``

59

`+

// if it's just a name, return packages by that name

`

``

60

`+

const { validForOldPackages: valid } = validName(arg)

`

``

61

`+

if (valid) {

`

``

62

`+

return tree.inventory.query('name', arg)

`

``

63

`+

}

`

``

64

+

``

65

`+

// if it's a location, get that node

`

``

66

`+

const maybeLoc = arg.replace(/\/g, '/').replace(//+$/, '')

`

``

67

`+

const nodeByLoc = tree.inventory.get(maybeLoc)

`

``

68

`+

if (nodeByLoc) {

`

``

69

`+

return [nodeByLoc]

`

``

70

`+

}

`

``

71

+

``

72

`+

// maybe a path to a node_modules folder

`

``

73

`+

const maybePath = relative(npm.prefix, resolve(maybeLoc))

`

``

74

`+

.replace(/\/g, '/').replace(//+$/, '')

`

``

75

`+

const nodeByPath = tree.inventory.get(maybePath)

`

``

76

`+

if (nodeByPath) {

`

``

77

`+

return [nodeByPath]

`

``

78

`+

}

`

``

79

+

``

80

`+

// otherwise, try to select all matching nodes

`

``

81

`+

try {

`

``

82

`+

return getNodesByVersion(tree, arg)

`

``

83

`+

} catch (er) {

`

``

84

`+

return []

`

``

85

`+

}

`

``

86

`+

}

`

``

87

+

``

88

`+

const getNodesByVersion = (tree, arg) => {

`

``

89

`+

const spec = npa(arg, npm.prefix)

`

``

90

`+

if (spec.type !== 'version' && spec.type !== 'range') {

`

``

91

`+

return []

`

``

92

`+

}

`

``

93

+

``

94

`+

return tree.inventory.filter(node => {

`

``

95

`+

return node.package.name === spec.name &&

`

``

96

`+

semver.satisfies(node.package.version, spec.rawSpec)

`

``

97

`+

})

`

``

98

`+

}

`

``

99

+

``

100

`+

module.exports = Object.assign(cmd, { usage, completion })

`