From e32956f4d08c27a13b21f8169b1dc48fd69da76a Mon Sep 17 00:00:00 2001 From: Michael Perrotte Date: Tue, 10 Mar 2020 02:16:07 -0400 Subject: [PATCH] feat: updated 'npm install' command - functionality to get diff and explicitRequets from arborist instace requires @npmcli/arborist@0.0.0-pre.12 - create printResult function - output install action time (fairly naively) - added functionality to create and print archy dep tree --- lib/install.js | 154 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/lib/install.js b/lib/install.js index eb60c794d263f..a6be79b4077d4 100644 --- a/lib/install.js +++ b/lib/install.js @@ -16,7 +16,30 @@ module.exports = install -var usage = require('./utils/usage') +// --- node core modules +const fs = require('fs') +// - @npmcli modules +const Arborist = require('@npmcli/arborist') +const log = require('npmlog') +const validate = require('aproba') +// - deps +const asyncMap = require('slide').asyncMap // XXX remove this +const archy = require('archy') +// - npm internal utils +const audit = require('./install/audit.js') +const { + getPrintFundingReport, + getPrintFundingReportJSON +} = require('./install/fund.js') +const usage = require('./utils/usage') +const npm = require('./npm.js') +const output = require('./utils/output.js') +const errorMessage = require('./utils/error-message.js') +// XXX this file doesn't exist anymore; remove this +// const sillyLogTree = require('./util/silly-log-tree.js') +// --- + +const path = require('path') install.usage = usage( 'install', @@ -34,8 +57,6 @@ install.usage = usage( '[--save-prod|--save-dev|--save-optional] [--save-exact] [--no-save]' ) -const npa = require('npm-package-arg') - install.completion = function (opts, cb) { validate('OF', arguments) // install can complete to a folder with a package.json, or any package. @@ -95,69 +116,120 @@ install.completion = function (opts, cb) { cb() } -const Arborist = require('@npmcli/arborist') - -// dependencies -var log = require('npmlog') -// const sillyLogTree = require('./util/silly-log-tree.js') - -// npm internal utils -var npm = require('./npm.js') -var output = require('./utils/output.js') -var saveMetrics = require('./utils/metrics.js').save - -// install specific libraries -var audit = require('./install/audit.js') -var { - getPrintFundingReport, - getPrintFundingReportJSON -} = require('./install/fund.js') -var errorMessage = require('./utils/error-message.js') - -const path = require('path') - -function install (where, args, cb) { +async function install (where, args, cb) { if (!cb) { cb = args args = where where = null } + // the /path/to/node_modules/.. const globalTop = path.resolve(npm.globalDir, '..') - if (!where) { - where = npm.flatOptions.global + const { + dryRun, + global: isGlobalInstall + } = npm.flatOptions + + // Determine where install is happening + // - If `where` is NOT set, then we need to determine if it's a global install + // or if it's a local context install (hence nested ternary) + // - `where` IS SET, just use it + where = (!where) + ? (isGlobalInstall) ? globalTop : npm.prefix - } - const {dryRun} = npm.flatOptions + : where // TODO: Add warnings for other deprecated flags if (npm.config.get('dev')) { log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.') } + // If global install, and NO args passed; user meant "this directory context" if (where === globalTop && !args.length) { args = ['.'] } + + // XXX What does this do? args = args.filter(a => path.resolve(a) !== npm.prefix) - const arb = new Arborist({ - ...this.flatOptions, - path: where, - }) + // Create arborist object to work with + const arb = new Arborist({ ...npm.flatOptions, path: where }) // TODO: // - audit // - funding - // - more logging (archy-ize the tree for silly logging) // - global installs in Arborist - const opt = { - ...this.flatOptions, - add: args, - } - arb[dryRun ? 'buildIdealTree' : 'reify'](opt).then(tree => { - output('TREEEEEEEE', tree) + const arbCallOpts = { ...npm.flatOptions, add: args } + + try { + const start = Date.now() + + const tree = (dryRun) + ? await arb.buildIdealTree(arbCallOpts) + : await arb.reify(arbCallOpts) + + const stop = Date.now() + + // Print list of installed packages and their version + // Buffer with newline + output('') + arb.diff.children.forEach(diff => { + const node = diff.ideal + if (diff.action === 'ADD' && arb.explicitRequests.has(node.name)) { + output(`+ ${node.name}@${node.package.version}`) + } + }) + + /** + * TODO: + * - Add audit + * - Add funding + */ + + printResults({ start, stop }, tree, arb) cb() - }, er => cb(er)) + } catch (err) { + const msg = errorMessage(err) + msg.summary.forEach(s => log.warn.apply(log, s)) + msg.detail.forEach(d => log.verbose.apply(log, d)) + cb(err) + } +} + +function printResults (timing, tree, arb) { + const { unicode } = npm.flatOptions + + // Create archy tree from arborist tree + const reducedTree = reduceArbTree(tree) + const archyTree = archy(reducedTree, '', { unicode }) + + // Collect information about command action taken + const pkgCount = arb.diff.children.length + const contribCount = pkgCount + const added = `added ${pkgCount}` + + const from = `from ${contribCount} contributor${contribCount ? 's' : ''}` + const audited = `audited ${0} package${'s'}` + const time = (timing.stop - timing.start) / 1000 + + // Print summary of command action taken + output(`${added}, ${from}, and ${audited} in ${time}s`) + + // Optionally print the top level dependency tree + log.silly('topLevelDepTree', archyTree) +} + +function reduceArbTree (rootNode) { + const initialValue = { + label: rootNode.name, + nodes: [] + } + const rootChildrenMap = rootNode.children + for (const child of rootChildrenMap.keys()) { + initialValue.nodes.push(child) + } + + return initialValue }