1#!/usr/bin/env node
2
3const fs = require('fs')
4const path = require('path')
5
6const { bin, arb: options } = require('./lib/options')
7const version = require('../package.json').version
8
9const usage = (message = '') => `Arborist - the npm tree doctor
10
11Version: ${version}
12${message && '\n' + message + '\n'}
13# USAGE
14  arborist <cmd> [path] [options...]
15
16# COMMANDS
17
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
31
32# OPTIONS
33
34  Most npm options are supported, but in camelCase rather than css-case.  For
35  example, instead of '--dry-run', use '--dryRun'.
36
37  Additionally:
38
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'
48`
49
50const commands = {
51  version: () => console.log(version),
52  help: () => console.log(usage()),
53  exit: () => {
54    process.exitCode = 1
55    console.error(
56      usage(`Error: command '${bin.command}' does not exist.`)
57    )
58  },
59}
60
61const commandFiles = fs.readdirSync(__dirname).filter((f) => path.extname(f) === '.js' && f !== __filename)
62
63for (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        return r
103      })
104  }
105}
106
107if (commands[bin.command]) {
108  commands[bin.command]()
109} else {
110  commands.exit()
111}
112