@npmcli/arborist 4.3.1 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/actual.js +16 -20
- package/bin/audit.js +23 -26
- package/bin/funding.js +35 -31
- package/bin/ideal.js +11 -18
- package/bin/index.js +92 -63
- package/bin/lib/logging.js +62 -26
- package/bin/lib/options.js +117 -53
- package/bin/lib/print-tree.js +2 -3
- package/bin/lib/timers.js +17 -15
- package/bin/license.js +44 -34
- package/bin/prune.js +21 -22
- package/bin/reify.js +21 -22
- package/bin/shrinkwrap.js +5 -10
- package/bin/virtual.js +11 -15
- package/lib/add-rm-pkg-deps.js +12 -11
- package/lib/arborist/build-ideal-tree.js +12 -13
- package/lib/arborist/index.js +1 -3
- package/lib/arborist/rebuild.js +17 -17
- package/lib/arborist/reify.js +16 -10
- package/lib/audit-report.js +7 -8
- package/lib/get-workspace-nodes.js +4 -1
- package/lib/shrinkwrap.js +5 -7
- package/lib/tracker.js +6 -17
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -333,3 +333,13 @@ pruning nodes from the tree.
|
|
|
333
333
|
|
|
334
334
|
Note: `devOptional` is only set in the shrinkwrap/package-lock file if
|
|
335
335
|
_neither_ `dev` nor `optional` are set, as it would be redundant.
|
|
336
|
+
|
|
337
|
+
## BIN
|
|
338
|
+
|
|
339
|
+
Arborist ships with a cli that can be used to run arborist specific commands outside of the context of the npm CLI. This script is currently not part of the public API and is subject to breaking changes outside of major version bumps.
|
|
340
|
+
|
|
341
|
+
To see the usage run:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
npx @npmcli/arborist --help
|
|
345
|
+
```
|
package/bin/actual.js
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
const Arborist = require('../')
|
|
2
|
-
const print = require('./lib/print-tree.js')
|
|
3
|
-
const options = require('./lib/options.js')
|
|
4
|
-
require('./lib/logging.js')
|
|
5
|
-
require('./lib/timers.js')
|
|
6
2
|
|
|
7
|
-
const
|
|
8
|
-
new Arborist(options).loadActual(options).then(tree => {
|
|
9
|
-
const end = process.hrtime(start)
|
|
10
|
-
if (!process.argv.includes('--quiet')) {
|
|
11
|
-
print(tree)
|
|
12
|
-
}
|
|
3
|
+
const printTree = require('./lib/print-tree.js')
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
module.exports = (options, time) => new Arborist(options)
|
|
6
|
+
.loadActual(options)
|
|
7
|
+
.then(time)
|
|
8
|
+
.then(async ({ timing, result: tree }) => {
|
|
9
|
+
printTree(tree)
|
|
10
|
+
if (options.save) {
|
|
11
|
+
await tree.meta.save()
|
|
12
|
+
}
|
|
13
|
+
if (options.saveHidden) {
|
|
14
|
+
tree.meta.hiddenLockfile = true
|
|
15
|
+
tree.meta.filename = options.path + '/node_modules/.package-lock.json'
|
|
16
|
+
await tree.meta.save()
|
|
17
|
+
}
|
|
18
|
+
return `read ${tree.inventory.size} deps in ${timing.ms}`
|
|
19
|
+
})
|
package/bin/audit.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
const Arborist = require('../')
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
require('./lib/timers.js')
|
|
6
|
-
require('./lib/logging.js')
|
|
3
|
+
const printTree = require('./lib/print-tree.js')
|
|
4
|
+
const log = require('./lib/logging.js')
|
|
7
5
|
|
|
8
6
|
const Vuln = require('../lib/vuln.js')
|
|
9
7
|
const printReport = report => {
|
|
10
8
|
for (const vuln of report.values()) {
|
|
11
|
-
|
|
9
|
+
log.info(printVuln(vuln))
|
|
12
10
|
}
|
|
13
11
|
if (report.topVulns.size) {
|
|
14
|
-
|
|
12
|
+
log.info('\n# top-level vulnerabilities')
|
|
15
13
|
for (const vuln of report.topVulns.values()) {
|
|
16
|
-
|
|
14
|
+
log.info(printVuln(vuln))
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
17
|
}
|
|
@@ -33,22 +31,21 @@ const printVuln = vuln => {
|
|
|
33
31
|
|
|
34
32
|
const printAdvisory = a => `${a.title}${a.url ? ' ' + a.url : ''}`
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}).catch(er => console.error(er))
|
|
34
|
+
module.exports = (options, time) => {
|
|
35
|
+
const arb = new Arborist(options)
|
|
36
|
+
return arb
|
|
37
|
+
.audit(options)
|
|
38
|
+
.then(time)
|
|
39
|
+
.then(async ({ timing, result: tree }) => {
|
|
40
|
+
if (options.fix) {
|
|
41
|
+
printTree(tree)
|
|
42
|
+
}
|
|
43
|
+
printReport(arb.auditReport)
|
|
44
|
+
if (tree.meta && options.save) {
|
|
45
|
+
await tree.meta.save()
|
|
46
|
+
}
|
|
47
|
+
return options.fix
|
|
48
|
+
? `resolved ${tree.inventory.size} deps in ${timing.seconds}`
|
|
49
|
+
: `done in ${timing.seconds}`
|
|
50
|
+
})
|
|
51
|
+
}
|
package/bin/funding.js
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
const options = require('./lib/options.js')
|
|
2
|
-
require('./lib/logging.js')
|
|
3
|
-
require('./lib/timers.js')
|
|
4
|
-
|
|
5
1
|
const Arborist = require('../')
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (node.package.funding) {
|
|
23
|
-
console.log(node.name, node.location, node.package.funding)
|
|
2
|
+
|
|
3
|
+
const log = require('./lib/logging.js')
|
|
4
|
+
|
|
5
|
+
module.exports = (options, time) => {
|
|
6
|
+
const query = options._.shift()
|
|
7
|
+
const a = new Arborist(options)
|
|
8
|
+
return a
|
|
9
|
+
.loadVirtual()
|
|
10
|
+
.then(tree => {
|
|
11
|
+
// only load the actual tree if the virtual one doesn't have modern metadata
|
|
12
|
+
if (!tree.meta || !(tree.meta.originalLockfileVersion >= 2)) {
|
|
13
|
+
log.error('old metadata, load actual')
|
|
14
|
+
throw 'load actual'
|
|
15
|
+
} else {
|
|
16
|
+
log.error('meta ok, return virtual tree')
|
|
17
|
+
return tree
|
|
24
18
|
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
})
|
|
20
|
+
.catch(() => a.loadActual())
|
|
21
|
+
.then(time)
|
|
22
|
+
.then(({ timing, result: tree }) => {
|
|
23
|
+
if (!query) {
|
|
24
|
+
for (const node of tree.inventory.values()) {
|
|
25
|
+
if (node.package.funding) {
|
|
26
|
+
log.info(node.name, node.location, node.package.funding)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
for (const node of tree.inventory.query('name', query)) {
|
|
31
|
+
if (node.package.funding) {
|
|
32
|
+
log.info(node.name, node.location, node.package.funding)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
36
|
+
return `read ${tree.inventory.size} deps in ${timing.ms}`
|
|
37
|
+
})
|
|
38
|
+
}
|
package/bin/ideal.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
const Arborist = require('../')
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const options = require('./lib/options.js')
|
|
5
|
-
const print = require('./lib/print-tree.js')
|
|
6
|
-
require('./lib/logging.js')
|
|
7
|
-
require('./lib/timers.js')
|
|
3
|
+
const printTree = require('./lib/print-tree.js')
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
console.error(er.code === 'ERESOLVE' ? inspect(er, opt) : er)
|
|
20
|
-
process.exitCode = 1
|
|
21
|
-
})
|
|
5
|
+
module.exports = (options, time) => new Arborist(options)
|
|
6
|
+
.buildIdealTree(options)
|
|
7
|
+
.then(time)
|
|
8
|
+
.then(async ({ timing, result: tree }) => {
|
|
9
|
+
printTree(tree)
|
|
10
|
+
if (tree.meta && options.save) {
|
|
11
|
+
await tree.meta.save()
|
|
12
|
+
}
|
|
13
|
+
return `resolved ${tree.inventory.size} deps in ${timing.seconds}`
|
|
14
|
+
})
|
package/bin/index.js
CHANGED
|
@@ -1,81 +1,110 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [cmd] = process.argv.splice(2, 1)
|
|
3
2
|
|
|
4
|
-
const
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const { bin, arb: options } = require('./lib/options')
|
|
7
|
+
const version = require('../package.json').version
|
|
7
8
|
|
|
9
|
+
const usage = (message = '') => `Arborist - the npm tree doctor
|
|
10
|
+
|
|
11
|
+
Version: ${version}
|
|
12
|
+
${message && '\n' + message + '\n'}
|
|
8
13
|
# USAGE
|
|
9
14
|
arborist <cmd> [path] [options...]
|
|
10
15
|
|
|
11
16
|
# COMMANDS
|
|
12
17
|
|
|
13
|
-
* reify: reify ideal tree to node_modules (install, update, rm, ...)
|
|
14
|
-
* prune: prune the ideal tree and reify (like npm prune)
|
|
15
|
-
* ideal: generate and print the ideal tree
|
|
16
|
-
* actual: read and print the actual tree in node_modules
|
|
17
|
-
* virtual: read and print the virtual tree in the local shrinkwrap file
|
|
18
|
-
* shrinkwrap: load a local shrinkwrap and print its data
|
|
19
|
-
* audit: perform a security audit on project dependencies
|
|
20
|
-
* funding: query funding information in the local package tree. A second
|
|
21
|
-
|
|
22
|
-
* license: query license information in the local package tree. A second
|
|
23
|
-
|
|
24
|
-
* help: print this text
|
|
18
|
+
* reify: reify ideal tree to node_modules (install, update, rm, ...)
|
|
19
|
+
* prune: prune the ideal tree and reify (like npm prune)
|
|
20
|
+
* ideal: generate and print the ideal tree
|
|
21
|
+
* actual: read and print the actual tree in node_modules
|
|
22
|
+
* virtual: read and print the virtual tree in the local shrinkwrap file
|
|
23
|
+
* shrinkwrap: load a local shrinkwrap and print its data
|
|
24
|
+
* audit: perform a security audit on project dependencies
|
|
25
|
+
* funding: query funding information in the local package tree. A second
|
|
26
|
+
positional argument after the path name can limit to a package name.
|
|
27
|
+
* license: query license information in the local package tree. A second
|
|
28
|
+
positional argument after the path name can limit to a license type.
|
|
29
|
+
* help: print this text
|
|
30
|
+
* version: print the version
|
|
25
31
|
|
|
26
32
|
# OPTIONS
|
|
27
33
|
|
|
28
|
-
Most npm options are supported, but in camelCase rather than css-case. For
|
|
29
|
-
example, instead of '--dry-run', use '--dryRun'.
|
|
34
|
+
Most npm options are supported, but in camelCase rather than css-case. For
|
|
35
|
+
example, instead of '--dry-run', use '--dryRun'.
|
|
30
36
|
|
|
31
|
-
Additionally:
|
|
37
|
+
Additionally:
|
|
32
38
|
|
|
33
|
-
* --quiet will supppress the printing of package trees
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
* Instead of 'npm
|
|
37
|
-
|
|
38
|
-
* Instead of 'npm
|
|
39
|
-
|
|
39
|
+
* --loglevel=warn|--quiet will supppress the printing of package trees
|
|
40
|
+
* --logfile <file|bool> will output logs to a file
|
|
41
|
+
* --timing will show timing information
|
|
42
|
+
* Instead of 'npm install <pkg>', use 'arborist reify --add=<pkg>'.
|
|
43
|
+
The '--add=<pkg>' option can be specified multiple times.
|
|
44
|
+
* Instead of 'npm rm <pkg>', use 'arborist reify --rm=<pkg>'.
|
|
45
|
+
The '--rm=<pkg>' option can be specified multiple times.
|
|
46
|
+
* Instead of 'npm update', use 'arborist reify --update-all'.
|
|
47
|
+
* 'npm audit fix' is 'arborist audit --fix'
|
|
40
48
|
`
|
|
41
49
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
require('./actual.js')
|
|
47
|
-
break
|
|
48
|
-
case 'virtual':
|
|
49
|
-
require('./virtual.js')
|
|
50
|
-
break
|
|
51
|
-
case 'ideal':
|
|
52
|
-
require('./ideal.js')
|
|
53
|
-
break
|
|
54
|
-
case 'prune':
|
|
55
|
-
require('./prune.js')
|
|
56
|
-
break
|
|
57
|
-
case 'reify':
|
|
58
|
-
require('./reify.js')
|
|
59
|
-
break
|
|
60
|
-
case 'audit':
|
|
61
|
-
require('./audit.js')
|
|
62
|
-
break
|
|
63
|
-
case 'funding':
|
|
64
|
-
require('./funding.js')
|
|
65
|
-
break
|
|
66
|
-
case 'license':
|
|
67
|
-
require('./license.js')
|
|
68
|
-
break
|
|
69
|
-
case 'shrinkwrap':
|
|
70
|
-
require('./shrinkwrap.js')
|
|
71
|
-
break
|
|
72
|
-
case 'help':
|
|
73
|
-
case '-h':
|
|
74
|
-
case '--help':
|
|
75
|
-
help()
|
|
76
|
-
break
|
|
77
|
-
default:
|
|
50
|
+
const commands = {
|
|
51
|
+
version: () => console.log(version),
|
|
52
|
+
help: () => console.log(usage()),
|
|
53
|
+
exit: () => {
|
|
78
54
|
process.exitCode = 1
|
|
79
|
-
console.error(
|
|
80
|
-
|
|
55
|
+
console.error(
|
|
56
|
+
usage(`Error: command '${bin.command}' does not exist.`)
|
|
57
|
+
)
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const commandFiles = fs.readdirSync(__dirname).filter((f) => path.extname(f) === '.js' && f !== __filename)
|
|
62
|
+
|
|
63
|
+
for (const file of commandFiles) {
|
|
64
|
+
const command = require(`./${file}`)
|
|
65
|
+
const name = path.basename(file, '.js')
|
|
66
|
+
const totalTime = `bin:${name}:init`
|
|
67
|
+
const scriptTime = `bin:${name}:script`
|
|
68
|
+
|
|
69
|
+
commands[name] = () => {
|
|
70
|
+
const timers = require('./lib/timers')
|
|
71
|
+
const log = require('./lib/logging')
|
|
72
|
+
|
|
73
|
+
log.info(name, options)
|
|
74
|
+
|
|
75
|
+
process.emit('time', totalTime)
|
|
76
|
+
process.emit('time', scriptTime)
|
|
77
|
+
|
|
78
|
+
return command(options, (result) => {
|
|
79
|
+
process.emit('timeEnd', scriptTime)
|
|
80
|
+
return {
|
|
81
|
+
result,
|
|
82
|
+
timing: {
|
|
83
|
+
seconds: `${timers.get(scriptTime) / 1e9}s`,
|
|
84
|
+
ms: `${timers.get(scriptTime) / 1e6}ms`,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.then((result) => {
|
|
89
|
+
log.info(result)
|
|
90
|
+
return result
|
|
91
|
+
})
|
|
92
|
+
.catch((err) => {
|
|
93
|
+
process.exitCode = 1
|
|
94
|
+
log.error(err)
|
|
95
|
+
return err
|
|
96
|
+
})
|
|
97
|
+
.then((r) => {
|
|
98
|
+
process.emit('timeEnd', totalTime)
|
|
99
|
+
if (bin.loglevel !== 'silent') {
|
|
100
|
+
console[process.exitCode ? 'error' : 'log'](r)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (commands[bin.command]) {
|
|
107
|
+
commands[bin.command]()
|
|
108
|
+
} else {
|
|
109
|
+
commands.exit()
|
|
81
110
|
}
|
package/bin/lib/logging.js
CHANGED
|
@@ -1,42 +1,78 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
const log = require('proc-log')
|
|
2
|
+
const mkdirp = require('mkdirp')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const { dirname } = require('path')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const { inspect, format } = require('util')
|
|
7
|
+
|
|
8
|
+
const { bin: options } = require('./options.js')
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
// add a meta method to proc-log for passing optional
|
|
11
|
+
// metadata through to log handlers
|
|
12
|
+
const META = Symbol('meta')
|
|
13
|
+
const parseArgs = (...args) => {
|
|
14
|
+
const { [META]: isMeta } = args[args.length - 1] || {}
|
|
15
|
+
return isMeta
|
|
16
|
+
? [args[args.length - 1], ...args.slice(0, args.length - 1)]
|
|
17
|
+
: [{}, ...args]
|
|
18
|
+
}
|
|
19
|
+
log.meta = (meta = {}) => ({ [META]: true, ...meta })
|
|
20
|
+
|
|
21
|
+
const levels = new Map([
|
|
6
22
|
'silly',
|
|
7
23
|
'verbose',
|
|
8
24
|
'info',
|
|
9
|
-
'timing',
|
|
10
25
|
'http',
|
|
11
26
|
'notice',
|
|
12
27
|
'warn',
|
|
13
28
|
'error',
|
|
14
29
|
'silent',
|
|
15
|
-
]
|
|
30
|
+
].map((level, index) => [level, index]))
|
|
16
31
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
return set
|
|
20
|
-
}, []))
|
|
32
|
+
const addLogListener = (write, { eol = os.EOL, loglevel = 'silly', colors = false } = {}) => {
|
|
33
|
+
const levelIndex = levels.get(loglevel)
|
|
21
34
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
const magenta = m => colors ? `\x1B[35m${m}\x1B[39m` : m
|
|
36
|
+
const dim = m => colors ? `\x1B[2m${m}\x1B[22m` : m
|
|
37
|
+
const red = m => colors ? `\x1B[31m${m}\x1B[39m` : m
|
|
38
|
+
|
|
39
|
+
const formatter = (level, ...args) => {
|
|
40
|
+
const depth = level === 'error' && args[0] && args[0].code === 'ERESOLVE' ? Infinity : 10
|
|
41
|
+
|
|
42
|
+
if (level === 'info' && args[0] === 'timeEnd') {
|
|
43
|
+
args[1] = dim(args[1])
|
|
44
|
+
} else if (level === 'error' && args[0] === 'timeError') {
|
|
45
|
+
args[1] = red(args[1])
|
|
29
46
|
}
|
|
47
|
+
|
|
48
|
+
const messages = args.map(a => typeof a === 'string' ? a : inspect(a, { depth, colors }))
|
|
30
49
|
const pref = `${process.pid} ${magenta(level)} `
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
|
|
51
|
+
return pref + format(...messages).trim().split('\n').join(`${eol}${pref}`) + eol
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process.on('log', (...args) => {
|
|
55
|
+
const [meta, level, ...logArgs] = parseArgs(...args)
|
|
56
|
+
|
|
57
|
+
if (levelIndex <= levels.get(level) || meta.force) {
|
|
58
|
+
write(formatter(level, ...logArgs))
|
|
38
59
|
}
|
|
39
|
-
const msg = pref + format(...args).trim().split('\n').join(`\n${pref}`)
|
|
40
|
-
console.error(msg)
|
|
41
60
|
})
|
|
42
61
|
}
|
|
62
|
+
|
|
63
|
+
if (options.loglevel !== 'silent') {
|
|
64
|
+
addLogListener((v) => process.stderr.write(v), {
|
|
65
|
+
eol: '\n',
|
|
66
|
+
colors: options.colors,
|
|
67
|
+
loglevel: options.loglevel,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (options.logfile) {
|
|
72
|
+
log.silly('logfile', options.logfile)
|
|
73
|
+
mkdirp.sync(dirname(options.logfile))
|
|
74
|
+
const fd = fs.openSync(options.logfile, 'a')
|
|
75
|
+
addLogListener((str) => fs.writeSync(fd, str))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = log
|