@npmcli/arborist 4.3.0 → 5.0.1

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 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 start = process.hrtime()
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
- console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`)
15
- if (options.save) {
16
- tree.meta.save()
17
- }
18
- if (options.saveHidden) {
19
- tree.meta.hiddenLockfile = true
20
- tree.meta.filename = options.path + '/node_modules/.package-lock.json'
21
- tree.meta.save()
22
- }
23
- }).catch(er => console.error(er))
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 print = require('./lib/print-tree.js')
4
- const options = require('./lib/options.js')
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
- console.log(printVuln(vuln))
9
+ log.info(printVuln(vuln))
12
10
  }
13
11
  if (report.topVulns.size) {
14
- console.log('\n# top-level vulnerabilities')
12
+ log.info('\n# top-level vulnerabilities')
15
13
  for (const vuln of report.topVulns.values()) {
16
- console.log(printVuln(vuln))
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
- const start = process.hrtime()
37
- process.emit('time', 'audit script')
38
- const arb = new Arborist(options)
39
- arb.audit(options).then(tree => {
40
- process.emit('timeEnd', 'audit script')
41
- const end = process.hrtime(start)
42
- if (options.fix) {
43
- print(tree)
44
- }
45
- if (!options.quiet) {
46
- printReport(arb.auditReport)
47
- }
48
- if (options.fix) {
49
- console.error(`resolved ${tree.inventory.size} deps in ${end[0] + end[1] / 1e9}s`)
50
- }
51
- if (tree.meta && options.save) {
52
- tree.meta.save()
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
- const a = new Arborist(options)
7
- const query = options._.shift()
8
- const start = process.hrtime()
9
- a.loadVirtual().then(tree => {
10
- // only load the actual tree if the virtual one doesn't have modern metadata
11
- if (!tree.meta || !(tree.meta.originalLockfileVersion >= 2)) {
12
- console.error('old metadata, load actual')
13
- throw 'load actual'
14
- } else {
15
- console.error('meta ok, return virtual tree')
16
- return tree
17
- }
18
- }).catch(() => a.loadActual()).then(tree => {
19
- const end = process.hrtime(start)
20
- if (!query) {
21
- for (const node of tree.inventory.values()) {
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
- } else {
27
- for (const node of tree.inventory.query('name', query)) {
28
- if (node.package.funding) {
29
- console.log(node.name, node.location, node.package.funding)
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
- console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`)
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 { inspect } = require('util')
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
- const start = process.hrtime()
10
- new Arborist(options).buildIdealTree(options).then(tree => {
11
- const end = process.hrtime(start)
12
- print(tree)
13
- console.error(`resolved ${tree.inventory.size} deps in ${end[0] + end[1] / 10e9}s`)
14
- if (tree.meta && options.save) {
15
- tree.meta.save()
16
- }
17
- }).catch(er => {
18
- const opt = { depth: Infinity, color: true }
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 usage = () => `Arborist - the npm tree doctor
3
+ const fs = require('fs')
4
+ const path = require('path')
5
5
 
6
- Version: ${require('../package.json').version}
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
- positional argument after the path name can limit to a package name.
22
- * license: query license information in the local package tree. A second
23
- positional argument after the path name can limit to a license type.
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
- * Instead of 'npm install <pkg>', use 'arborist reify --add=<pkg>'.
35
- The '--add=<pkg>' option can be specified multiple times.
36
- * Instead of 'npm rm <pkg>', use 'arborist reify --rm=<pkg>'.
37
- The '--rm=<pkg>' option can be specified multiple times.
38
- * Instead of 'npm update', use 'arborist reify --update-all'.
39
- * 'npm audit fix' is 'arborist audit --fix'
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 help = () => console.log(usage())
43
-
44
- switch (cmd) {
45
- case 'actual':
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(usage())
80
- break
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
  }
@@ -1,42 +1,78 @@
1
- const options = require('./options.js')
2
- const { quiet = false } = options
3
- const { loglevel = quiet ? 'warn' : 'silly' } = options
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
- const levels = [
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 levelMap = new Map(levels.reduce((set, level, index) => {
18
- set.push([level, index], [index, level])
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 { inspect, format } = require('util')
23
- const colors = process.stderr.isTTY
24
- const magenta = colors ? msg => `\x1B[35m${msg}\x1B[39m` : m => m
25
- if (loglevel !== 'silent') {
26
- process.on('log', (level, ...args) => {
27
- if (levelMap.get(level) < levelMap.get(loglevel)) {
28
- return
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
- if (level === 'warn' && args[0] === 'ERESOLVE') {
32
- args[2] = inspect(args[2], { depth: 10, colors })
33
- } else {
34
- args = args.map(a => {
35
- return typeof a === 'string' ? a
36
- : inspect(a, { depth: 10, colors })
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