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