diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md index e444d5fb80d4b..b1df6cb41d73d 100644 --- a/docs/content/using-npm/config.md +++ b/docs/content/using-npm/config.md @@ -148,6 +148,15 @@ you want your scoped package to be publicly viewable (and installable) set `--access=public`. The only valid values for `access` are `public` and `restricted`. Unscoped packages _always_ have an access level of `public`. +#### all + +* Default: `false` +* Type: Boolean + +When running `npm outdated` and `npm ls`, setting `--all` will show all +outdated or installed packages, rather than only those directly depended +upon by the current project. + #### allow-same-version * Default: false @@ -350,16 +359,12 @@ disabled when the environment variable `NO_COLOR` is set to any value. #### depth -* Default: Infinity +* Default: 0 * Type: Number -The depth to go when recursing directories for `npm ls`, -`npm cache ls`, and `npm outdated`. +The depth to go when recursing packages for `npm ls`. -For `npm outdated`, a setting of `Infinity` will be treated as `0` -since that gives more useful information. To show the outdated status -of all packages and dependents, use a large integer value, -e.g., `npm outdated --depth 9999` +To make this default to `Infinity` instead of `0`, set `--all`. #### description diff --git a/lib/config/defaults.js b/lib/config/defaults.js index b47adf673711f..4a78144a24b2c 100644 --- a/lib/config/defaults.js +++ b/lib/config/defaults.js @@ -131,7 +131,7 @@ Object.defineProperty(exports, 'defaults', {get: function () { cidr: null, color: process.env.NO_COLOR == null, - depth: Infinity, + depth: 0, description: true, dev: false, 'dry-run': false, diff --git a/lib/ls.js b/lib/ls.js index 78a2b1d791c7d..2c6171b3c4b71 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -1,554 +1,496 @@ -// show the installed versions of packages -// -// --parseable creates output like this: -// ::: -// Flags are a :-separated list of zero or more indicators - -module.exports = exports = ls - -var path = require('path') -var url = require('url') -var readPackageTree = require('read-package-tree') -var archy = require('archy') -var semver = require('semver') -var color = require('ansicolors') -var moduleName = require('./utils/module-name.js') -var npa = require('npm-package-arg') -var sortedObject = require('sorted-object') -var npm = require('./npm.js') -var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js') -var computeMetadata = require('./install/deps.js').computeMetadata -var readShrinkwrap = require('./install/read-shrinkwrap.js') -var packageId = require('./utils/package-id.js') -var usage = require('./utils/usage') -var output = require('./utils/output.js') - -ls.usage = usage( +'use strict' + +const { resolve } = require('path') +const { EOL } = require('os') + +const archy = require('archy') +const chalk = require('chalk') +const Arborist = require('@npmcli/arborist') +const { breadth } = require('treeverse') +const npa = require('npm-package-arg') + +const npm = require('./npm.js') +const usageUtil = require('./utils/usage.js') +const completion = require('./utils/completion/installed-deep.js') +const output = require('./utils/output.js') + +const _depth = Symbol('depth') +const _dedupe = Symbol('dedupe') +const _filteredBy = Symbol('filteredBy') +const _include = Symbol('include') +const _invalid = Symbol('invalid') +const _name = Symbol('name') +const _missing = Symbol('missing') +const _parent = Symbol('parent') +const _problems = Symbol('problems') +const _required = Symbol('required') +const _type = Symbol('type') + +const usage = usageUtil( 'ls', 'npm ls [[<@scope>/] ...]' ) -ls.completion = require('./utils/completion/installed-deep.js') +const cmd = (args, cb) => ls(args).then(() => cb()).catch(cb) -function ls (args, silent, cb) { - if (typeof cb !== 'function') { - cb = silent - silent = false - } - var dir = path.resolve(npm.dir, '..') - readPackageTree(dir, function (_, physicalTree) { - if (!physicalTree) physicalTree = {package: {}, path: dir} - physicalTree.isTop = true - readShrinkwrap.andInflate(physicalTree, function () { - lsFromTree(dir, computeMetadata(physicalTree), args, silent, cb) - }) - }) -} +const initTree = async ({ arb, args, json }) => { + let tree = await arb.loadActual() + tree[_include] = args.length === 0 + tree[_depth] = 0 -function inList (list, value) { - return list.indexOf(value) !== -1 + return tree } -var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) { - if (typeof cb !== 'function') { - cb = silent - silent = false - } +const isGitNode = (node) => { + if (!node.resolved) return - // npm ls 'foo@~1.3' bar 'baz@<2' - if (!args) { - args = [] - } else { - args = args.map(function (a) { - if (typeof a === 'object' && a.package._requested.type === 'alias') { - return [moduleName(a), `npm:${a.package.name}@${a.package.version}`, a] - } else if (typeof a === 'object') { - return [a.package.name, a.package.version, a] - } else { - var p = npa(a) - var name = p.name - // When version spec is missing, we'll skip using it when filtering. - // Otherwise, `semver.validRange` would return '*', which won't - // match prerelease versions. - var ver = (p.rawSpec && - (semver.validRange(p.rawSpec) || '')) - return [ name, ver, a ] - } - }) + try { + const { type } = npa(node.resolved) + return type === 'git' || type === 'hosted' + } catch (err) { + return false } +} - var data = mutateIntoLogicalTree.asReadInstalled(physicalTree) - - pruneNestedExtraneous(data) - filterByEnv(data) - filterByLink(data) +const isOptional = (node) => + node[_type] === 'optional' || node[_type] === 'peerOptional' - var unlooped = filterFound(unloop(data), args) - var lite = getLite(unlooped) +const isExtraneous = (node, { global }) => + node.extraneous && !global - if (silent) return cb(null, data, lite) +const getProblems = (node, { global }) => { + const problems = new Set() - var long = npm.config.get('long') - var json = npm.config.get('json') - var out - if (json) { - var seen = new Set() - var d = long ? unlooped : lite - // the raw data can be circular - out = JSON.stringify(d, function (k, o) { - if (typeof o === 'object') { - if (seen.has(o)) return '[Circular]' - seen.add(o) - } - return o - }, 2) - } else if (npm.config.get('parseable')) { - out = makeParseable(unlooped, long, dir) - } else if (data) { - out = makeArchy(unlooped, long, dir) + if (node[_missing] && !isOptional(node)) { + problems.add(`missing: ${node.pkgid}, required by ${node[_missing]}`) } - output(out) - if (args.length && !data._found) process.exitCode = 1 + if (node[_invalid]) { + problems.add(`invalid: ${node.pkgid} ${node.path}`) + } - var er - // if any errors were found, then complain and exit status 1 - if (lite.problems && lite.problems.length) { - er = lite.problems.join('\n') + if (node.extraneous && !global) { + problems.add(`extraneous: ${node.pkgid} ${node.path}`) } - cb(er, data, lite) + + return problems } -function pruneNestedExtraneous (data, visited) { - visited = visited || [] - visited.push(data) - for (var i in data.dependencies) { - if (data.dependencies[i].extraneous) { - data.dependencies[i].dependencies = {} - } else if (visited.indexOf(data.dependencies[i]) === -1) { - pruneNestedExtraneous(data.dependencies[i], visited) +// annotates _parent and _include metadata into the resulting +// item obj allowing for filtering out results during output +const augmentItemWithIncludeMetadata = (node, item) => { + item[_parent] = node[_parent] + item[_include] = node[_include] + + // append current item to its parent.nodes which is the + // structure expected by archy in order to print tree + if (node[_include]) { + // includes all ancestors of included node + let p = node[_parent] + while (p) { + p[_include] = true + p = p[_parent] } } + + return item } -function filterByEnv (data) { - var dev = npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('only')) - var production = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only')) - var dependencies = {} - var devKeys = Object.keys(data.devDependencies || []) - var prodKeys = Object.keys(data._dependencies || []) - Object.keys(data.dependencies).forEach(function (name) { - if (!dev && inList(devKeys, name) && !inList(prodKeys, name) && data.dependencies[name].missing) { - return - } +const getHumanOutputItem = (node, { args, color, global, long }) => { + const { pkgid, path } = node + let printable = pkgid - if ((dev && inList(devKeys, name)) || // only --dev - (production && inList(prodKeys, name)) || // only --production - (!dev && !production)) { // no --production|--dev|--only=xxx - dependencies[name] = data.dependencies[name] + // special formatting for top-level package name + if (node.isRoot) { + const hasNoPackageJson = !Object.keys(node.package).length + if (hasNoPackageJson) { + printable = path + } else { + printable += `${long ? EOL : ' '}${path}` } - }) - data.dependencies = dependencies -} - -function filterByLink (data) { - if (npm.config.get('link')) { - var dependencies = {} - Object.keys(data.dependencies).forEach(function (name) { - var dependency = data.dependencies[name] - if (dependency.link) { - dependencies[name] = dependency - } - }) - data.dependencies = dependencies } -} -function alphasort (a, b) { - a = a.toLowerCase() - b = b.toLowerCase() - return a > b ? 1 - : a < b ? -1 : 0 -} + const highlightDepName = + color && args.length && node[_filteredBy] + const missingColor = isOptional(node) + ? chalk.yellow.bgBlack + : chalk.red.bgBlack + const missingMsg = `UNMET ${isOptional(node) ? 'OPTIONAL ' : ''}DEPENDENCY` + const label = + ( + node[_missing] + ? (color ? missingColor(missingMsg) : missingMsg) + ' ' + : '' + ) + + `${highlightDepName ? chalk.yellow.bgBlack(printable) : printable}` + + ( + node[_dedupe] + ? ' ' + (color ? chalk.gray('deduped') : 'deduped') + : '' + ) + + ( + node[_invalid] + ? ' ' + (color ? chalk.red.bgBlack('invalid') : 'invalid') + : '' + ) + + ( + isExtraneous(node, { global }) + ? ' ' + (color ? chalk.green.bgBlack('extraneous') : 'extraneous') + : '' + ) + + (isGitNode(node) ? ` (${node.resolved})` : '') + + (node.isLink ? ` -> ${node.realpath}` : '') + + (long ? `${EOL}${node.package.description || ''}` : '') -function isCruft (data) { - return data.extraneous && data.error && data.error.code === 'ENOTDIR' + return augmentItemWithIncludeMetadata(node, { label, nodes: [] }) } -function getLite (data, noname, depth) { - var lite = {} +const getJsonOutputItem = (node, { global, long }) => { + const item = {} - if (isCruft(data)) return lite + if (node.version) { + item.version = node.version + } + if (node.resolved) { + item.resolved = node.resolved + } - var maxDepth = npm.config.get('depth') + item[_name] = node.name - if (typeof depth === 'undefined') depth = 0 - if (!noname && data.name) lite.name = data.name - if (data.version) lite.version = data.version - if (data.extraneous) { - lite.extraneous = true - lite.problems = lite.problems || [] - lite.problems.push('extraneous: ' + packageId(data) + ' ' + (data.path || '')) + // special formatting for top-level package name + const hasPackageJson = + node && node.package && Object.keys(node.package).length + if (node.isRoot && hasPackageJson) { + item.name = node.package.name || node.name } - if (data.error && data.path !== path.resolve(npm.globalDir, '..') && - (data.error.code !== 'ENOENT' || noname)) { - lite.invalid = true - lite.problems = lite.problems || [] - var message = data.error.message - lite.problems.push('error in ' + data.path + ': ' + message) + if (long) { + item.name = item[_name] + const { dependencies, ...packageInfo } = node.package + Object.assign(item, packageInfo) + item.extraneous = false + item.path = node.path + item._dependencies = node.package.dependencies || {} + item.devDependencies = node.package.devDependencies || {} + item.peerDependencies = node.package.peerDependencies || {} } - if (data._from) { - lite.from = data._from + // augment json output items with extra metadata + if (isExtraneous(node, { global })) { + item.extraneous = true } - - if (data._resolved) { - lite.resolved = data._resolved + if (node[_invalid]) { + item.invalid = true } - - if (data.invalid) { - lite.invalid = true - lite.problems = lite.problems || [] - lite.problems.push('invalid: ' + - packageId(data) + - ' ' + (data.path || '')) + if (node[_missing] && !isOptional(node)) { + item.required = node[_required] + item.missing = true } - - if (data.peerInvalid) { - lite.peerInvalid = true - lite.problems = lite.problems || [] - lite.problems.push('peer dep not met: ' + - packageId(data) + - ' ' + (data.path || '')) + if (node[_include] && node[_problems] && node[_problems].size) { + item.problems = [...node[_problems]] } - var deps = (data.dependencies && Object.keys(data.dependencies)) || [] - if (deps.length) { - lite.dependencies = deps.map(function (d) { - var dep = data.dependencies[d] - if (dep.missing && !dep.optional) { - lite.problems = lite.problems || [] - var p - if (data.depth > maxDepth) { - p = 'max depth reached: ' - } else { - p = 'missing: ' - } - p += d + '@' + dep.requiredBy + - ', required by ' + - packageId(data) - lite.problems.push(p) - if (dep.dependencies) { - return [d, getLite(dep, true)] - } else { - return [d, { required: dep.requiredBy, missing: true }] - } - } else if (dep.peerMissing) { - lite.problems = lite.problems || [] - dep.peerMissing.forEach(function (missing) { - var pdm = 'peer dep missing: ' + - missing.requires + - ', required by ' + - missing.requiredBy - lite.problems.push(pdm) - }) - return [d, { required: dep, peerMissing: true }] - } else if (npm.config.get('json')) { - if (depth === maxDepth) delete dep.dependencies - return [d, getLite(dep, true, depth + 1)] - } - return [d, getLite(dep, true)] - }).reduce(function (deps, d) { - if (d[1].problems) { - lite.problems = lite.problems || [] - lite.problems.push.apply(lite.problems, d[1].problems) - } - deps[d[0]] = d[1] - return deps - }, {}) - } - return lite + return augmentItemWithIncludeMetadata(node, item) } -function unloop (root) { - var queue = [root] - var seen = new Set() - seen.add(root) - - while (queue.length) { - var current = queue.shift() - var deps = current.dependencies = current.dependencies || {} - Object.keys(deps).forEach(function (d) { - var dep = deps[d] - if (dep.missing && !dep.dependencies) return - if (dep.path && seen.has(dep)) { - dep = deps[d] = Object.assign({}, dep) - dep.dependencies = {} - dep._deduped = path.relative(root.path, dep.path).replace(/node_modules\//g, '') - return - } - seen.add(dep) - queue.push(dep) - }) - } - - return root +const filterByEdgesTypes = ({ + dev, + development, + link, + node, + prod, + production, + only, + tree +}) => { + // filter deps by type, allows for: `npm ls --dev`, `npm ls --prod`, + // `npm ls --link`, `npm ls --only=dev`, etc + const filterDev = node === tree && + (dev || development || /^dev(elopment)?$/.test(only)) + const filterProd = node === tree && + (prod || production || /^prod(uction)?$/.test(only)) + const filterLink = node === tree && link + + return (edge) => + (filterDev ? edge.dev : true) && + (filterProd ? (!edge.dev && !edge.peer && !edge.peerOptional) : true) && + (filterLink ? (edge.to && edge.to.isLink) : true) } -function filterFound (root, args) { - if (!args.length) return root - if (!root.dependencies) return root - - // Mark all deps - var toMark = [root] - while (toMark.length) { - var markPkg = toMark.shift() - var markDeps = markPkg.dependencies - if (!markDeps) continue - Object.keys(markDeps).forEach(function (depName) { - var dep = markDeps[depName] - if (dep.peerMissing && !dep._from) return - dep._parent = markPkg - for (var ii = 0; ii < args.length; ii++) { - var argName = args[ii][0] - var argVersion = args[ii][1] - var argRaw = args[ii][2] - var found - if (typeof argRaw === 'object') { - if (dep.path === argRaw.path) { - found = true - } - } else if (depName === argName && argVersion) { - found = semver.satisfies(dep.version, argVersion, true) - } else if (depName === argName) { - // If version is missing from arg, just do a name match. - found = true - } - if (found) { - dep._found = 'explicit' - var parent = dep._parent - while (parent && !parent._found && !parent._deduped) { - parent._found = 'implicit' - parent = parent._parent - } - break - } - } - toMark.push(dep) - }) - } - var toTrim = [root] - while (toTrim.length) { - var trimPkg = toTrim.shift() - var trimDeps = trimPkg.dependencies - if (!trimDeps) continue - trimPkg.dependencies = {} - Object.keys(trimDeps).forEach(function (name) { - var dep = trimDeps[name] - if (!dep._found) return - if (dep._found === 'implicit' && dep._deduped) return - trimPkg.dependencies[name] = dep - toTrim.push(dep) - }) +const appendExtraneousChildren = ({ node }) => + // extraneous children are not represented + // in edges out, so here we add them to the list: + [...node.children.values()] + .filter(i => i.extraneous) + +const mapEdgesToNodes = (edge) => { + let node = edge.to + + // if the edge is linking to a missing node, we go ahead + // and create a new obj that will represent the missing node + if (edge.missing || (edge.optional && !node)) { + const { name, spec } = edge + const pkgid = `${name}@${spec}` + node = { name, pkgid, [_missing]: edge.from.pkgid } } - return root -} -function makeArchy (data, long, dir) { - var out = makeArchy_(data, long, dir, 0) - return archy(out, '', { unicode: npm.config.get('unicode') }) + node[_required] = edge.spec + node[_type] = edge.type + node[_invalid] = edge.invalid + + return node } -function makeArchy_ (data, long, dir, depth, parent, d) { - if (data.missing) { - if (depth - 1 <= npm.config.get('depth')) { - // just missing - var unmet = 'UNMET ' + (data.optional ? 'OPTIONAL ' : '') + 'DEPENDENCY' - if (npm.color) { - if (data.optional) { - unmet = color.bgBlack(color.yellow(unmet)) - } else { - unmet = color.bgBlack(color.red(unmet)) - } - } - var label = data._id || (d + '@' + data.requiredBy) - if (data._found === 'explicit' && data._id) { - if (npm.color) { - label = color.bgBlack(color.yellow(label.trim())) + ' ' - } else { - label = label.trim() + ' ' - } - } - return { - label: unmet + ' ' + label, - nodes: Object.keys(data.dependencies || {}) - .sort(alphasort).filter(function (d) { - return !isCruft(data.dependencies[d]) - }).map(function (d) { - return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d) - }) - } - } else { - return {label: d + '@' + data.requiredBy} +const filterByPositionalArgs = (args, { node }) => + args.length > 0 ? args.some( + (spec) => (node.satisfies && node.satisfies(spec)) + ) : true + +const augmentNodesWithMetadata = ({ + args, + currentDepth, + nodeResult, + parseable, + seenNodes +}) => (node) => { + // if the original edge was a deduped dep, treeverse will fail to + // revisit that node in tree traversal logic, so we make it so that + // we have a diff obj for deduped nodes: + if (seenNodes.has(node.path)) { + node = { + name: node.name, + version: node.version, + pkgid: node.pkgid, + package: node.package, + path: node.path, + isLink: node.isLink, + realpath: node.realpath, + [_invalid]: node[_invalid], + [_missing]: node[_missing], + [_dedupe]: true } - } - - var out = {} - if (data._requested && data._requested.type === 'alias') { - out.label = `${d}@npm:${data._id}` } else { - out.label = data._id || '' - } - if (data._found === 'explicit' && data._id) { - if (npm.color) { - out.label = color.bgBlack(color.yellow(out.label.trim())) + ' ' - } else { - out.label = out.label.trim() + ' ' - } - } - if (data.link) out.label += ' -> ' + data.link + // keeps track of already seen nodes in order to check for dedupes + seenNodes.set(node.path, node) + } + + // _parent is going to be a ref to a treeverse-visited node (returned from + // getHumanOutputItem, getJsonOutputItem, etc) so that we have an easy + // shortcut to place new nodes in their right place during tree traversal + node[_parent] = nodeResult + // _include is the property that allow us to filter based on position args + // e.g: `npm ls foo`, `npm ls simple-output@2` + // _filteredBy is used to apply extra color info to the item that + // was used in args in order to filter + node[_filteredBy] = node[_include] = + filterByPositionalArgs(args, { node: seenNodes.get(node.path), seenNodes }) + // _depth keeps track of how many levels deep tree traversal currently is + // so that we can `npm ls --depth=1` + node[_depth] = currentDepth + 1 + + return node +} - if (data._deduped) { - if (npm.color) { - out.label += ' ' + color.brightBlack('deduped') - } else { - out.label += ' deduped' +const sortAlphabetically = (a, b) => + a.pkgid.localeCompare(b.pkgid) + +const humanOutput = ({ color, result, seenItems, unicode }) => { + // we need to traverse the entire tree in order to determine which items + // should be included (since a nested transitive included dep will make it + // so that all its ancestors should be displayed) + // here is where we put items in their expected place for archy output + for (const item of seenItems) { + if (item[_include] && item[_parent]) { + item[_parent].nodes.push(item) } } - if (data.invalid) { - if (data.realName !== data.name) out.label += ' (' + data.realName + ')' - var invalid = 'invalid' - if (npm.color) invalid = color.bgBlack(color.red(invalid)) - out.label += ' ' + invalid - } - - if (data.peerInvalid) { - var peerInvalid = 'peer invalid' - if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid)) - out.label += ' ' + peerInvalid + if (!result.nodes.length) { + result.nodes = ['(empty)'] } - if (data.peerMissing) { - var peerMissing = 'UNMET PEER DEPENDENCY' + const archyOutput = archy(result, '', { unicode }) + return color ? chalk.reset(archyOutput) : archyOutput +} - if (npm.color) peerMissing = color.bgBlack(color.red(peerMissing)) - out.label = peerMissing + ' ' + out.label - } +const jsonOutput = ({ path, problems, result, rootError, seenItems }) => { + if (problems.size) { + result.problems = [...problems] + } + + if (rootError) { + result.problems = [ + ...(result.problems || []), + ...[`error in ${path}: Failed to parse root package.json`] + ] + result.invalid = true + } + + // we need to traverse the entire tree in order to determine which items + // should be included (since a nested transitive included dep will make it + // so that all its ancestors should be displayed) + // here is where we put items in their expected place for json output + for (const item of seenItems) { + // append current item to its parent item.dependencies obj in order + // to provide a json object structure that represents the installed tree + if (item[_include] && item[_parent]) { + if (!item[_parent].dependencies) { + item[_parent].dependencies = {} + } - if (data.extraneous && data.path !== dir) { - var extraneous = 'extraneous' - if (npm.color) extraneous = color.bgBlack(color.green(extraneous)) - out.label += ' ' + extraneous + item[_parent].dependencies[item[_name]] = item + } } - if (data.error && depth) { - var message = data.error.message - if (message.indexOf('\n')) message = message.slice(0, message.indexOf('\n')) - var error = 'error: ' + message - if (npm.color) error = color.bgRed(color.brightWhite(error)) - out.label += ' ' + error - } + return JSON.stringify(result, null, 2) +} - // add giturl to name@version - if (data._resolved) { - try { - var type = npa(data._resolved).type - var isGit = type === 'git' || type === 'hosted' - if (isGit) { - out.label += ' (' + data._resolved + ')' +const parseableOutput = ({ global, long, seenNodes }) => { + let out = '' + for (const node of seenNodes.values()) { + if (node.path && node[_include]) { + out += node.path + if (long) { + out += `:${node.pkgid}` + out += node.path !== node.realpath ? `:${node.realpath}` : '' + out += isExtraneous(node, { global }) ? ':EXTRANEOUS' : '' + out += node[_invalid] ? ':INVALID' : '' } - } catch (ex) { - // npa threw an exception then it ain't git so whatev + out += EOL } } + return out.trim() +} - if (long) { - if (dir === data.path) out.label += '\n' + dir - out.label += '\n' + getExtras(data) - } else if (dir === data.path) { - if (out.label) out.label += ' ' - out.label += dir - } - - // now all the children. - out.nodes = [] - if (depth <= npm.config.get('depth')) { - out.nodes = Object.keys(data.dependencies || {}) - .sort(alphasort).filter(function (d) { - return !isCruft(data.dependencies[d]) - }).map(function (d) { - return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d) - }) - } - - if (out.nodes.length === 0 && data.path === dir) { - out.nodes = ['(empty)'] - } +const ls = async (args) => { + const { + all, + color, + depth, + json, + long, + global, + parseable, + prefix, + unicode + } = npm.flatOptions + const path = global ? resolve(npm.globalDir, '..') : prefix + const dev = npm.config.get('dev') + const development = npm.config.get('development') + const link = npm.config.get('link') + const only = npm.config.get('only') + const prod = npm.config.get('prod') + const production = npm.config.get('production') + + const arb = new Arborist({ + global, + ...npm.flatOptions, + legacyPeerDeps: false, + path + }) + let tree = await initTree({ + arb, + args, + global, + json + }) - return out -} + const seenItems = new Set() + const seenNodes = new Map() + const problems = new Set() + const depthToPrint = (all || args.length) ? Infinity : (depth || 0) + + // add root node of tree to list of seenNodes + seenNodes.set(tree.path, tree) + + // tree traversal happens here, using treeverse.breadth + const result = await breadth({ + tree, + // recursive method, `node` is going to be the current elem (starting from + // the `tree` obj) that was just visited in the `visit` method below + // `nodeResult` is going to be the returned `item` from `visit` + getChildren (node, nodeResult) { + const shouldSkipChildren = + !(node instanceof Arborist.Node) || (node[_depth] > depthToPrint) + return (shouldSkipChildren) + ? [] + : [...node.edgesOut.values()] + .filter(filterByEdgesTypes({ + dev, + development, + link, + node, + prod, + production, + only, + tree + })) + .map(mapEdgesToNodes) + .concat(appendExtraneousChildren({ node })) + .sort(sortAlphabetically) + .map(augmentNodesWithMetadata({ + args, + currentDepth: node[_depth], + nodeResult, + parseable, + seenNodes + })) + }, + // visit each `node` of the `tree`, returning an `item` - these are + // the elements that will be used to build the final output + visit (node) { + node[_problems] = getProblems(node, { global }) + + const item = json + ? getJsonOutputItem(node, { global, long }) + : parseable + ? null + : getHumanOutputItem(node, { args, color, global, long }) + + // loop through list of node problems to add them to global list + if (node[_include]) { + for (const problem of node[_problems]) { + problems.add(problem) + } + } -function getExtras (data) { - var extras = [] + seenItems.add(item) - if (data.description) extras.push(data.description) - if (data.repository) extras.push(data.repository.url) - if (data.homepage) extras.push(data.homepage) - if (data._from) { - var from = data._from - if (from.indexOf(data.name + '@') === 0) { - from = from.substr(data.name.length + 1) + // return a promise so we don't blow the stack + return Promise.resolve(item) } - var u = url.parse(from) - if (u.protocol) extras.push(from) - } - return extras.join('\n') -} + }) -function makeParseable (data, long, dir, depth, parent, d) { - if (data._deduped) return [] - depth = depth || 0 - if (depth > npm.config.get('depth')) return [ makeParseable_(data, long, dir, depth, parent, d) ] - return [ makeParseable_(data, long, dir, depth, parent, d) ] - .concat(Object.keys(data.dependencies || {}) - .sort(alphasort).map(function (d) { - return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d) - })) - .filter(function (x) { return x && x.length }) - .join('\n') -} + // handle the special case of a broken package.json in the root folder + const [rootError] = tree.errors.filter(e => + e.code === 'EJSONPARSE' && e.path === resolve(path, 'package.json')) -function makeParseable_ (data, long, dir, depth, parent, d) { - if (data.hasOwnProperty('_found') && data._found !== 'explicit') return '' + output( + json + ? jsonOutput({ path, problems, result, rootError, seenItems }) + : parseable + ? parseableOutput({ seenNodes, global, long }) + : humanOutput({ color, result, seenItems, unicode }) + ) - if (data.missing) { - if (depth < npm.config.get('depth')) { - data = npm.config.get('long') - ? path.resolve(parent.path, 'node_modules', d) + - ':' + d + '@' + JSON.stringify(data.requiredBy) + ':INVALID:MISSING' - : '' - } else { - data = path.resolve(dir || '', 'node_modules', d || '') + - (npm.config.get('long') - ? ':' + d + '@' + JSON.stringify(data.requiredBy) + - ':' + // no realpath resolved - ':MAXDEPTH' - : '') - } - - return data + // if filtering items, should exit with error code on no results + if (!tree[_include] && args.length) { + process.exitCode = 1 } - if (!npm.config.get('long')) return data.path + if (rootError) { + throw Object.assign( + new Error('Failed to parse root package.json'), + { code: 'EJSONPARSE' } + ) + } - return data.path + - ':' + (data._id || '') + - ':' + (data.realPath !== data.path ? data.realPath : '') + - (data.extraneous ? ':EXTRANEOUS' : '') + - (data.error && data.path !== path.resolve(npm.globalDir, '..') ? ':ERROR' : '') + - (data.invalid ? ':INVALID' : '') + - (data.peerInvalid ? ':PEERINVALID' : '') + - (data.peerMissing ? ':PEERINVALID:MISSING' : '') + if (problems.size) { + throw Object.assign( + new Error([...problems].join(EOL)), + { code: 'ELSPROBLEMS' } + ) + } } + +module.exports = Object.assign(cmd, { usage, completion }) diff --git a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index 880b698f3cf92..2fc4951d95e9e 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -338,9 +338,9 @@ module.exports = cls => class IdealTreeBuilder extends cls { // be printed by npm-audit-report as if they can be fixed, because // they can't. if (bundler) { - this.log.warn(`audit fix ${node.name}@${node.package.version}`, + this.log.warn(`audit fix ${node.name}@${node.version}`, `${node.location}\nis a bundled dependency of\n${ - bundler.name}@${bundler.package.version} at ${bundler.location}\n` + + bundler.name}@${bundler.version} at ${bundler.location}\n` + 'It cannot be fixed automatically.\n' + `Check for updates to the ${bundler.name} package.`) continue @@ -916,8 +916,8 @@ module.exports = cls => class IdealTreeBuilder extends cls { } // if the version is greater, try to use the new one - const curVer = current.package.version - const newVer = dep.package.version + const curVer = current.version + const newVer = dep.version // always try to replace if the version is greater const tryReplace = curVer && newVer && semver.gte(newVer, curVer) if (tryReplace && current.canReplaceWith(dep)) { diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js index 90d10eeaf7d71..13c3f811f7544 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js @@ -37,8 +37,10 @@ module.exports = cls => class VirtualLoader extends cls { return this[loadFromShrinkwrap](options.root.meta, options.root) return Shrinkwrap.load({ path: this.path }).then(s => { - if (!s.loadedFromDisk && !options.root) - throw new Error('loadVirtual requires existing shrinkwrap file') + if (!s.loadedFromDisk && !options.root) { + const er = new Error('loadVirtual requires existing shrinkwrap file') + throw Object.assign(er, { code: 'ENOLOCK' }) + } // when building the ideal tree, we pass in a root node to this function // otherwise, load it from the root package in the lockfile diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index f1dc231d13ce7..4b399da743cd0 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -396,8 +396,8 @@ module.exports = cls => class Reifier extends cls { // Do the best with what we have, or else remove it from the tree // entirely, since we can't possibly reify it. const res = node.resolved ? this[_registryResolved](node.resolved) - : node.package.name && node.package.version - ? `${node.package.name}@${node.package.version}` + : node.package.name && node.version + ? `${node.package.name}@${node.version}` : null // no idea what this thing is. remove it from the tree. @@ -755,7 +755,7 @@ module.exports = cls => class Reifier extends cls { const res = npa(child.resolved) if (req.registry) { - const version = child.package.version + const version = child.version const range = this[_savePrefix] + version const pname = child.package.name const alias = name !== pname diff --git a/node_modules/@npmcli/arborist/lib/audit-report.js b/node_modules/@npmcli/arborist/lib/audit-report.js index abee3eefd4ede..cd3501c30ed8a 100644 --- a/node_modules/@npmcli/arborist/lib/audit-report.js +++ b/node_modules/@npmcli/arborist/lib/audit-report.js @@ -252,7 +252,7 @@ class AuditReport extends Map { const metaVuln = [] if (!paku) { // not a dep that comes from the registry, apparently - metaVuln.push(p.package.version) + metaVuln.push(p.version) } else { for (const [version, pmani] of Object.entries(paku.versions)) { const spec = this[_getDepSpec](pmani, name) @@ -425,7 +425,7 @@ const prepareBulkData = (tree, opts) => { for (const name of tree.inventory.query('name')) { const set = new Set() for (const node of tree.inventory.query('name', name)) { - set.add(node.package.version) + set.add(node.version) } payload[name] = [...set] } diff --git a/node_modules/@npmcli/arborist/lib/node.js b/node_modules/@npmcli/arborist/lib/node.js index 6ed6bfe215e7b..884a77dca93af 100644 --- a/node_modules/@npmcli/arborist/lib/node.js +++ b/node_modules/@npmcli/arborist/lib/node.js @@ -34,6 +34,7 @@ const Inventory = require('./inventory.js') const Shrinkwrap = require('./shrinkwrap.js') const {normalize} = require('read-package-json-fast') const {getPaths: getBinPaths} = require('bin-links') +const npa = require('npm-package-arg') /* istanbul ignore next */ const dassert = /\barborist\b/.test(process.env.NODE_DEBUG || '') @@ -263,6 +264,19 @@ class Node { return !!(hasInstallScript || install || preinstall || postinstall) } + get version () { + return this[_package].version || '' + } + + get pkgid () { + const { name = '', version = '' } = this.package + // root package will prefer package name over folder name, + // and never be called an alias. + const myname = this.isRoot ? name || this.name : this.name + const alias = !this.isRoot && name && myname !== name ? `npm:${name}@` : '' + return `${myname}@${alias}${version}` + } + get package () { return this[_package] } @@ -502,6 +516,20 @@ class Node { return node.canReplaceWith(this) } + satisfies (requested) { + if (requested instanceof Edge) + return this.name === requested.name && requested.satisfiedBy(this) + + const parsed = npa(requested) + const { name = this.name, rawSpec: spec } = parsed + return this.name === name && this.satisfies(new Edge({ + from: new Node({ path: this.root.path }), + type: 'prod', + name, + spec, + })) + } + matches (node) { // if the nodes are literally the same object, obviously a match. if (node === this) @@ -532,8 +560,8 @@ class Node { // otherwise, conclude that they are different things return this.package.name && node.package.name && this.package.name === node.package.name && - this.package.version && node.package.version && - this.package.version === node.package.version + this.version && node.version && + this.version === node.version } // replace this node with the supplied argument diff --git a/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index f7f96159e84b5..bd33070857461 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/node_modules/@npmcli/arborist/lib/shrinkwrap.js @@ -715,7 +715,7 @@ class Shrinkwrap { const entry = this.yarnLock.entries.get(`${node.name}@${edge.spec}`) if (!entry || - mismatch(node.package.version, entry.version) || + mismatch(node.version, entry.version) || mismatch(node.integrity, entry.integrity) || mismatch(pathFixed, entry.resolved)) return @@ -765,8 +765,8 @@ class Shrinkwrap { if (node === this.tree) { // the root node lock.name = node.package.name || node.name - if (node.package.version) - lock.version = node.package.version + if (node.version) + lock.version = node.version } // npm v6 and before tracked 'from', meaning "the request that led @@ -811,9 +811,9 @@ class Shrinkwrap { node.package && node.package.name && node.package.name !== node.name) - lock.version = `npm:${node.package.name}@${node.package.version}` - else if (node.package && node.package.version) - lock.version = node.package.version + lock.version = `npm:${node.package.name}@${node.version}` + else if (node.package && node.version) + lock.version = node.version if (node.inBundle) lock.bundled = true diff --git a/node_modules/@npmcli/arborist/lib/yarn-lock.js b/node_modules/@npmcli/arborist/lib/yarn-lock.js index 495e98d3a1911..5d8ace12ef973 100644 --- a/node_modules/@npmcli/arborist/lib/yarn-lock.js +++ b/node_modules/@npmcli/arborist/lib/yarn-lock.js @@ -270,8 +270,8 @@ class YarnLock { n.dependencies = node.package.dependencies if (node.package.optionalDependencies) n.optionalDependencies = node.package.optionalDependencies - if (node.package.version) - n.version = node.package.version + if (node.version) + n.version = node.version if (node.resolved) n.resolved = consistentResolve( node.resolved, diff --git a/node_modules/@npmcli/arborist/node_modules/cmd-shim/package.json b/node_modules/@npmcli/arborist/node_modules/cmd-shim/package.json index a7b77db4816c5..6dd9b4b7ec2eb 100644 --- a/node_modules/@npmcli/arborist/node_modules/cmd-shim/package.json +++ b/node_modules/@npmcli/arborist/node_modules/cmd-shim/package.json @@ -1,8 +1,8 @@ { "_from": "cmd-shim@^4.0.1", - "_id": "cmd-shim@4.0.1", + "_id": "cmd-shim@4.0.2", "_inBundle": false, - "_integrity": "sha512-exU/B+ts37psdPUgBhYZsHTGZ6kmZuy0i3L6+TG1BzvrQCfgc4VPpjnY4WxCz7tdMtgtCwXUIu7wLsTZ1LsIRg==", + "_integrity": "sha512-yuOHOon6oFX6kcxVl2jIkvPJsQ/yiKp9fd2dnuoBRZB9GEJ3USWAFCIqfB4xmFou93C3MjjhAprcDwrw+O29VA==", "_location": "/@npmcli/arborist/cmd-shim", "_phantomChildren": {}, "_requested": { @@ -18,17 +18,17 @@ "_requiredBy": [ "/@npmcli/arborist/bin-links" ], - "_resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.0.1.tgz", - "_shasum": "8853812cd7033fae6bae2763d2721231fc784424", + "_resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.0.2.tgz", + "_shasum": "600c8b5962eea0cfd8cb809826b9584a59380c01", "_spec": "cmd-shim@^4.0.1", - "_where": "/Users/claudiahdz/npm/cli/node_modules/@npmcli/arborist/node_modules/bin-links", + "_where": "/Users/ruyadorno/Documents/workspace/cli/node_modules/@npmcli/arborist/node_modules/bin-links", "bugs": { "url": "https://github.com/npm/cmd-shim/issues" }, "bundleDependencies": false, "dependencies": { "graceful-fs": "^4.1.2", - "mkdirp-infer-owner": "^1.0.2" + "mkdirp-infer-owner": "^2.0.0" }, "deprecated": false, "description": "Used in npm for command line application support", @@ -62,5 +62,5 @@ "after": "test/zz-cleanup.js", "check-coverage": true }, - "version": "4.0.1" + "version": "4.0.2" } diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index db65bf0e80e32..cf905a7ddb779 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,13 +1,13 @@ { "_from": "@npmcli/arborist@latest", - "_id": "@npmcli/arborist@0.0.0-pre.21", + "_id": "@npmcli/arborist@0.0.1", "_inBundle": false, - "_integrity": "sha512-WQ/t8dmeo5ea8+8WRTojLetS6rBbyC5i+GAFyH91YGe+T2X3OLhHV0xkif9TEjoGE0Nb2HGYXXk8NBq6hcwmKA==", + "_integrity": "sha512-i/ir1MAzc4dHLMwWCoyszanE2wMMes36iYxf8BeoTFnO+NSwNrznwPD/yNcXO3JaH89h/nzvM9SMrmrOCPOYmQ==", "_location": "/@npmcli/arborist", "_phantomChildren": { "glob": "7.1.4", "graceful-fs": "4.2.3", - "mkdirp-infer-owner": "1.0.2", + "mkdirp-infer-owner": "2.0.0", "npm-normalize-package-bin": "1.0.1", "semver": "7.3.2", "write-file-atomic": "2.4.3" @@ -25,13 +25,12 @@ }, "_requiredBy": [ "#USER", - "/", - "/libnpmfund" + "/" ], - "_resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-0.0.0-pre.21.tgz", - "_shasum": "1b078c9dfa3339af9a13b0c52c056be9e13484c9", + "_resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-0.0.1.tgz", + "_shasum": "6b05fa7dd4207fbf5f592c66e3e06a429f5ccffd", "_spec": "@npmcli/arborist@latest", - "_where": "/Users/isaacs/dev/npm/cli", + "_where": "/Users/ruyadorno/Documents/workspace/cli", "author": { "name": "Isaac Z. Schlueter", "email": "i@izs.me", @@ -50,7 +49,7 @@ "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", "json-stringify-nice": "^1.1.1", - "mkdirp-infer-owner": "^1.0.2", + "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.0.0", "npm-pick-manifest": "^6.1.0", @@ -98,5 +97,5 @@ "esm": false, "timeout": "60" }, - "version": "0.0.0-pre.21" + "version": "0.0.1" } diff --git a/node_modules/libnpmfund/package.json b/node_modules/libnpmfund/package.json index c192120fb745d..04ccf7bb158be 100644 --- a/node_modules/libnpmfund/package.json +++ b/node_modules/libnpmfund/package.json @@ -1,8 +1,8 @@ { "_from": "libnpmfund@latest", - "_id": "libnpmfund@0.0.0-pre.3", + "_id": "libnpmfund@0.0.0", "_inBundle": false, - "_integrity": "sha512-i+vAX+7en4s2RLkrXLpjuprFs3bxY4/Fa+jGkzXhXMPF0p2kZTyrNNI3ahDtW6hoLlq8wJh3v4m8AQllFErM0A==", + "_integrity": "sha512-FI872t5j5hAPPGnKXi6cb7vukUl9kODkW9MIgm1l0FG/zAWhl0Zm/ZHbv5ZYWAEf7ouAhqaDN/9igfqTe73UzQ==", "_location": "/libnpmfund", "_phantomChildren": {}, "_requested": { @@ -19,10 +19,10 @@ "#USER", "/" ], - "_resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-0.0.0-pre.3.tgz", - "_shasum": "1c7f91d21cfc1514b255349033e0c5c3711af627", + "_resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-0.0.0.tgz", + "_shasum": "750525f6c9a0024f6f89ed4b5f091fe07b9ddf62", "_spec": "libnpmfund@latest", - "_where": "/Users/isaacs/dev/npm/cli", + "_where": "/Users/ruyadorno/Documents/workspace/cli", "author": { "name": "npm Inc.", "email": "support@npmjs.com" @@ -38,7 +38,7 @@ } ], "dependencies": { - "@npmcli/arborist": "0.0.0-pre.21" + "@npmcli/arborist": "1 || 0" }, "deprecated": false, "description": "Programmatic API for npm fund", @@ -83,5 +83,5 @@ "tap": { "check-coverage": true }, - "version": "0.0.0-pre.3" + "version": "0.0.0" } diff --git a/node_modules/mkdirp-infer-owner/node_modules/chownr/LICENSE b/node_modules/mkdirp-infer-owner/node_modules/chownr/LICENSE new file mode 100644 index 0000000000000..19129e315fe59 --- /dev/null +++ b/node_modules/mkdirp-infer-owner/node_modules/chownr/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/mkdirp-infer-owner/node_modules/chownr/README.md b/node_modules/mkdirp-infer-owner/node_modules/chownr/README.md new file mode 100644 index 0000000000000..70e9a54a32b8e --- /dev/null +++ b/node_modules/mkdirp-infer-owner/node_modules/chownr/README.md @@ -0,0 +1,3 @@ +Like `chown -R`. + +Takes the same arguments as `fs.chown()` diff --git a/node_modules/mkdirp-infer-owner/node_modules/chownr/chownr.js b/node_modules/mkdirp-infer-owner/node_modules/chownr/chownr.js new file mode 100644 index 0000000000000..0d40932169654 --- /dev/null +++ b/node_modules/mkdirp-infer-owner/node_modules/chownr/chownr.js @@ -0,0 +1,167 @@ +'use strict' +const fs = require('fs') +const path = require('path') + +/* istanbul ignore next */ +const LCHOWN = fs.lchown ? 'lchown' : 'chown' +/* istanbul ignore next */ +const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync' + +/* istanbul ignore next */ +const needEISDIRHandled = fs.lchown && + !process.version.match(/v1[1-9]+\./) && + !process.version.match(/v10\.[6-9]/) + +const lchownSync = (path, uid, gid) => { + try { + return fs[LCHOWNSYNC](path, uid, gid) + } catch (er) { + if (er.code !== 'ENOENT') + throw er + } +} + +/* istanbul ignore next */ +const chownSync = (path, uid, gid) => { + try { + return fs.chownSync(path, uid, gid) + } catch (er) { + if (er.code !== 'ENOENT') + throw er + } +} + +/* istanbul ignore next */ +const handleEISDIR = + needEISDIRHandled ? (path, uid, gid, cb) => er => { + // Node prior to v10 had a very questionable implementation of + // fs.lchown, which would always try to call fs.open on a directory + // Fall back to fs.chown in those cases. + if (!er || er.code !== 'EISDIR') + cb(er) + else + fs.chown(path, uid, gid, cb) + } + : (_, __, ___, cb) => cb + +/* istanbul ignore next */ +const handleEISDirSync = + needEISDIRHandled ? (path, uid, gid) => { + try { + return lchownSync(path, uid, gid) + } catch (er) { + if (er.code !== 'EISDIR') + throw er + chownSync(path, uid, gid) + } + } + : (path, uid, gid) => lchownSync(path, uid, gid) + +// fs.readdir could only accept an options object as of node v6 +const nodeVersion = process.version +let readdir = (path, options, cb) => fs.readdir(path, options, cb) +let readdirSync = (path, options) => fs.readdirSync(path, options) +/* istanbul ignore next */ +if (/^v4\./.test(nodeVersion)) + readdir = (path, options, cb) => fs.readdir(path, cb) + +const chown = (cpath, uid, gid, cb) => { + fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => { + // Skip ENOENT error + cb(er && er.code !== 'ENOENT' ? er : null) + })) +} + +const chownrKid = (p, child, uid, gid, cb) => { + if (typeof child === 'string') + return fs.lstat(path.resolve(p, child), (er, stats) => { + // Skip ENOENT error + if (er) + return cb(er.code !== 'ENOENT' ? er : null) + stats.name = child + chownrKid(p, stats, uid, gid, cb) + }) + + if (child.isDirectory()) { + chownr(path.resolve(p, child.name), uid, gid, er => { + if (er) + return cb(er) + const cpath = path.resolve(p, child.name) + chown(cpath, uid, gid, cb) + }) + } else { + const cpath = path.resolve(p, child.name) + chown(cpath, uid, gid, cb) + } +} + + +const chownr = (p, uid, gid, cb) => { + readdir(p, { withFileTypes: true }, (er, children) => { + // any error other than ENOTDIR or ENOTSUP means it's not readable, + // or doesn't exist. give up. + if (er) { + if (er.code === 'ENOENT') + return cb() + else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP') + return cb(er) + } + if (er || !children.length) + return chown(p, uid, gid, cb) + + let len = children.length + let errState = null + const then = er => { + if (errState) + return + if (er) + return cb(errState = er) + if (-- len === 0) + return chown(p, uid, gid, cb) + } + + children.forEach(child => chownrKid(p, child, uid, gid, then)) + }) +} + +const chownrKidSync = (p, child, uid, gid) => { + if (typeof child === 'string') { + try { + const stats = fs.lstatSync(path.resolve(p, child)) + stats.name = child + child = stats + } catch (er) { + if (er.code === 'ENOENT') + return + else + throw er + } + } + + if (child.isDirectory()) + chownrSync(path.resolve(p, child.name), uid, gid) + + handleEISDirSync(path.resolve(p, child.name), uid, gid) +} + +const chownrSync = (p, uid, gid) => { + let children + try { + children = readdirSync(p, { withFileTypes: true }) + } catch (er) { + if (er.code === 'ENOENT') + return + else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP') + return handleEISDirSync(p, uid, gid) + else + throw er + } + + if (children && children.length) + children.forEach(child => chownrKidSync(p, child, uid, gid)) + + return handleEISDirSync(p, uid, gid) +} + +module.exports = chownr +chownr.sync = chownrSync diff --git a/node_modules/mkdirp-infer-owner/node_modules/chownr/package.json b/node_modules/mkdirp-infer-owner/node_modules/chownr/package.json new file mode 100644 index 0000000000000..862584d79336a --- /dev/null +++ b/node_modules/mkdirp-infer-owner/node_modules/chownr/package.json @@ -0,0 +1,65 @@ +{ + "_from": "chownr@^2.0.0", + "_id": "chownr@2.0.0", + "_inBundle": false, + "_integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "_location": "/mkdirp-infer-owner/chownr", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "chownr@^2.0.0", + "name": "chownr", + "escapedName": "chownr", + "rawSpec": "^2.0.0", + "saveSpec": null, + "fetchSpec": "^2.0.0" + }, + "_requiredBy": [ + "/mkdirp-infer-owner" + ], + "_resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "_shasum": "15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece", + "_spec": "chownr@^2.0.0", + "_where": "/Users/ruyadorno/Documents/workspace/cli/node_modules/mkdirp-infer-owner", + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "bugs": { + "url": "https://github.com/isaacs/chownr/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "like `chown -R`", + "devDependencies": { + "mkdirp": "0.3", + "rimraf": "^2.7.1", + "tap": "^14.10.6" + }, + "engines": { + "node": ">=10" + }, + "files": [ + "chownr.js" + ], + "homepage": "https://github.com/isaacs/chownr#readme", + "license": "ISC", + "main": "chownr.js", + "name": "chownr", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/chownr.git" + }, + "scripts": { + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", + "test": "tap" + }, + "tap": { + "check-coverage": true + }, + "version": "2.0.0" +} diff --git a/node_modules/mkdirp-infer-owner/package.json b/node_modules/mkdirp-infer-owner/package.json index 38baa7fafd91e..bec1aa46b12e9 100644 --- a/node_modules/mkdirp-infer-owner/package.json +++ b/node_modules/mkdirp-infer-owner/package.json @@ -1,28 +1,28 @@ { - "_from": "mkdirp-infer-owner@^1.0.2", - "_id": "mkdirp-infer-owner@1.0.2", + "_from": "mkdirp-infer-owner@^2.0.0", + "_id": "mkdirp-infer-owner@2.0.0", "_inBundle": false, - "_integrity": "sha512-gU3/MnB7QwipTZzStIdCEx1/WqmXWCZzHNU7f/WyW53VbCy/mKGki96WvJuFNkv7S9UZGh/lrIWKa6cbIoW6ag==", + "_integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", "_location": "/mkdirp-infer-owner", "_phantomChildren": {}, "_requested": { "type": "range", "registry": true, - "raw": "mkdirp-infer-owner@^1.0.2", + "raw": "mkdirp-infer-owner@^2.0.0", "name": "mkdirp-infer-owner", "escapedName": "mkdirp-infer-owner", - "rawSpec": "^1.0.2", + "rawSpec": "^2.0.0", "saveSpec": null, - "fetchSpec": "^1.0.2" + "fetchSpec": "^2.0.0" }, "_requiredBy": [ "/@npmcli/arborist", "/@npmcli/arborist/cmd-shim" ], - "_resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-1.0.2.tgz", - "_shasum": "9155d4b5e0f89a835455d7dbae8f3bba3d2fa77c", - "_spec": "mkdirp-infer-owner@^1.0.2", - "_where": "/Users/claudiahdz/npm/cli/node_modules/@npmcli/arborist", + "_resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", + "_shasum": "55d3b368e7d89065c38f32fd38e638f0ab61d316", + "_spec": "mkdirp-infer-owner@^2.0.0", + "_where": "/Users/ruyadorno/Documents/workspace/cli/node_modules/@npmcli/arborist", "author": { "name": "Isaac Z. Schlueter", "email": "i@izs.me", @@ -33,7 +33,7 @@ }, "bundleDependencies": false, "dependencies": { - "chownr": "^1.1.3", + "chownr": "^2.0.0", "infer-owner": "^1.0.4", "mkdirp": "^1.0.3" }, @@ -43,6 +43,9 @@ "require-inject": "^1.4.4", "tap": "^14.10.6" }, + "engines": { + "node": ">=10" + }, "files": [ "index.js" ], @@ -63,5 +66,5 @@ "tap": { "check-coverage": true }, - "version": "1.0.2" + "version": "2.0.0" } diff --git a/node_modules/parse-conflict-json/README.md b/node_modules/parse-conflict-json/README.md index d4ff7ef09abb4..ee9e4fd564199 100644 --- a/node_modules/parse-conflict-json/README.md +++ b/node_modules/parse-conflict-json/README.md @@ -17,6 +17,9 @@ const data = fs.readFileSync('package-lock.json', 'utf8') // preference defaults to 'ours', set to 'theirs' to prefer the other // side's changes. const parsed = parseConflictJson(data, reviverFunction, preference) + +// returns true if the data looks like a conflicted diff file +parsed.isDiff(data) ``` ## Algorithm diff --git a/node_modules/parse-conflict-json/index.js b/node_modules/parse-conflict-json/index.js index 8d230cca545d0..4089fe6bf4f9f 100644 --- a/node_modules/parse-conflict-json/index.js +++ b/node_modules/parse-conflict-json/index.js @@ -89,4 +89,4 @@ const resolve = (parent, ours, theirs) => { return theirs } -module.exports = parseConflictJSON +module.exports = Object.assign(parseConflictJSON, { isDiff }) diff --git a/node_modules/parse-conflict-json/package.json b/node_modules/parse-conflict-json/package.json index b2935bec509c9..79c2c81d4efac 100644 --- a/node_modules/parse-conflict-json/package.json +++ b/node_modules/parse-conflict-json/package.json @@ -1,8 +1,8 @@ { "_from": "parse-conflict-json@^1.0.0", - "_id": "parse-conflict-json@1.0.1", + "_id": "parse-conflict-json@1.1.0", "_inBundle": false, - "_integrity": "sha512-htogfp+nUg/kCA7SnyH6Z0ZRAp6Pij1pgGeXWpbXCFCEdlC1S/sGcu6bUScj3cr140PU5fGKzjwm5i5rdQBWCw==", + "_integrity": "sha512-l9DnPN7S2TDgSm/KklvxUDkV8QMqfTLaIniSSUpY1Wbm+0Dc4qonmCC3hnVhmtJgVLQNIX0O9pOBIkkubjo9wQ==", "_location": "/parse-conflict-json", "_phantomChildren": {}, "_requested": { @@ -16,12 +16,13 @@ "fetchSpec": "^1.0.0" }, "_requiredBy": [ - "/@npmcli/arborist" + "/@npmcli/arborist", + "/libnpmfund/@npmcli/arborist" ], - "_resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.0.1.tgz", - "_shasum": "780b559bda5d6fd64846291dde9a38704a2132d2", + "_resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.0.tgz", + "_shasum": "93d7c5e86badffbd7249411250737e5a175b1294", "_spec": "parse-conflict-json@^1.0.0", - "_where": "/Users/claudiahdz/npm/cli/node_modules/@npmcli/arborist", + "_where": "/Users/ruyadorno/Documents/workspace/cli/node_modules/@npmcli/arborist", "author": { "name": "Isaac Z. Schlueter", "email": "i@izs.me", @@ -61,5 +62,5 @@ "tap": { "check-coverage": true }, - "version": "1.0.1" + "version": "1.1.0" } diff --git a/package-lock.json b/package-lock.json index 7eda373c92bcb..7ee174d917bec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -173,9 +173,9 @@ "dev": true }, "@npmcli/arborist": { - "version": "0.0.0-pre.21", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-0.0.0-pre.21.tgz", - "integrity": "sha512-WQ/t8dmeo5ea8+8WRTojLetS6rBbyC5i+GAFyH91YGe+T2X3OLhHV0xkif9TEjoGE0Nb2HGYXXk8NBq6hcwmKA==", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-0.0.1.tgz", + "integrity": "sha512-i/ir1MAzc4dHLMwWCoyszanE2wMMes36iYxf8BeoTFnO+NSwNrznwPD/yNcXO3JaH89h/nzvM9SMrmrOCPOYmQ==", "requires": { "@npmcli/installed-package-contents": "^1.0.5", "@npmcli/map-workspaces": "0.0.0-pre.1", @@ -185,7 +185,7 @@ "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", "json-stringify-nice": "^1.1.1", - "mkdirp-infer-owner": "^1.0.2", + "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.0.0", "npm-pick-manifest": "^6.1.0", @@ -214,12 +214,12 @@ } }, "cmd-shim": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.0.1.tgz", - "integrity": "sha512-exU/B+ts37psdPUgBhYZsHTGZ6kmZuy0i3L6+TG1BzvrQCfgc4VPpjnY4WxCz7tdMtgtCwXUIu7wLsTZ1LsIRg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.0.2.tgz", + "integrity": "sha512-yuOHOon6oFX6kcxVl2jIkvPJsQ/yiKp9fd2dnuoBRZB9GEJ3USWAFCIqfB4xmFou93C3MjjhAprcDwrw+O29VA==", "requires": { "graceful-fs": "^4.1.2", - "mkdirp-infer-owner": "^1.0.2" + "mkdirp-infer-owner": "^2.0.0" } }, "mkdirp": { @@ -3605,11 +3605,11 @@ } }, "libnpmfund": { - "version": "0.0.0-pre.3", - "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-0.0.0-pre.3.tgz", - "integrity": "sha512-i+vAX+7en4s2RLkrXLpjuprFs3bxY4/Fa+jGkzXhXMPF0p2kZTyrNNI3ahDtW6hoLlq8wJh3v4m8AQllFErM0A==", + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-0.0.0.tgz", + "integrity": "sha512-FI872t5j5hAPPGnKXi6cb7vukUl9kODkW9MIgm1l0FG/zAWhl0Zm/ZHbv5ZYWAEf7ouAhqaDN/9igfqTe73UzQ==", "requires": { - "@npmcli/arborist": "0.0.0-pre.21" + "@npmcli/arborist": "1 || 0" } }, "libnpmhook": { @@ -4118,15 +4118,20 @@ } }, "mkdirp-infer-owner": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-1.0.2.tgz", - "integrity": "sha512-gU3/MnB7QwipTZzStIdCEx1/WqmXWCZzHNU7f/WyW53VbCy/mKGki96WvJuFNkv7S9UZGh/lrIWKa6cbIoW6ag==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", + "integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", "requires": { - "chownr": "^1.1.3", + "chownr": "^2.0.0", "infer-owner": "^1.0.4", "mkdirp": "^1.0.3" }, "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -5110,9 +5115,9 @@ } }, "parse-conflict-json": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.0.1.tgz", - "integrity": "sha512-htogfp+nUg/kCA7SnyH6Z0ZRAp6Pij1pgGeXWpbXCFCEdlC1S/sGcu6bUScj3cr140PU5fGKzjwm5i5rdQBWCw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.0.tgz", + "integrity": "sha512-l9DnPN7S2TDgSm/KklvxUDkV8QMqfTLaIniSSUpY1Wbm+0Dc4qonmCC3hnVhmtJgVLQNIX0O9pOBIkkubjo9wQ==", "requires": { "json-parse-better-errors": "^1.0.2", "just-diff": "^3.0.1", diff --git a/package.json b/package.json index 751aec94a895a..cd9f252a76fe3 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "npx": "./bin/npx-cli.js" }, "dependencies": { - "@npmcli/arborist": "0.0.0-pre.21", + "@npmcli/arborist": "0.0.1", "@npmcli/run-script": "^1.3.1", "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -75,7 +75,7 @@ "json-parse-even-better-errors": "^2.2.0", "lazy-property": "~1.0.0", "libnpmaccess": "^4.0.0", - "libnpmfund": "0.0.0-pre.3", + "libnpmfund": "0.0.0", "libnpmhook": "^6.0.0", "libnpmorg": "^2.0.0", "libnpmpack": "^2.0.0", diff --git a/tap-snapshots/test-lib-ls.js-TAP.test.js b/tap-snapshots/test-lib-ls.js-TAP.test.js index eab6f18790f62..1363eec701422 100644 --- a/tap-snapshots/test-lib-ls.js-TAP.test.js +++ b/tap-snapshots/test-lib-ls.js-TAP.test.js @@ -14,9 +14,9 @@ test-npm-ls@1.0.0 {CWD}/ls-ls--depth-0 exports[`test/lib/ls.js TAP ls --depth=1 > should output tree containing top-level deps and their deps only 1`] = ` test-npm-ls@1.0.0 {CWD}/ls-ls--depth-1 -+-- foo@1.0.0 -| \`-- bar@1.0.0 -\`-- lorem@1.0.0 ++-- a@1.0.0 +| \`-- b@1.0.0 +\`-- e@1.0.0 ` @@ -44,7 +44,7 @@ test-npm-ls@1.0.0 | +-- optional-dep@1.0.0 | Maybe a dep? -+-- peer-dep@1.0.0 extraneous ++-- peer-dep@1.0.0 | Peer-dep description here \`-- prod-dep@1.0.0 A PROD dep kind of dep @@ -65,7 +65,7 @@ test-npm-ls@1.0.0 | +-- optional-dep@1.0.0 | Maybe a dep? -+-- peer-dep@1.0.0 extraneous ++-- peer-dep@1.0.0 | Peer-dep description here \`-- prod-dep@1.0.0 | A PROD dep kind of dep @@ -97,11 +97,11 @@ exports[`test/lib/ls.js TAP ls --parseable --depth=0 > should output tree contai {CWD}/ls-ls-parseable--depth-0/node_modules/lorem ` -exports[`test/lib/ls.js TAP ls --parseable --depth=1 > should output tree containing top-level deps and their deps only 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable --depth=1 > should output parseable containing top-level deps and their deps only 1`] = ` {CWD}/ls-ls-parseable--depth-1 {CWD}/ls-ls-parseable--depth-1/node_modules/foo -{CWD}/ls-ls-parseable--depth-1/node_modules/bar {CWD}/ls-ls-parseable--depth-1/node_modules/lorem +{CWD}/ls-ls-parseable--depth-1/node_modules/bar ` exports[`test/lib/ls.js TAP ls --parseable --dev > should output tree containing dev deps 1`] = ` @@ -117,24 +117,51 @@ exports[`test/lib/ls.js TAP ls --parseable --link > should output tree containin ` exports[`test/lib/ls.js TAP ls --parseable --long --depth=0 > should output tree containing top-level deps with descriptions 1`] = ` -{CWD}/ls-ls-parseable--long-depth-0:test-npm-ls@1.0.0:undefined -{CWD}/ls-ls-parseable--long-depth-0/node_modules/dev-dep:dev-dep@1.0.0:undefined -{CWD}/ls-ls-parseable--long-depth-0/node_modules/lorem:lorem@1.0.0:undefined -{CWD}/ls-ls-parseable--long-depth-0/node_modules/optional-dep:optional-dep@1.0.0:undefined -{CWD}/ls-ls-parseable--long-depth-0/node_modules/peer-dep:peer-dep@1.0.0:undefined:EXTRANEOUS -{CWD}/ls-ls-parseable--long-depth-0/node_modules/prod-dep:prod-dep@1.0.0:undefined +{CWD}/ls-ls-parseable--long-depth-0:test-npm-ls@1.0.0 +{CWD}/ls-ls-parseable--long-depth-0/node_modules/dev-dep:dev-dep@1.0.0 +{CWD}/ls-ls-parseable--long-depth-0/node_modules/lorem:lorem@1.0.0 +{CWD}/ls-ls-parseable--long-depth-0/node_modules/optional-dep:optional-dep@1.0.0 +{CWD}/ls-ls-parseable--long-depth-0/node_modules/peer-dep:peer-dep@1.0.0 +{CWD}/ls-ls-parseable--long-depth-0/node_modules/prod-dep:prod-dep@1.0.0 ` exports[`test/lib/ls.js TAP ls --parseable --long > should output tree info with descriptions 1`] = ` -{CWD}/ls-ls-parseable--long:test-npm-ls@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/dev-dep:dev-dep@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/foo:foo@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/bar:bar@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/lorem:lorem@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/optional-dep:optional-dep@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/peer-dep:peer-dep@1.0.0:undefined:EXTRANEOUS -{CWD}/ls-ls-parseable--long/node_modules/prod-dep:prod-dep@1.0.0:undefined -{CWD}/ls-ls-parseable--long/node_modules/prod-dep/node_modules/bar:bar@2.0.0:undefined +{CWD}/ls-ls-parseable--long:test-npm-ls@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/dev-dep:dev-dep@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/lorem:lorem@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/optional-dep:optional-dep@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/peer-dep:peer-dep@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/prod-dep:prod-dep@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/foo:foo@1.0.0 +{CWD}/ls-ls-parseable--long/node_modules/prod-dep/node_modules/bar:bar@2.0.0 +{CWD}/ls-ls-parseable--long/node_modules/bar:bar@1.0.0 +` + +exports[`test/lib/ls.js TAP ls --parseable --long missing/invalid/extraneous > should output parseable result containing EXTRANEOUS/INVALID labels 1`] = ` +{CWD}/ls-ls-parseable--long-missing-invalid-extraneous:test-npm-ls@1.0.0 +{CWD}/ls-ls-parseable--long-missing-invalid-extraneous/node_modules/foo:foo@1.0.0:INVALID +{CWD}/ls-ls-parseable--long-missing-invalid-extraneous/node_modules/lorem:lorem@1.0.0:EXTRANEOUS +{CWD}/ls-ls-parseable--long-missing-invalid-extraneous/node_modules/bar:bar@1.0.0 +` + +exports[`test/lib/ls.js TAP ls --parseable --long print symlink target location > should output parseable results with symlink targets 1`] = ` +{CWD}/ls-ls-parseable--long-print-symlink-target-location:test-npm-ls@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/dev-dep:dev-dep@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/linked-dep:linked-dep@1.0.0:{CWD}/ls-ls-parseable--long-print-symlink-target-location/linked-dep +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/lorem:lorem@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/optional-dep:optional-dep@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/peer-dep:peer-dep@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/prod-dep:prod-dep@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/foo:foo@1.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/prod-dep/node_modules/bar:bar@2.0.0 +{CWD}/ls-ls-parseable--long-print-symlink-target-location/node_modules/bar:bar@1.0.0 +` + +exports[`test/lib/ls.js TAP ls --parseable --long with extraneous deps > should output long parseable output with extraneous info 1`] = ` +{CWD}/ls-ls-parseable--long-with-extraneous-deps:test-npm-ls@1.0.0 +{CWD}/ls-ls-parseable--long-with-extraneous-deps/node_modules/foo:foo@1.0.0 +{CWD}/ls-ls-parseable--long-with-extraneous-deps/node_modules/lorem:lorem@1.0.0:EXTRANEOUS +{CWD}/ls-ls-parseable--long-with-extraneous-deps/node_modules/bar:bar@1.0.0 ` exports[`test/lib/ls.js TAP ls --parseable --only=development > should output tree containing only development deps 1`] = ` @@ -166,6 +193,12 @@ exports[`test/lib/ls.js TAP ls --parseable cycle deps > should print tree output {CWD}/ls-ls-parseable-cycle-deps/node_modules/b ` +exports[`test/lib/ls.js TAP ls --parseable default --depth value should be 0 > should output parseable output containing only top-level dependencies 1`] = ` +{CWD}/ls-ls-parseable-default-depth-value-should-be-0 +{CWD}/ls-ls-parseable-default-depth-value-should-be-0/node_modules/foo +{CWD}/ls-ls-parseable-default-depth-value-should-be-0/node_modules/lorem +` + exports[`test/lib/ls.js TAP ls --parseable empty location > should print empty result 1`] = ` {CWD}/ls-ls-parseable-empty-location ` @@ -173,8 +206,8 @@ exports[`test/lib/ls.js TAP ls --parseable empty location > should print empty r exports[`test/lib/ls.js TAP ls --parseable extraneous deps > should output containing problems info 1`] = ` {CWD}/ls-ls-parseable-extraneous-deps {CWD}/ls-ls-parseable-extraneous-deps/node_modules/foo -{CWD}/ls-ls-parseable-extraneous-deps/node_modules/bar {CWD}/ls-ls-parseable-extraneous-deps/node_modules/lorem +{CWD}/ls-ls-parseable-extraneous-deps/node_modules/bar ` exports[`test/lib/ls.js TAP ls --parseable from and resolved properties > should not be printed in tree output 1`] = ` @@ -182,30 +215,42 @@ exports[`test/lib/ls.js TAP ls --parseable from and resolved properties > should {CWD}/ls-ls-parseable-from-and-resolved-properties/node_modules/simple-output ` +exports[`test/lib/ls.js TAP ls --parseable global > should print parseable output for global deps 1`] = ` +{CWD}/ls-ls-parseable-global +{CWD}/ls-ls-parseable-global/node_modules/a +{CWD}/ls-ls-parseable-global/node_modules/b +{CWD}/ls-ls-parseable-global/node_modules/b/node_modules/c +` + exports[`test/lib/ls.js TAP ls --parseable json read problems > should print empty result 1`] = ` {CWD}/ls-ls-parseable-json-read-problems ` -exports[`test/lib/ls.js TAP ls --parseable missing package.json > should output json missing name/version of top-level package 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable missing package.json > should log all extraneous deps on error msg 1`] = ` +extraneous: bar@1.0.0 {CWD}/ls-ls-parseable-missing-package-json/node_modules/bar +extraneous: foo@1.0.0 {CWD}/ls-ls-parseable-missing-package-json/node_modules/foo +extraneous: lorem@1.0.0 {CWD}/ls-ls-parseable-missing-package-json/node_modules/lorem +` + +exports[`test/lib/ls.js TAP ls --parseable missing package.json > should output parseable missing name/version of top-level package 1`] = ` {CWD}/ls-ls-parseable-missing-package-json -{CWD}/ls-ls-parseable-missing-package-json/node_modules/foo {CWD}/ls-ls-parseable-missing-package-json/node_modules/bar +{CWD}/ls-ls-parseable-missing-package-json/node_modules/foo {CWD}/ls-ls-parseable-missing-package-json/node_modules/lorem ` -exports[`test/lib/ls.js TAP ls --parseable missing/invalid/extraneous > should output tree containing top-level deps and their deps only 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable missing/invalid/extraneous > should output parseable containing top-level deps and their deps only 1`] = ` {CWD}/ls-ls-parseable-missing-invalid-extraneous {CWD}/ls-ls-parseable-missing-invalid-extraneous/node_modules/foo -{CWD}/ls-ls-parseable-missing-invalid-extraneous/node_modules/bar -{CWD}/ls-ls-parseable-missing-invalid-extraneous/node_modules/ipsum {CWD}/ls-ls-parseable-missing-invalid-extraneous/node_modules/lorem +{CWD}/ls-ls-parseable-missing-invalid-extraneous/node_modules/bar ` -exports[`test/lib/ls.js TAP ls --parseable no args > should output tree representation of dependencies structure 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable no args > should output parseable representation of dependencies structure 1`] = ` {CWD}/ls-ls-parseable-no-args {CWD}/ls-ls-parseable-no-args/node_modules/foo -{CWD}/ls-ls-parseable-no-args/node_modules/bar {CWD}/ls-ls-parseable-no-args/node_modules/lorem +{CWD}/ls-ls-parseable-no-args/node_modules/bar ` exports[`test/lib/ls.js TAP ls --parseable resolved points to git ref > should output tree containing git refs 1`] = ` @@ -213,28 +258,28 @@ exports[`test/lib/ls.js TAP ls --parseable resolved points to git ref > should o {CWD}/ls-ls-parseable-resolved-points-to-git-ref/node_modules/abbrev ` -exports[`test/lib/ls.js TAP ls --parseable unmet optional dep > should output tree with empty entry for missing optional deps 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable unmet optional dep > should output parseable with empty entry for missing optional deps 1`] = ` {CWD}/ls-ls-parseable-unmet-optional-dep {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/dev-dep -{CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/foo -{CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/bar {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/lorem {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/optional-dep {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/peer-dep {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/prod-dep +{CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/foo {CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/prod-dep/node_modules/bar +{CWD}/ls-ls-parseable-unmet-optional-dep/node_modules/bar ` -exports[`test/lib/ls.js TAP ls --parseable unmet peer dep > should output tree signaling missing peer dep in problems 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable unmet peer dep > should output parseable signaling missing peer dep in problems 1`] = ` {CWD}/ls-ls-parseable-unmet-peer-dep {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/dev-dep -{CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/foo -{CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/bar {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/lorem {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/optional-dep {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/peer-dep {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/prod-dep +{CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/foo {CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/prod-dep/node_modules/bar +{CWD}/ls-ls-parseable-unmet-peer-dep/node_modules/bar ` exports[`test/lib/ls.js TAP ls --parseable using aliases > should output tree containing aliases 1`] = ` @@ -242,12 +287,21 @@ exports[`test/lib/ls.js TAP ls --parseable using aliases > should output tree co {CWD}/ls-ls-parseable-using-aliases/node_modules/a ` -exports[`test/lib/ls.js TAP ls --parseable with filter arg > should output tree contaning only occurences of filtered by package 1`] = ` +exports[`test/lib/ls.js TAP ls --parseable with filter arg > should output parseable contaning only occurences of filtered by package 1`] = ` {CWD}/ls-ls-parseable-with-filter-arg/node_modules/lorem ` -exports[`test/lib/ls.js TAP ls --parseable with missing filter arg > should output tree containing no dependencies info 1`] = ` -{CWD}/ls-ls-parseable-with-missing-filter-arg +exports[`test/lib/ls.js TAP ls --parseable with filter arg nested dep > should output parseable contaning only occurences of filtered package 1`] = ` +{CWD}/ls-ls-parseable-with-filter-arg-nested-dep/node_modules/bar +` + +exports[`test/lib/ls.js TAP ls --parseable with missing filter arg > should output parseable output containing no dependencies info 1`] = ` + +` + +exports[`test/lib/ls.js TAP ls --parseable with multiple filter args > should output parseable contaning only occurences of multiple filtered packages and their ancestors 1`] = ` +{CWD}/ls-ls-parseable-with-multiple-filter-args/node_modules/lorem +{CWD}/ls-ls-parseable-with-multiple-filter-args/node_modules/bar ` exports[`test/lib/ls.js TAP ls --production > should output tree containing production deps 1`] = ` @@ -259,6 +313,21 @@ test-npm-ls@1.0.0 {CWD}/ls-ls--production ` +exports[`test/lib/ls.js TAP ls broken resolved field > should NOT print git refs in output tree 1`] = ` +npm-broken-resolved-field-test@1.0.0 {CWD}/ls-ls-broken-resolved-field +\`-- a@1.0.1 + +` + +exports[`test/lib/ls.js TAP ls coloured output > should output tree containing color info 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-coloured-output ++-- foo@1.0.0 invalid +| \`-- bar@1.0.0 ++-- UNMET DEPENDENCY ipsum@^1.0.0 +\`-- lorem@1.0.0 extraneous + +` + exports[`test/lib/ls.js TAP ls cycle deps > should print tree output containing deduped ref 1`] = ` test-npm-ls@1.0.0 {CWD}/ls-ls-cycle-deps \`-- a@1.0.0 @@ -267,6 +336,29 @@ test-npm-ls@1.0.0 {CWD}/ls-ls-cycle-deps ` +exports[`test/lib/ls.js TAP ls cycle deps with filter args > should print tree output containing deduped ref 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-cycle-deps-with-filter-args +\`-- a@1.0.0 + \`-- b@1.0.0 + \`-- a@1.0.0 deduped + +` + +exports[`test/lib/ls.js TAP ls deduped missing dep > should output parseable signaling missing peer dep in problems 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-deduped-missing-dep ++-- a@1.0.0 +| \`-- UNMET DEPENDENCY b@^1.0.0 deduped +\`-- UNMET DEPENDENCY b@^1.0.0 + +` + +exports[`test/lib/ls.js TAP ls default --depth value should be 0 > should output tree containing only top-level dependencies 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-default-depth-value-should-be-0 ++-- foo@1.0.0 +\`-- lorem@1.0.0 + +` + exports[`test/lib/ls.js TAP ls empty location > should print empty result 1`] = ` {CWD}/ls-ls-empty-location \`-- (empty) @@ -287,24 +379,60 @@ test-npm-ls@1.0.0 {CWD}/ls-ls-from-and-resolved-properties ` +exports[`test/lib/ls.js TAP ls global > should print tree and not mark top-level items extraneous 1`] = ` +{CWD}/ls-ls-global ++-- a@1.0.0 +\`-- b@1.0.0 + \`-- c@1.0.0 + +` + +exports[`test/lib/ls.js TAP ls invalid deduped dep > should output tree signaling mismatching peer dep in problems 1`] = ` +invalid-deduped-dep@1.0.0 {CWD}/ls-ls-invalid-deduped-dep ++-- a@1.0.0 +| \`-- b@1.0.0 deduped invalid +\`-- b@1.0.0 invalid + +` + +exports[`test/lib/ls.js TAP ls invalid peer dep > should output tree signaling mismatching peer dep in problems 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-invalid-peer-dep ++-- dev-dep@1.0.0 +| \`-- foo@1.0.0 +| \`-- bar@1.0.0 ++-- lorem@1.0.0 ++-- optional-dep@1.0.0 ++-- peer-dep@1.0.0 invalid +\`-- prod-dep@1.0.0 + \`-- bar@2.0.0 + +` + exports[`test/lib/ls.js TAP ls json read problems > should print empty result 1`] = ` {CWD}/ls-ls-json-read-problems \`-- (empty) ` -exports[`test/lib/ls.js TAP ls missing package.json > should output json missing name/version of top-level package 1`] = ` +exports[`test/lib/ls.js TAP ls missing package.json > should log all extraneous deps on error msg 1`] = ` +extraneous: bar@1.0.0 {CWD}/ls-ls-missing-package-json/node_modules/bar +extraneous: foo@1.0.0 {CWD}/ls-ls-missing-package-json/node_modules/foo +extraneous: lorem@1.0.0 {CWD}/ls-ls-missing-package-json/node_modules/lorem +` + +exports[`test/lib/ls.js TAP ls missing package.json > should output tree missing name/version of top-level package 1`] = ` {CWD}/ls-ls-missing-package-json -+-- foo@1.0.0 -| \`-- bar@1.0.0 -\`-- lorem@1.0.0 ++-- bar@1.0.0 extraneous ++-- foo@1.0.0 extraneous +| \`-- bar@1.0.0 deduped +\`-- lorem@1.0.0 extraneous ` -exports[`test/lib/ls.js TAP ls missing/invalid/extraneous > should output tree containing top-level deps and their deps only 1`] = ` +exports[`test/lib/ls.js TAP ls missing/invalid/extraneous > should output tree containing missing, invalid, extraneous labels 1`] = ` test-npm-ls@1.0.0 {CWD}/ls-ls-missing-invalid-extraneous +-- foo@1.0.0 invalid -| \`-- bar@1.0.0 extraneous +| \`-- bar@1.0.0 +-- UNMET DEPENDENCY ipsum@^1.0.0 \`-- lorem@1.0.0 extraneous @@ -318,36 +446,37 @@ test-npm-ls@1.0.0 {CWD}/ls-ls-no-args ` +exports[`test/lib/ls.js TAP ls print deduped symlinks > should output tree containing linked deps 1`] = ` +print-deduped-symlinks@1.0.0 {CWD}/ls-ls-print-deduped-symlinks ++-- a@1.0.0 +| \`-- b@1.0.0 deduped -> {CWD}/ls-ls-print-deduped-symlinks/b +\`-- b@1.0.0 -> {CWD}/ls-ls-print-deduped-symlinks/b + +` + exports[`test/lib/ls.js TAP ls resolved points to git ref > should output tree containing git refs 1`] = ` test-npm-ls@1.0.0 {CWD}/ls-ls-resolved-points-to-git-ref -\`-- abbrev@1.1.1 (git+https://github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c) +\`-- abbrev@1.1.1 (git+ssh://git@github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c) ` exports[`test/lib/ls.js TAP ls unmet optional dep > should output tree with empty entry for missing optional deps 1`] = ` -test-npm-ls@1.0.0 {CWD}/ls-ls-unmet-optional-dep -+-- dev-dep@1.0.0 -| \`-- foo@1.0.0 -| \`-- bar@1.0.0 -+-- lorem@1.0.0 -+-- UNMET OPTIONAL DEPENDENCY missing-optional-dep@^1.0.0 -+-- optional-dep@1.0.0 invalid -+-- peer-dep@1.0.0 extraneous -\`-- prod-dep@1.0.0 - \`-- bar@2.0.0 - +test-npm-ls@1.0.0 {CWD}/ls-ls-unmet-optional-dep ++-- dev-dep@1.0.0 +| \`-- foo@1.0.0 +| \`-- bar@1.0.0 ++-- lorem@1.0.0 ++-- UNMET OPTIONAL DEPENDENCY missing-optional-dep@^1.0.0 ++-- optional-dep@1.0.0 invalid ++-- peer-dep@1.0.0 +\`-- prod-dep@1.0.0 + \`-- bar@2.0.0 + ` exports[`test/lib/ls.js TAP ls unmet peer dep > should output tree signaling missing peer dep in problems 1`] = ` test-npm-ls@1.0.0 {CWD}/ls-ls-unmet-peer-dep -+-- dev-dep@1.0.0 -| \`-- foo@1.0.0 -| \`-- bar@1.0.0 -+-- lorem@1.0.0 -+-- optional-dep@1.0.0 -+-- UNMET PEER DEPENDENCY peer-dep@1.0.0 extraneous -\`-- prod-dep@1.0.0 - \`-- bar@2.0.0 +\`-- UNMET DEPENDENCY peer-dep@* ` @@ -357,9 +486,42 @@ test-npm-ls@1.0.0 {CWD}/ls-ls-using-aliases ` -exports[`test/lib/ls.js TAP ls with filter arg > should output tree contaning only occurences of filtered by package 1`] = ` -test-npm-ls@1.0.0 {CWD}/ls-ls-with-filter-arg -\`-- lorem@1.0.0 +exports[`test/lib/ls.js TAP ls with args and dedupe entries > should print tree output containing deduped ref 1`] = ` +dedupe-entries@1.0.0 {CWD}/ls-ls-with-args-and-dedupe-entries ++-- @npmcli/a@1.0.0 +| \`-- @npmcli/b@1.1.2 deduped ++-- @npmcli/b@1.1.2 +\`-- @npmcli/c@1.0.0 + \`-- @npmcli/b@1.1.2 deduped + +` + +exports[`test/lib/ls.js TAP ls with args and different order of items > should print tree output containing deduped ref 1`] = ` +dedupe-entries@1.0.0 {CWD}/ls-ls-with-args-and-different-order-of-items ++-- @npmcli/a@1.0.0 +| \`-- @npmcli/c@1.0.0 deduped ++-- @npmcli/b@1.1.2 +| \`-- @npmcli/c@1.0.0 deduped +\`-- @npmcli/c@1.0.0 + +` + +exports[`test/lib/ls.js TAP ls with dot filter arg > should output tree contaning only occurences of filtered by package and coloured output 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-with-dot-filter-arg +\`-- (empty) + +` + +exports[`test/lib/ls.js TAP ls with filter arg > should output tree contaning only occurences of filtered by package and coloured output 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-with-filter-arg +\`-- lorem@1.0.0 + +` + +exports[`test/lib/ls.js TAP ls with filter arg nested dep > should output tree contaning only occurences of filtered package and its ancestors 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-with-filter-arg-nested-dep +\`-- foo@1.0.0 + \`-- bar@1.0.0 ` @@ -368,3 +530,29 @@ test-npm-ls@1.0.0 {CWD}/ls-ls-with-missing-filter-arg \`-- (empty) ` + +exports[`test/lib/ls.js TAP ls with multiple filter args > should output tree contaning only occurences of multiple filtered packages and their ancestors 1`] = ` +test-npm-ls@1.0.0 {CWD}/ls-ls-with-multiple-filter-args ++-- foo@1.0.0 +| \`-- bar@1.0.0 +\`-- lorem@1.0.0 + +` + +exports[`test/lib/ls.js TAP ls with no args dedupe entries > should print tree output containing deduped ref 1`] = ` +dedupe-entries@1.0.0 {CWD}/ls-ls-with-no-args-dedupe-entries ++-- @npmcli/a@1.0.0 +| \`-- @npmcli/b@1.1.2 deduped ++-- @npmcli/b@1.1.2 +\`-- @npmcli/c@1.0.0 + \`-- @npmcli/b@1.1.2 deduped + +` + +exports[`test/lib/ls.js TAP ls with no args dedupe entries and not displaying all > should print tree output containing deduped ref 1`] = ` +dedupe-entries@1.0.0 {CWD}/ls-ls-with-no-args-dedupe-entries-and-not-displaying-all ++-- @npmcli/a@1.0.0 ++-- @npmcli/b@1.1.2 +\`-- @npmcli/c@1.0.0 + +` diff --git a/test/lib/ls.js b/test/lib/ls.js index ae9aabaeed0b5..83a87513b1966 100644 --- a/test/lib/ls.js +++ b/test/lib/ls.js @@ -1,3 +1,5 @@ +const { resolve } = require('path') + const { test } = require('tap') const requireInject = require('require-inject') @@ -77,14 +79,21 @@ const diffDepTypesNmFixture = { } let prefix +let globalDir = 'MISSING_GLOBAL_DIR' let result = '' +// note this _flatOptions representations is for tests-only and does not +// represent exactly the properties found in the actual flatOptions obj const _flatOptions = { + all: true, + color: false, dev: false, depth: Infinity, + global: false, json: false, link: false, only: null, parseable: false, + get prefix () { return prefix }, production: false } const ls = requireInject('../../lib/ls.js', { @@ -93,8 +102,8 @@ const ls = requireInject('../../lib/ls.js', { limit: { fetch: 3 }, - get dir () { return prefix + '/node_modules/' }, - globalDir: '/foo', + get prefix () { return _flatOptions.prefix }, + get globalDir () { return globalDir }, config: { get (key) { return _flatOptions[key] @@ -105,12 +114,18 @@ const ls = requireInject('../../lib/ls.js', { }) const redactCwd = res => - res.replace(/\\/g, '/').replace(new RegExp(__dirname.replace(/\\/g, '/'), 'gi'), '{CWD}') + res && res.replace(/\\/g, '/').replace(new RegExp(__dirname.replace(/\\/g, '/'), 'gi'), '{CWD}') const jsonParse = res => JSON.parse(redactCwd(res)) +const cleanUpResult = (done, t) => { + result = '' + done() +} + test('ls', (t) => { + t.beforeEach(cleanUpResult) _flatOptions.json = false _flatOptions.unicode = false t.test('no args', (t) => { @@ -137,8 +152,12 @@ test('ls', (t) => { ...simpleNmFixture }) ls([], (err) => { - t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output json missing name/version of top-level package') + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.matchSnapshot( + redactCwd(err.message), + 'should log all extraneous deps on error msg' + ) + t.matchSnapshot(redactCwd(result), 'should output tree missing name/version of top-level package') t.end() }) }) @@ -155,8 +174,9 @@ test('ls', (t) => { ...simpleNmFixture }) ls([], (err) => { + t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.equal( - redactCwd(err), + redactCwd(err.message), 'extraneous: lorem@1.0.0 {CWD}/ls-ls-extraneous-deps/node_modules/lorem', 'should log extraneous dep as error' ) @@ -166,6 +186,7 @@ test('ls', (t) => { }) t.test('with filter arg', (t) => { + _flatOptions.color = true prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -179,7 +200,78 @@ test('ls', (t) => { }) ls(['lorem'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package and coloured output') + _flatOptions.color = false + t.end() + }) + }) + + t.test('with dot filter arg', (t) => { + _flatOptions.all = false + _flatOptions.depth = 0 + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + ipsum: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls(['.'], (err) => { + t.ifError(err, 'should not throw on missing dep above current level') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package and coloured output') + _flatOptions.all = true + _flatOptions.depth = Infinity + t.end() + }) + }) + + t.test('with filter arg nested dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls(['bar'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered package and its ancestors') + t.end() + }) + }) + + t.test('with multiple filter args', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0', + ipsum: '^1.0.0' + } + }), + node_modules: { + ...simpleNmFixture.node_modules, + ipsum: { + 'package.json': JSON.stringify({ + name: 'ipsum', + version: '1.0.0' + }) + } + } + }) + ls(['bar@*', 'lorem@1.0.0'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of multiple filtered packages and their ancestors') t.end() }) }) @@ -209,7 +301,31 @@ test('ls', (t) => { }) }) + t.test('default --depth value should be 0', (t) => { + _flatOptions.all = false + _flatOptions.depth = undefined + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') + _flatOptions.all = true + _flatOptions.depth = Infinity + t.end() + }) + }) + t.test('--depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -225,34 +341,74 @@ test('ls', (t) => { ls([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { + _flatOptions.all = false _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', dependencies: { - foo: '^1.0.0', - lorem: '^1.0.0' + a: '^1.0.0', + e: '^1.0.0' } }), - ...simpleNmFixture + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^1.0.0' + } + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + c: '^1.0.0', + d: '*' + } + }) + }, + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + }, + d: { + 'package.json': JSON.stringify({ + name: 'd', + version: '1.0.0' + }) + }, + e: { + 'package.json': JSON.stringify({ + name: 'e', + version: '1.0.0' + }) + } + } }) ls([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps and their deps only') + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -264,9 +420,37 @@ test('ls', (t) => { }), ...simpleNmFixture }) - ls([], () => { - t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps and their deps only') - _flatOptions.depth = Infinity + ls([], (err) => { + t.equal(err.code, 'ELSPROBLEMS', 'should have error code') + t.equal( + redactCwd(err.message), + 'invalid: foo@1.0.0 {CWD}/ls-ls-missing-invalid-extraneous/node_modules/foo\n' + + 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0\n' + + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-missing-invalid-extraneous/node_modules/lorem', + 'should log missing/invalid/extraneous errors' + ) + t.matchSnapshot(redactCwd(result), 'should output tree containing missing, invalid, extraneous labels') + t.end() + }) + }) + + t.test('coloured output', (t) => { + _flatOptions.color = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^2.0.0', + ipsum: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.equal(err.code, 'ELSPROBLEMS', 'should have error code') + t.matchSnapshot(redactCwd(result), 'should output tree containing color info') + _flatOptions.color = false t.end() }) }) @@ -368,6 +552,42 @@ test('ls', (t) => { }) }) + t.test('print deduped symlinks', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'print-deduped-symlinks', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0', + 'b': '^1.0.0' + } + }), + 'b': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }) + }, + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^1.0.0' + } + }) + }, + 'b': t.fixture('symlink', '../b') + } + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') + _flatOptions.link = false + t.end() + }) + }) + t.test('--production', (t) => { _flatOptions.production = true prefix = t.testdir({ @@ -456,6 +676,7 @@ test('ls', (t) => { }) t.test('--long --depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 _flatOptions.long = true prefix = t.testdir({ @@ -480,6 +701,7 @@ test('ls', (t) => { }) ls([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') + _flatOptions.all = true _flatOptions.depth = Infinity _flatOptions.long = false t.end() @@ -491,7 +713,7 @@ test('ls', (t) => { 'package.json': '{broken json' }) ls([], (err) => { - t.match(err, /Failed to parse json/) + t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() }) @@ -506,7 +728,7 @@ test('ls', (t) => { }) }) - t.test('unmet peer dep', (t) => { + t.test('invalid peer dep', (t) => { prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -528,12 +750,97 @@ test('ls', (t) => { ...diffDepTypesNmFixture }) ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree signaling mismatching peer dep in problems') + t.end() + }) + }) + + t.test('invalid deduped dep', (t) => { + _flatOptions.color = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'invalid-deduped-dep', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0', + 'b': '^2.0.0' + } + }), + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^2.0.0' + } + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }) + } + } + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree signaling mismatching peer dep in problems') + _flatOptions.color = false + t.end() + }) + }) + + t.test('deduped missing dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0', + 'b': '^1.0.0' + } + }), + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^1.0.0' + } + }) + } + } + }) + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.match(err.message, /missing: b@\^1.0.0/, 'should list missing dep problem') + t.matchSnapshot(redactCwd(result), 'should output parseable signaling missing peer dep in problems') + t.end() + }) + }) + + t.test('unmet peer dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + peerDependencies: { + 'peer-dep': '*' + } + }) + }) + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.match(err.message, 'missing: peer-dep@*, required by test-npm-ls@1.0.0', 'should have missing peer-dep error msg') t.matchSnapshot(redactCwd(result), 'should output tree signaling missing peer dep in problems') t.end() }) }) t.test('unmet optional dep', (t) => { + _flatOptions.color = true prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -553,45 +860,282 @@ test('ls', (t) => { 'peer-dep': '^1.0.0' } }), - ...diffDepTypesNmFixture + ...diffDepTypesNmFixture + }) + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.match(err.message, /invalid: optional-dep@1.0.0/, 'should have invalid dep error msg') + t.matchSnapshot(redactCwd(result), 'should output tree with empty entry for missing optional deps') + _flatOptions.color = false + t.end() + }) + }) + + t.test('cycle deps', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0' + } + }), + node_modules: { + 'a': { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + a: '^1.0.0' + } + }) + } + } + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + t.end() + }) + }) + + t.test('cycle deps with filter args', (t) => { + _flatOptions.color = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0' + } + }), + node_modules: { + 'a': { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + b: '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + a: '^1.0.0' + } + }) + } + } + }) + ls(['a'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + _flatOptions.color = false + t.end() + }) + }) + + t.test('with no args dedupe entries', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'dedupe-entries', + version: '1.0.0', + dependencies: { + '@npmcli/a': '^1.0.0', + '@npmcli/b': '^1.0.0', + '@npmcli/c': '^1.0.0' + } + }), + node_modules: { + '@npmcli': { + 'a': { + 'package.json': JSON.stringify({ + name: '@npmcli/a', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: '@npmcli/b', + version: '1.1.2' + }) + }, + 'c': { + 'package.json': JSON.stringify({ + name: '@npmcli/c', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + } + } + } + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + t.end() + }) + }) + + t.test('with no args dedupe entries and not displaying all', (t) => { + _flatOptions.all = false + _flatOptions.depth = 0 + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'dedupe-entries', + version: '1.0.0', + dependencies: { + '@npmcli/a': '^1.0.0', + '@npmcli/b': '^1.0.0', + '@npmcli/c': '^1.0.0' + } + }), + node_modules: { + '@npmcli': { + 'a': { + 'package.json': JSON.stringify({ + name: '@npmcli/a', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: '@npmcli/b', + version: '1.1.2' + }) + }, + 'c': { + 'package.json': JSON.stringify({ + name: '@npmcli/c', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + } + } + } + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + _flatOptions.all = true + _flatOptions.depth = Infinity + t.end() + }) + }) + + t.test('with args and dedupe entries', (t) => { + _flatOptions.color = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'dedupe-entries', + version: '1.0.0', + dependencies: { + '@npmcli/a': '^1.0.0', + '@npmcli/b': '^1.0.0', + '@npmcli/c': '^1.0.0' + } + }), + node_modules: { + '@npmcli': { + 'a': { + 'package.json': JSON.stringify({ + name: '@npmcli/a', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: '@npmcli/b', + version: '1.1.2' + }) + }, + 'c': { + 'package.json': JSON.stringify({ + name: '@npmcli/c', + version: '1.0.0', + dependencies: { + '@npmcli/b': '^1.0.0' + } + }) + } + } + } }) - ls([], () => { - t.matchSnapshot(redactCwd(result), 'should output tree with empty entry for missing optional deps') + ls(['@npmcli/b'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + _flatOptions.color = false t.end() }) }) - t.test('cycle deps', (t) => { + t.test('with args and different order of items', (t) => { prefix = t.testdir({ 'package.json': JSON.stringify({ - name: 'test-npm-ls', + name: 'dedupe-entries', version: '1.0.0', dependencies: { - 'a': '^1.0.0' + '@npmcli/a': '^1.0.0', + '@npmcli/b': '^1.0.0', + '@npmcli/c': '^1.0.0' } }), node_modules: { - 'a': { - 'package.json': JSON.stringify({ - name: 'a', - version: '1.0.0', - dependencies: { - b: '^1.0.0' - } - }) - }, - 'b': { - 'package.json': JSON.stringify({ - name: 'b', - version: '1.0.0', - dependencies: { - a: '^1.0.0' - } - }) + '@npmcli': { + 'a': { + 'package.json': JSON.stringify({ + name: '@npmcli/a', + version: '1.0.0', + dependencies: { + '@npmcli/c': '^1.0.0' + } + }) + }, + 'b': { + 'package.json': JSON.stringify({ + name: '@npmcli/b', + version: '1.1.2', + dependencies: { + '@npmcli/c': '^1.0.0' + } + }) + }, + 'c': { + 'package.json': JSON.stringify({ + name: '@npmcli/c', + version: '1.0.0' + }) + } } } }) - ls([], () => { + ls(['@npmcli/c'], (err) => { + t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') t.end() }) @@ -655,12 +1199,57 @@ test('ls', (t) => { } } }) - ls([], () => { + ls([], (err) => { + t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') t.end() }) }) + t.test('broken resolved field', (t) => { + prefix = t.testdir({ + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.1' + }) + } + }, + 'package-lock.json': JSON.stringify({ + 'name': 'npm-broken-resolved-field-test', + 'version': '1.0.0', + 'lockfileVersion': 2, + 'requires': true, + 'packages': { + '': { + 'name': 'a', + 'version': '1.0.1' + } + }, + 'dependencies': { + a: { + 'version': '1.0.1', + 'resolved': 'foo@bar://b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + 'integrity': 'sha512-8AN9lNCcBt5Xeje7fMEEpp5K3rgcAzIpTtAjYb/YMUYu8SbIVF6wz0WqACDVKvpQOUcSfNHZQNLNmue0QSwXOQ==' + } + } + }), + 'package.json': JSON.stringify({ + 'name': 'npm-broken-resolved-field-test', + 'version': '1.0.0', + 'dependencies': { + 'a': '^1.0.1' + } + }) + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should NOT print git refs in output tree') + t.end() + }) + }) + t.test('from and resolved properties', (t) => { prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -704,10 +1293,49 @@ test('ls', (t) => { }) }) + t.test('global', (t) => { + _flatOptions.global = true + const fixtures = t.testdir({ + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0' + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }), + node_modules: { + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + } + } + } + } + }) + + // mimics lib/npm.js globalDir getter but pointing to fixtures + globalDir = resolve(fixtures, 'node_modules') + + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should print tree and not mark top-level items extraneous') + globalDir = 'MISSING_GLOBAL_DIR' + _flatOptions.global = false + t.end() + }) + }) + t.end() }) test('ls --parseable', (t) => { + t.beforeEach(cleanUpResult) _flatOptions.json = false _flatOptions.unicode = false _flatOptions.parseable = true @@ -725,7 +1353,7 @@ test('ls --parseable', (t) => { }) ls([], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree representation of dependencies structure') + t.matchSnapshot(redactCwd(result), 'should output parseable representation of dependencies structure') t.end() }) }) @@ -735,8 +1363,12 @@ test('ls --parseable', (t) => { ...simpleNmFixture }) ls([], (err) => { - t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output json missing name/version of top-level package') + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.matchSnapshot( + redactCwd(err.message), + 'should log all extraneous deps on error msg' + ) + t.matchSnapshot(redactCwd(result), 'should output parseable missing name/version of top-level package') t.end() }) }) @@ -753,11 +1385,7 @@ test('ls --parseable', (t) => { ...simpleNmFixture }) ls([], (err) => { - t.equal( - redactCwd(err), - 'extraneous: lorem@1.0.0 {CWD}/ls-ls-parseable-extraneous-deps/node_modules/lorem', - 'should log extraneous dep as error' - ) + t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.matchSnapshot(redactCwd(result), 'should output containing problems info') t.end() }) @@ -777,7 +1405,54 @@ test('ls --parseable', (t) => { }) ls(['lorem'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurences of filtered by package') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of filtered by package') + t.end() + }) + }) + + t.test('with filter arg nested dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls(['bar'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of filtered package') + t.end() + }) + }) + + t.test('with multiple filter args', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0', + ipsum: '^1.0.0' + } + }), + node_modules: { + ...simpleNmFixture.node_modules, + ipsum: { + 'package.json': JSON.stringify({ + name: 'ipsum', + version: '1.0.0' + }) + } + } + }) + ls(['bar@*', 'lorem@1.0.0'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurences of multiple filtered packages and their ancestors') t.end() }) }) @@ -796,7 +1471,7 @@ test('ls --parseable', (t) => { }) ls(['notadep'], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree containing no dependencies info') + t.matchSnapshot(redactCwd(result), 'should output parseable output containing no dependencies info') t.equal( process.exitCode, 1, @@ -807,7 +1482,31 @@ test('ls --parseable', (t) => { }) }) + t.test('default --depth value should be 0', (t) => { + _flatOptions.all = false + _flatOptions.depth = undefined + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable output containing only top-level dependencies') + _flatOptions.all = true + _flatOptions.depth = Infinity + t.end() + }) + }) + t.test('--depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -823,12 +1522,14 @@ test('ls --parseable', (t) => { ls([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { + _flatOptions.all = false _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -843,14 +1544,14 @@ test('ls --parseable', (t) => { }) ls([], (err) => { t.ifError(err, 'npm ls') - t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps and their deps only') + t.matchSnapshot(redactCwd(result), 'should output parseable containing top-level deps and their deps only') + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -862,9 +1563,9 @@ test('ls --parseable', (t) => { }), ...simpleNmFixture }) - ls([], () => { - t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps and their deps only') - _flatOptions.depth = Infinity + ls([], (err) => { + t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') + t.matchSnapshot(redactCwd(result), 'should output parseable containing top-level deps and their deps only') t.end() }) }) @@ -1053,7 +1754,88 @@ test('ls --parseable', (t) => { }) }) + t.test('--long with extraneous deps', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.equal(err.code, 'ELSPROBLEMS', 'should have error code') + t.match(redactCwd(err.message), 'extraneous: lorem@1.0.0 {CWD}/ls-ls-parseable--long-with-extraneous-deps/node_modules/lorem', 'should have error code') + t.matchSnapshot(redactCwd(result), 'should output long parseable output with extraneous info') + t.end() + }) + }) + + t.test('--long missing/invalid/extraneous', (t) => { + _flatOptions.long = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^2.0.0', + ipsum: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') + t.matchSnapshot(redactCwd(result), 'should output parseable result containing EXTRANEOUS/INVALID labels') + _flatOptions.long = false + t.end() + }) + }) + + t.test('--long print symlink target location', (t) => { + _flatOptions.long = true + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0', + 'linked-dep': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + 'linked-dep': { + 'package.json': JSON.stringify({ + name: 'linked-dep', + version: '1.0.0' + }) + }, + node_modules: { + 'linked-dep': t.fixture('symlink', '../linked-dep'), + ...diffDepTypesNmFixture.node_modules + } + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable results with symlink targets') + _flatOptions.long = false + t.end() + }) + }) + t.test('--long --depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 _flatOptions.long = true prefix = t.testdir({ @@ -1078,6 +1860,7 @@ test('ls --parseable', (t) => { }) ls([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') + _flatOptions.all = true _flatOptions.depth = Infinity _flatOptions.long = false t.end() @@ -1089,7 +1872,7 @@ test('ls --parseable', (t) => { 'package.json': '{broken json' }) ls([], (err) => { - t.match(err, /Failed to parse json/) + t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() }) @@ -1126,7 +1909,7 @@ test('ls --parseable', (t) => { ...diffDepTypesNmFixture }) ls([], () => { - t.matchSnapshot(redactCwd(result), 'should output tree signaling missing peer dep in problems') + t.matchSnapshot(redactCwd(result), 'should output parseable signaling missing peer dep in problems') t.end() }) }) @@ -1153,8 +1936,10 @@ test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture }) - ls([], () => { - t.matchSnapshot(redactCwd(result), 'should output tree with empty entry for missing optional deps') + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.match(err.message, /invalid: optional-dep@1.0.0/, 'should have invalid dep error msg') + t.matchSnapshot(redactCwd(result), 'should output parseable with empty entry for missing optional deps') t.end() }) }) @@ -1297,7 +2082,45 @@ test('ls --parseable', (t) => { } }) ls([], () => { - t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') + t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') + t.end() + }) + }) + + t.test('global', (t) => { + _flatOptions.global = true + const fixtures = t.testdir({ + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0' + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }), + node_modules: { + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + } + } + } + } + }) + + // mimics lib/npm.js globalDir getter but pointing to fixtures + globalDir = resolve(fixtures, 'node_modules') + + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should print parseable output for global deps') + globalDir = 'MISSING_GLOBAL_DIR' + _flatOptions.global = false t.end() }) }) @@ -1306,6 +2129,7 @@ test('ls --parseable', (t) => { }) test('ls --json', (t) => { + t.beforeEach(cleanUpResult) _flatOptions.json = true _flatOptions.parseable = false t.test('no args', (t) => { @@ -1352,13 +2176,29 @@ test('ls --json', (t) => { ...simpleNmFixture }) ls([], (err) => { - t.ifError(err, 'npm ls') + t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.deepEqual( jsonParse(result), { + 'problems': [ + 'extraneous: bar@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/bar', + 'extraneous: foo@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/foo', + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/lorem' + ], 'dependencies': { + 'bar': { + 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: bar@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/bar' + ] + }, 'foo': { 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: foo@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/foo' + ], 'dependencies': { 'bar': { 'version': '1.0.0' @@ -1366,7 +2206,11 @@ test('ls --json', (t) => { } }, 'lorem': { - 'version': '1.0.0' + 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/lorem' + ] } } }, @@ -1389,10 +2233,15 @@ test('ls --json', (t) => { }) ls([], (err) => { t.equal( - redactCwd(err), + redactCwd(err.message), 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-extraneous-deps/node_modules/lorem', 'should log extraneous dep as error' ) + t.equal( + err.code, + 'ELSPROBLEMS', + 'should have ELSPROBLEMS error code' + ) t.deepEqual( jsonParse(result), { @@ -1456,6 +2305,90 @@ test('ls --json', (t) => { }) }) + t.test('with filter arg nested dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls(['bar'], (err) => { + t.ifError(err, 'npm ls') + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + bar: { + version: '1.0.0' + } + } + } + } + }, + 'should output json contaning only occurences of filtered by package' + ) + t.end() + }) + }) + + t.test('with multiple filter args', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0', + ipsum: '^1.0.0' + } + }), + node_modules: { + ...simpleNmFixture.node_modules, + ipsum: { + 'package.json': JSON.stringify({ + name: 'ipsum', + version: '1.0.0' + }) + } + } + }) + ls(['bar@*', 'lorem@1.0.0'], (err) => { + t.ifError(err, 'npm ls') + t.deepEqual( + jsonParse(result), + { + 'version': '1.0.0', + 'name': 'test-npm-ls', + 'dependencies': { + 'foo': { + 'version': '1.0.0', + 'dependencies': { + 'bar': { + 'version': '1.0.0' + } + } + }, + 'lorem': { + 'version': '1.0.0' + } + } + }, + 'should output json contaning only occurences of multiple filtered packages and their ancestors' + ) + t.end() + }) + }) + t.test('with missing filter arg', (t) => { prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -1488,7 +2421,46 @@ test('ls --json', (t) => { }) }) + t.test('default --depth value should now be 0', (t) => { + _flatOptions.all = false + _flatOptions.depth = undefined + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + 'dependencies': { + 'foo': { + 'version': '1.0.0' + }, + 'lorem': { + 'version': '1.0.0' + } + } + }, + 'should output json containing only top-level dependencies' + ) + _flatOptions.all = true + _flatOptions.depth = Infinity + t.end() + }) + }) + t.test('--depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -1519,12 +2491,14 @@ test('ls --json', (t) => { }, 'should output json containing only top-level dependencies' ) + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { + _flatOptions.all = false _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ @@ -1560,13 +2534,13 @@ test('ls --json', (t) => { }, 'should output json containing top-level deps and their deps only' ) + _flatOptions.all = true _flatOptions.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - _flatOptions.depth = 1 prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', @@ -1578,16 +2552,16 @@ test('ls --json', (t) => { }), ...simpleNmFixture }) - ls([], () => { + ls([], (err) => { + t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.deepEqual( jsonParse(result), { 'name': 'test-npm-ls', 'version': '1.0.0', 'problems': [ - 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0', 'invalid: foo@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/foo', - 'extraneous: bar@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/bar', + 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0', 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/lorem' ], 'dependencies': { @@ -1595,16 +2569,11 @@ test('ls --json', (t) => { 'version': '1.0.0', 'invalid': true, 'problems': [ - 'invalid: foo@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/foo', - 'extraneous: bar@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/bar' + 'invalid: foo@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/foo' ], 'dependencies': { 'bar': { - 'version': '1.0.0', - 'extraneous': true, - 'problems': [ - 'extraneous: bar@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/bar' - ] + 'version': '1.0.0' } } }, @@ -1617,13 +2586,15 @@ test('ls --json', (t) => { }, 'ipsum': { 'required': '^1.0.0', - 'missing': true + 'missing': true, + 'problems': [ + 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0' + ] } } }, 'should output json containing top-level deps and their deps only' ) - _flatOptions.depth = Infinity t.end() }) }) @@ -1761,7 +2732,10 @@ test('ls --json', (t) => { name: 'test-npm-ls', version: '1.0.0', dependencies: { - 'linked-dep': { version: '1.0.0' } + 'linked-dep': { + version: '1.0.0', + resolved: 'file:../linked-dep' + } } }, 'should output json containing linked deps' @@ -1853,6 +2827,135 @@ test('ls --json', (t) => { }) }) + t.test('from lockfile', (t) => { + prefix = t.testdir({ + node_modules: { + '@isaacs': { + 'dedupe-tests-a': { + 'package.json': JSON.stringify({ + name: '@isaacs/dedupe-tests-a', + version: '1.0.1' + }), + node_modules: { + '@isaacs': { + 'dedupe-tests-b': { + name: '@isaacs/dedupe-tests-b', + version: '1.0.0' + } + } + } + }, + 'dedupe-tests-b': { + 'package.json': JSON.stringify({ + name: '@isaacs/dedupe-tests-b', + version: '2.0.0' + }) + } + } + }, + 'package-lock.json': JSON.stringify({ + 'name': 'dedupe-lockfile', + 'version': '1.0.0', + 'lockfileVersion': 2, + 'requires': true, + 'packages': { + '': { + 'name': 'dedupe-lockfile', + 'version': '1.0.0', + 'dependencies': { + '@isaacs/dedupe-tests-a': '1.0.1', + '@isaacs/dedupe-tests-b': '1||2' + } + }, + 'node_modules/@isaacs/dedupe-tests-a': { + 'name': '@isaacs/dedupe-tests-a', + 'version': '1.0.1', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + 'integrity': 'sha512-8AN9lNCcBt5Xeje7fMEEpp5K3rgcAzIpTtAjYb/YMUYu8SbIVF6wz0WqACDVKvpQOUcSfNHZQNLNmue0QSwXOQ==', + 'dependencies': { + '@isaacs/dedupe-tests-b': '1' + } + }, + 'node_modules/@isaacs/dedupe-tests-a/node_modules/@isaacs/dedupe-tests-b': { + 'name': '@isaacs/dedupe-tests-b', + 'version': '1.0.0', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + 'integrity': 'sha512-3nmvzIb8QL8OXODzipwoV3U8h9OQD9g9RwOPuSBQqjqSg9JZR1CCFOWNsDUtOfmwY8HFUJV9EAZ124uhqVxq+w==' + }, + 'node_modules/@isaacs/dedupe-tests-b': { + 'name': '@isaacs/dedupe-tests-b', + 'version': '2.0.0', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz', + 'integrity': 'sha512-KTYkpRv9EzlmCg4Gsm/jpclWmRYFCXow8GZKJXjK08sIZBlElTZEa5Bw/UQxIvEfcKmWXczSqItD49Kr8Ax4UA==' + } + }, + 'dependencies': { + '@isaacs/dedupe-tests-a': { + 'version': '1.0.1', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + 'integrity': 'sha512-8AN9lNCcBt5Xeje7fMEEpp5K3rgcAzIpTtAjYb/YMUYu8SbIVF6wz0WqACDVKvpQOUcSfNHZQNLNmue0QSwXOQ==', + 'requires': { + '@isaacs/dedupe-tests-b': '1' + }, + 'dependencies': { + '@isaacs/dedupe-tests-b': { + 'version': '1.0.0', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + 'integrity': 'sha512-3nmvzIb8QL8OXODzipwoV3U8h9OQD9g9RwOPuSBQqjqSg9JZR1CCFOWNsDUtOfmwY8HFUJV9EAZ124uhqVxq+w==' + } + } + }, + '@isaacs/dedupe-tests-b': { + 'version': '2.0.0', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz', + 'integrity': 'sha512-KTYkpRv9EzlmCg4Gsm/jpclWmRYFCXow8GZKJXjK08sIZBlElTZEa5Bw/UQxIvEfcKmWXczSqItD49Kr8Ax4UA==' + } + } + }), + 'package.json': JSON.stringify({ + 'name': 'dedupe-lockfile', + 'version': '1.0.0', + 'dependencies': { + '@isaacs/dedupe-tests-a': '1.0.1', + '@isaacs/dedupe-tests-b': '1||2' + } + }) + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + 'version': '1.0.0', + 'name': 'dedupe-lockfile', + 'dependencies': { + '@isaacs/dedupe-tests-a': { + 'version': '1.0.1', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + 'dependencies': { + '@isaacs/dedupe-tests-b': { + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + 'extraneous': true, + 'problems': [ + 'extraneous: @isaacs/dedupe-tests-b@ {CWD}/ls-ls-json-from-lockfile/node_modules/@isaacs/dedupe-tests-a/node_modules/@isaacs/dedupe-tests-b' + ] + } + } + }, + '@isaacs/dedupe-tests-b': { + 'version': '2.0.0', + 'resolved': 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz' + } + }, + 'problems': [ + 'extraneous: @isaacs/dedupe-tests-b@ {CWD}/ls-ls-json-from-lockfile/node_modules/@isaacs/dedupe-tests-a/node_modules/@isaacs/dedupe-tests-b' + ] + }, + 'should output json containing only prod deps' + ) + t.end() + }) + }) + t.test('--long', (t) => { _flatOptions.long = true prefix = t.testdir({ @@ -1886,15 +2989,12 @@ test('ls --json', (t) => { name: 'peer-dep', description: 'Peer-dep description here', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'peer-dep@1.0.0', - dependencies: {}, devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long/node_modules/peer-dep', - error: null, - extraneous: true + extraneous: false }, 'dev-dep': { name: 'dev-dep', @@ -1908,61 +3008,48 @@ test('ls --json', (t) => { bar: { name: 'bar', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'bar@1.0.0', - dependencies: {}, devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long/node_modules/bar', - error: '[Circular]', extraneous: false } }, - readme: 'ERROR: No README data found!', _id: 'foo@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: { bar: '^1.0.0' }, path: '{CWD}/ls-ls-json--long/node_modules/foo', - error: '[Circular]', extraneous: false } }, - readme: 'ERROR: No README data found!', _id: 'dev-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: { foo: '^1.0.0' }, path: '{CWD}/ls-ls-json--long/node_modules/dev-dep', - error: '[Circular]', extraneous: false }, lorem: { name: 'lorem', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'lorem@1.0.0', - dependencies: {}, devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long/node_modules/lorem', - error: '[Circular]', extraneous: false }, 'optional-dep': { name: 'optional-dep', description: 'Maybe a dep?', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'optional-dep@1.0.0', - dependencies: {}, devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long/node_modules/optional-dep', - error: '[Circular]', extraneous: false }, 'prod-dep': { @@ -1974,36 +3061,28 @@ test('ls --json', (t) => { name: 'bar', description: 'A dep that bars', version: '2.0.0', - readme: 'ERROR: No README data found!', _id: 'bar@2.0.0', - dependencies: {}, devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long/node_modules/prod-dep/node_modules/bar', - error: '[Circular]', extraneous: false } }, - readme: 'ERROR: No README data found!', _id: 'prod-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: { bar: '^2.0.0' }, path: '{CWD}/ls-ls-json--long/node_modules/prod-dep', - error: '[Circular]', extraneous: false } }, devDependencies: { 'dev-dep': '^1.0.0' }, optionalDependencies: { 'optional-dep': '^1.0.0' }, peerDependencies: { 'peer-dep': '^1.0.0' }, - readme: 'ERROR: No README data found!', _id: 'test-npm-ls@1.0.0', - _shrinkwrap: '[Circular]', _dependencies: { 'prod-dep': '^1.0.0', lorem: '^1.0.0', 'optional-dep': '^1.0.0' }, path: '{CWD}/ls-ls-json--long', - error: '[Circular]', extraneous: false }, 'should output long json info' @@ -2014,6 +3093,7 @@ test('ls --json', (t) => { }) t.test('--long --depth=0', (t) => { + _flatOptions.all = false _flatOptions.depth = 0 _flatOptions.long = true prefix = t.testdir({ @@ -2047,80 +3127,68 @@ test('ls --json', (t) => { name: 'peer-dep', description: 'Peer-dep description here', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'peer-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long-depth-0/node_modules/peer-dep', - error: null, - extraneous: true + extraneous: false }, 'dev-dep': { name: 'dev-dep', description: 'A DEV dep kind of dep', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'dev-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: { foo: '^1.0.0' }, path: '{CWD}/ls-ls-json--long-depth-0/node_modules/dev-dep', - error: '[Circular]', extraneous: false }, lorem: { name: 'lorem', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'lorem@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long-depth-0/node_modules/lorem', - error: '[Circular]', extraneous: false }, 'optional-dep': { name: 'optional-dep', description: 'Maybe a dep?', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'optional-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: {}, path: '{CWD}/ls-ls-json--long-depth-0/node_modules/optional-dep', - error: '[Circular]', extraneous: false }, 'prod-dep': { name: 'prod-dep', description: 'A PROD dep kind of dep', version: '1.0.0', - readme: 'ERROR: No README data found!', _id: 'prod-dep@1.0.0', devDependencies: {}, - optionalDependencies: {}, + peerDependencies: {}, _dependencies: { bar: '^2.0.0' }, path: '{CWD}/ls-ls-json--long-depth-0/node_modules/prod-dep', - error: '[Circular]', extraneous: false } }, devDependencies: { 'dev-dep': '^1.0.0' }, optionalDependencies: { 'optional-dep': '^1.0.0' }, peerDependencies: { 'peer-dep': '^1.0.0' }, - readme: 'ERROR: No README data found!', _id: 'test-npm-ls@1.0.0', - _shrinkwrap: '[Circular]', _dependencies: { 'prod-dep': '^1.0.0', lorem: '^1.0.0', 'optional-dep': '^1.0.0' }, path: '{CWD}/ls-ls-json--long-depth-0', - error: '[Circular]', extraneous: false }, 'should output json containing top-level deps in long format' ) + _flatOptions.all = true _flatOptions.depth = Infinity _flatOptions.long = false t.end() @@ -2132,13 +3200,14 @@ test('ls --json', (t) => { 'package.json': '{broken json' }) ls([], (err) => { - t.match(err, /Failed to parse json/) + t.match(err.message, 'Failed to parse root package.json', 'should have missin root package.json msg') + t.match(err.code, 'EJSONPARSE', 'should have EJSONPARSE error code') t.deepEqual( jsonParse(result), { invalid: true, problems: [ - "error in {CWD}/ls-ls-json-json-read-problems: Failed to parse json/nUnexpected token b in JSON at position 1 while parsing near '{broken json'" + 'error in {CWD}/ls-ls-json-json-read-problems: Failed to parse root package.json' ] }, 'should print empty json result' @@ -2181,38 +3250,23 @@ test('ls --json', (t) => { }), ...diffDepTypesNmFixture }) - ls([], () => { + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'Should have ELSPROBLEMS error code') t.deepEqual( jsonParse(result), { name: 'test-npm-ls', version: '1.0.0', problems: [ - 'peer dep missing: peer-dep@^2.0.0, required by test-npm-ls@1.0.0' + 'invalid: peer-dep@1.0.0 {CWD}/ls-ls-json-unmet-peer-dep/node_modules/peer-dep' ], dependencies: { 'peer-dep': { - required: { - name: 'peer-dep', - description: 'Peer-dep description here', - version: '1.0.0', - readme: 'ERROR: No README data found!', - _id: 'peer-dep@1.0.0', - dependencies: {}, - devDependencies: {}, - optionalDependencies: {}, - _dependencies: {}, - path: '{CWD}/ls-ls-json-unmet-peer-dep/node_modules/peer-dep', - error: null, - extraneous: true, - peerMissing: [ - { - requiredBy: 'test-npm-ls@1.0.0', - requires: 'peer-dep@^2.0.0' - } - ] - }, - peerMissing: true + version: '1.0.0', + invalid: true, + problems: [ + 'invalid: peer-dep@1.0.0 {CWD}/ls-ls-json-unmet-peer-dep/node_modules/peer-dep' + ] }, 'dev-dep': { version: '1.0.0', @@ -2256,15 +3310,16 @@ test('ls --json', (t) => { }), ...diffDepTypesNmFixture }) - ls([], () => { + ls([], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.match(err.message, /invalid: optional-dep@1.0.0/, 'should have invalid dep error msg') t.deepEqual( jsonParse(result), { name: 'test-npm-ls', version: '1.0.0', problems: [ - 'invalid: optional-dep@1.0.0 {CWD}/ls-ls-json-unmet-optional-dep/node_modules/optional-dep', // mismatching optional deps get flagged in problems - 'extraneous: peer-dep@1.0.0 {CWD}/ls-ls-json-unmet-optional-dep/node_modules/peer-dep' + 'invalid: optional-dep@1.0.0 {CWD}/ls-ls-json-unmet-optional-dep/node_modules/optional-dep' // mismatching optional deps get flagged in problems ], dependencies: { 'optional-dep': { @@ -2275,11 +3330,7 @@ test('ls --json', (t) => { ] }, 'peer-dep': { - version: '1.0.0', - extraneous: true, - problems: [ - 'extraneous: peer-dep@1.0.0 {CWD}/ls-ls-json-unmet-optional-dep/node_modules/peer-dep' - ] + version: '1.0.0' }, 'dev-dep': { version: '1.0.0', @@ -2389,7 +3440,6 @@ test('ls --json', (t) => { dependencies: { a: { version: '1.0.0', - from: 'a@npm:b', resolved: 'https://localhost:8080/abbrev/-/abbrev-1.1.1.tgz' } } @@ -2438,8 +3488,7 @@ test('ls --json', (t) => { dependencies: { abbrev: { version: '1.1.1', - from: 'git+https://github.com/isaacs/abbrev-js.git', - resolved: 'git+https://github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c' + resolved: 'git+ssh://git@github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c' } } }, @@ -2495,7 +3544,6 @@ test('ls --json', (t) => { dependencies: { 'simple-output': { version: '2.1.1', - from: 'simple-output', resolved: 'https://registry.npmjs.org/simple-output/-/simple-output-2.1.1.tgz' } } @@ -2506,5 +3554,80 @@ test('ls --json', (t) => { }) }) + t.test('node.name fallback if missing root package name', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + version: '1.0.0' + }) + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + 'version': '1.0.0', + 'name': 'ls-ls-json-node-name-fallback-if-missing-root-package-name' + }, + 'should use node.name as key in json result obj' + ) + t.end() + }) + }) + + t.test('global', (t) => { + _flatOptions.global = true + const fixtures = t.testdir({ + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0' + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }), + node_modules: { + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + } + } + } + } + }) + + // mimics lib/npm.js globalDir getter but pointing to fixtures + globalDir = resolve(fixtures, 'node_modules') + + ls([], () => { + t.deepEqual( + jsonParse(result), + { + 'dependencies': { + 'a': { + 'version': '1.0.0' + }, + 'b': { + 'version': '1.0.0', + 'dependencies': { + 'c': { + 'version': '1.0.0' + } + } + } + } + }, + 'should print json output for global deps' + ) + globalDir = 'MISSING_GLOBAL_DIR' + _flatOptions.global = false + t.end() + }) + }) + t.end() })