diff options
Diffstat (limited to 'deps/npm/test/lib/ls.js')
-rw-r--r-- | deps/npm/test/lib/ls.js | 3808 |
1 files changed, 3808 insertions, 0 deletions
diff --git a/deps/npm/test/lib/ls.js b/deps/npm/test/lib/ls.js new file mode 100644 index 0000000000..db4ba81eec --- /dev/null +++ b/deps/npm/test/lib/ls.js @@ -0,0 +1,3808 @@ +const { resolve } = require('path') + +const t = require('tap') +const requireInject = require('require-inject') + +t.cleanSnapshot = str => str.split(/\r\n/).join('\n') + +const simpleNmFixture = { + node_modules: { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + dependencies: { + 'bar': '^1.0.0' + } + }) + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + version: '1.0.0' + }) + }, + 'lorem': { + 'package.json': JSON.stringify({ + name: 'lorem', + version: '1.0.0' + }) + } + } +} + +const diffDepTypesNmFixture = { + node_modules: { + 'dev-dep': { + 'package.json': JSON.stringify({ + name: 'dev-dep', + description: 'A DEV dep kind of dep', + version: '1.0.0', + dependencies: { + 'foo': '^1.0.0' + } + }) + }, + 'prod-dep': { + 'package.json': JSON.stringify({ + name: 'prod-dep', + description: 'A PROD dep kind of dep', + version: '1.0.0', + dependencies: { + 'bar': '^2.0.0' + } + }), + node_modules: { + bar: { + 'package.json': JSON.stringify({ + name: 'bar', + description: 'A dep that bars', + version: '2.0.0' + }) + } + } + }, + 'optional-dep': { + 'package.json': JSON.stringify({ + name: 'optional-dep', + description: 'Maybe a dep?', + version: '1.0.0' + }) + }, + 'peer-dep': { + 'package.json': JSON.stringify({ + name: 'peer-dep', + description: 'Peer-dep description here', + version: '1.0.0' + }) + }, + ...simpleNmFixture.node_modules + } +} + +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', { + '../../lib/npm.js': { + flatOptions: _flatOptions, + limit: { + fetch: 3 + }, + get prefix () { return _flatOptions.prefix }, + get globalDir () { return globalDir }, + config: { + get (key) { + return _flatOptions[key] + } + } + }, + '../../lib/utils/output.js': msg => { result = msg } +}) + +const redactCwd = res => + 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() +} + +t.test('ls', (t) => { + t.beforeEach(cleanUpResult) + _flatOptions.json = false + _flatOptions.unicode = false + t.test('no 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' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output tree representation of dependencies structure') + t.end() + }) + }) + + t.test('missing package.json', (t) => { + prefix = t.testdir({ + ...simpleNmFixture + }) + ls([], (err) => { + 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() + }) + }) + + t.test('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.equal( + redactCwd(err.message), + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-extraneous-deps/node_modules/lorem', + 'should log extraneous dep as error' + ) + t.matchSnapshot(redactCwd(result), 'should output containing problems info') + t.end() + }) + }) + + t.test('with filter arg', (t) => { + _flatOptions.color = true + 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(['lorem'], (err) => { + t.ifError(err, 'npm ls') + 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() + }) + }) + + t.test('with missing filter arg', (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(['notadep'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output tree containing no dependencies info') + t.equal( + process.exitCode, + 1, + 'should exit with error code 1' + ) + process.exitCode = 0 + t.end() + }) + }) + + 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({ + 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=1', (t) => { + _flatOptions.all = false + _flatOptions.depth = 1 + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: '^1.0.0', + e: '^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: { + 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) => { + 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.equal( + redactCwd(err.message).replace(/\r\n/g, '\n'), + '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() + }) + }) + + t.test('--dev', (t) => { + _flatOptions.dev = 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') + _flatOptions.dev = false + t.end() + }) + }) + + t.test('--only=development', (t) => { + _flatOptions.only = 'development' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') + _flatOptions.only = null + t.end() + }) + }) + + t.test('--link', (t) => { + _flatOptions.link = 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([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') + _flatOptions.link = false + t.end() + }) + }) + + 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({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') + _flatOptions.production = false + t.end() + }) + }) + + t.test('--only=prod', (t) => { + _flatOptions.only = 'prod' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') + _flatOptions.only = null + t.end() + }) + }) + + t.test('--long', (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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') + _flatOptions.long = true + t.end() + }) + }) + + t.test('--long --depth=0', (t) => { + _flatOptions.all = false + _flatOptions.depth = 0 + _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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + 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() + }) + }) + + t.test('json read problems', (t) => { + prefix = t.testdir({ + 'package.json': '{broken json' + }) + ls([], (err) => { + t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') + t.matchSnapshot(redactCwd(result), 'should print empty result') + t.end() + }) + }) + + t.test('empty location', (t) => { + prefix = t.testdir({}) + ls([], (err) => { + t.ifError(err, 'should not error out on empty locations') + t.matchSnapshot(redactCwd(result), 'should print empty result') + t.end() + }) + }) + + t.test('invalid peer dep', (t) => { + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^2.0.0' // mismatching version # + } + }), + ...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', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'missing-optional-dep': '^1.0.0', + 'optional-dep': '^2.0.0' // mismatching version # + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...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(['@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('with args and different order of items', (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/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(['@npmcli/c'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') + t.end() + }) + }) + + t.test('using aliases', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: 'npm:b@1.0.0' + } + }), + node_modules: { + 'a': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + _from: 'a@npm:b', + _resolved: 'https://localhost:8080/abbrev/-/abbrev-1.1.1.tgz', + _requested: { + type: 'alias' + } + }) + } + } + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') + t.end() + }) + }) + + t.test('resolved points to git ref', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'abbrev': 'git+https://github.com/isaacs/abbrev-js.git' + } + }), + node_modules: { + 'abbrev': { + 'package.json': JSON.stringify({ + name: 'abbrev', + version: '1.1.1', + _id: 'abbrev@1.1.1', + _from: 'git+https://github.com/isaacs/abbrev-js.git', + _resolved: 'git+https://github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + _requested: { + type: 'git', + raw: 'git+https:github.com/isaacs/abbrev-js.git', + rawSpec: 'git+https:github.com/isaacs/abbrev-js.git', + saveSpec: 'git+https://github.com/isaacs/abbrev-js.git', + fetchSpec: 'https://github.com/isaacs/abbrev-js.git', + gitCommittish: null + } + }) + } + } + }) + 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({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'simple-output': '^2.0.0' + } + }), + node_modules: { + 'simple-output': { + 'package.json': JSON.stringify({ + name: 'simple-output', + version: '2.1.1', + _from: 'simple-output', + _id: 'simple-output@2.1.1', + _resolved: 'https://registry.npmjs.org/simple-output/-/simple-output-2.1.1.tgz', + _requested: { + type: 'tag', + registry: true, + raw: 'simple-output', + name: 'simple-output', + escapedName: 'simple-output', + rawSpec: '', + saveSpec: null, + fetchSpec: 'latest' + }, + _requiredBy: [ + '#USER', + '/' + ], + _shasum: '3c07708ec9ef3e3c985cf0ddd67df09ab8ec2abc', + _spec: 'simple-output' + }) + } + } + }) + ls([], () => { + 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 tree and not mark top-level items extraneous') + globalDir = 'MISSING_GLOBAL_DIR' + _flatOptions.global = false + t.end() + }) + }) + + t.test('filtering by child of missing dep', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'filter-by-child-of-missing-dep', + version: '1.0.0', + dependencies: { + 'a': '^1.0.0' + } + }), + node_modules: { + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + c: '^1.0.0' + } + }) + }, + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + }, + d: { + 'package.json': JSON.stringify({ + name: 'd', + version: '1.0.0', + dependencies: { + c: '^2.0.0' + } + }), + node_modules: { + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '2.0.0' + }) + } + } + } + } + }) + + ls(['c'], (err) => { + t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') + t.matchSnapshot(redactCwd(result), 'should print tree and not duplicate child of missing items') + t.end() + }) + }) + + t.test('loading a tree containing workspaces', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'filter-by-child-of-missing-dep', + version: '1.0.0', + workspaces: [ + './a', + './b' + ] + }), + node_modules: { + a: t.fixture('symlink', '../a'), + b: t.fixture('symlink', '../b'), + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0' + }) + } + }, + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + c: '^1.0.0' + } + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0' + }) + } + }) + + ls([], (err) => { + t.ifError(err, 'should NOT have ELSPROBLEMS error code') + t.matchSnapshot(redactCwd(result), 'should list workspaces properly') + + // should also be able to filter out one of the workspaces + ls(['a'], (err) => { + t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') + t.matchSnapshot(redactCwd(result), 'should filter single workspace') + + t.end() + }) + }) + }) + + t.test('filter pkg arg using depth option', (t) => { + _flatOptions.depth = 0 + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-pkg-arg-filter-with-depth-opt', + 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' + }) + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + c: '^1.0.0' + } + }) + }, + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0', + dependencies: { + d: '^1.0.0' + } + }) + }, + d: { + 'package.json': JSON.stringify({ + name: 'd', + version: '1.0.0', + dependencies: { + a: '^1.0.0' + } + }) + } + } + }) + + t.plan(6) + ls(['a'], (err) => { + t.ifError(err, 'should NOT have ELSPROBLEMS error code') + t.matchSnapshot(redactCwd(result), 'should list a in top-level only') + + ls(['d'], (err) => { + t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') + t.matchSnapshot(redactCwd(result), 'should print empty results msg') + + // if no --depth config is defined, should print path to dep + _flatOptions.depth = null // default config value + ls(['d'], (err) => { + t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') + t.matchSnapshot(redactCwd(result), 'should print expected result') + }) + }) + }) + }) + + t.teardown(() => { + _flatOptions.depth = Infinity + }) + + t.end() +}) + +t.test('ls --parseable', (t) => { + t.beforeEach(cleanUpResult) + _flatOptions.json = false + _flatOptions.unicode = false + _flatOptions.parseable = true + t.test('no 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' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable representation of dependencies structure') + t.end() + }) + }) + + t.test('missing package.json', (t) => { + prefix = t.testdir({ + ...simpleNmFixture + }) + ls([], (err) => { + 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() + }) + }) + + t.test('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.matchSnapshot(redactCwd(result), 'should output containing problems info') + t.end() + }) + }) + + t.test('with filter arg', (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(['lorem'], (err) => { + t.ifError(err, 'npm ls') + 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() + }) + }) + + t.test('with missing filter arg', (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(['notadep'], (err) => { + t.ifError(err, 'npm ls') + t.matchSnapshot(redactCwd(result), 'should output parseable output containing no dependencies info') + t.equal( + process.exitCode, + 1, + 'should exit with error code 1' + ) + process.exitCode = 0 + t.end() + }) + }) + + 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({ + 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=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' + } + }), + ...simpleNmFixture + }) + ls([], (err) => { + t.ifError(err, 'npm ls') + 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) => { + 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 containing top-level deps and their deps only') + t.end() + }) + }) + + t.test('--dev', (t) => { + _flatOptions.dev = 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') + _flatOptions.dev = false + t.end() + }) + }) + + t.test('--only=development', (t) => { + _flatOptions.only = 'development' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') + _flatOptions.only = null + t.end() + }) + }) + + t.test('--link', (t) => { + _flatOptions.link = 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([], () => { + 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({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') + _flatOptions.production = false + t.end() + }) + }) + + t.test('--only=prod', (t) => { + _flatOptions.only = 'prod' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') + _flatOptions.only = null + t.end() + }) + }) + + t.test('--long', (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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') + _flatOptions.long = true + t.end() + }) + }) + + 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({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + 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() + }) + }) + + t.test('json read problems', (t) => { + prefix = t.testdir({ + 'package.json': '{broken json' + }) + ls([], (err) => { + t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') + t.matchSnapshot(redactCwd(result), 'should print empty result') + t.end() + }) + }) + + t.test('empty location', (t) => { + prefix = t.testdir({}) + ls([], (err) => { + t.ifError(err, 'should not error out on empty locations') + t.matchSnapshot(redactCwd(result), 'should print empty result') + t.end() + }) + }) + + t.test('unmet peer dep', (t) => { + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^2.0.0' // mismatching version # + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output parseable signaling missing peer dep in problems') + t.end() + }) + }) + + t.test('unmet optional dep', (t) => { + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'missing-optional-dep': '^1.0.0', + 'optional-dep': '^2.0.0' // mismatching version # + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...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 parseable with empty entry for missing optional deps') + 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([], () => { + t.matchSnapshot(redactCwd(result), 'should print tree output ommiting deduped ref') + t.end() + }) + }) + + t.test('using aliases', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: 'npm:b@1.0.0' + } + }), + node_modules: { + 'a': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + _from: 'a@npm:b', + _resolved: 'https://localhost:8080/abbrev/-/abbrev-1.1.1.tgz', + _requested: { + type: 'alias' + } + }) + } + } + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') + t.end() + }) + }) + + t.test('resolved points to git ref', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'abbrev': 'git+https://github.com/isaacs/abbrev-js.git' + } + }), + node_modules: { + 'abbrev': { + 'package.json': JSON.stringify({ + name: 'abbrev', + version: '1.1.1', + _id: 'abbrev@1.1.1', + _from: 'git+https://github.com/isaacs/abbrev-js.git', + _resolved: 'git+https://github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + _requested: { + type: 'git', + raw: 'git+https:github.com/isaacs/abbrev-js.git', + rawSpec: 'git+https:github.com/isaacs/abbrev-js.git', + saveSpec: 'git+https://github.com/isaacs/abbrev-js.git', + fetchSpec: 'https://github.com/isaacs/abbrev-js.git', + gitCommittish: null + } + }) + } + } + }) + ls([], () => { + t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') + t.end() + }) + }) + + t.test('from and resolved properties', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'simple-output': '^2.0.0' + } + }), + node_modules: { + 'simple-output': { + 'package.json': JSON.stringify({ + name: 'simple-output', + version: '2.1.1', + _from: 'simple-output', + _id: 'simple-output@2.1.1', + _resolved: 'https://registry.npmjs.org/simple-output/-/simple-output-2.1.1.tgz', + _requested: { + type: 'tag', + registry: true, + raw: 'simple-output', + name: 'simple-output', + escapedName: 'simple-output', + rawSpec: '', + saveSpec: null, + fetchSpec: 'latest' + }, + _requiredBy: [ + '#USER', + '/' + ], + _shasum: '3c07708ec9ef3e3c985cf0ddd67df09ab8ec2abc', + _spec: 'simple-output' + }) + } + } + }) + ls([], () => { + 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() + }) + }) + + t.end() +}) + +t.test('ls --json', (t) => { + t.beforeEach(cleanUpResult) + _flatOptions.json = true + _flatOptions.parseable = false + t.test('no 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' + } + }), + ...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', + 'dependencies': { + 'bar': { + 'version': '1.0.0' + } + } + }, + 'lorem': { + 'version': '1.0.0' + } + } + }, + 'should output json representation of dependencies structure' + ) + t.end() + }) + }) + + t.test('missing package.json', (t) => { + prefix = t.testdir({ + ...simpleNmFixture + }) + ls([], (err) => { + 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' + } + } + }, + 'lorem': { + 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-missing-package-json/node_modules/lorem' + ] + } + } + }, + 'should output json missing name/version of top-level package' + ) + t.end() + }) + }) + + t.test('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( + 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), + { + name: 'test-npm-ls', + version: '1.0.0', + 'problems': [ + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-extraneous-deps/node_modules/lorem' + ], + 'dependencies': { + 'foo': { + 'version': '1.0.0', + 'dependencies': { + 'bar': { + 'version': '1.0.0' + } + } + }, + 'lorem': { + 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-extraneous-deps/node_modules/lorem' + ] + } + } + }, + 'should output json containing problems info' + ) + t.end() + }) + }) + + t.test('with filter arg', (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(['lorem'], (err) => { + t.ifError(err, 'npm ls') + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + 'dependencies': { + 'lorem': { + 'version': '1.0.0' + } + } + }, + 'should output json 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.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({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + lorem: '^1.0.0' + } + }), + ...simpleNmFixture + }) + ls(['notadep'], (err) => { + t.ifError(err, 'npm ls') + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0' + }, + 'should output json containing no dependencies info' + ) + t.equal( + process.exitCode, + 1, + 'should exit with error code 1' + ) + process.exitCode = 0 + t.end() + }) + }) + + 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({ + 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=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' + } + }), + ...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', + 'dependencies': { + 'bar': { + 'version': '1.0.0' + } + } + }, + 'lorem': { + 'version': '1.0.0' + } + } + }, + '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) => { + 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.deepEqual( + jsonParse(result), + { + 'name': 'test-npm-ls', + 'version': '1.0.0', + 'problems': [ + 'invalid: foo@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/foo', + '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': { + 'foo': { + 'version': '1.0.0', + 'invalid': true, + 'problems': [ + 'invalid: foo@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/foo' + ], + 'dependencies': { + 'bar': { + 'version': '1.0.0' + } + } + }, + 'lorem': { + 'version': '1.0.0', + 'extraneous': true, + 'problems': [ + 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-missing-invalid-extraneous/node_modules/lorem' + ] + }, + 'ipsum': { + 'required': '^1.0.0', + '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' + ) + t.end() + }) + }) + + t.test('--dev', (t) => { + _flatOptions.dev = 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'dev-dep': { + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { bar: { version: '1.0.0' } } + } + } + } + } + }, + 'should output json containing dev deps' + ) + _flatOptions.dev = false + t.end() + }) + }) + + t.test('--only=development', (t) => { + _flatOptions.only = 'development' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'dev-dep': { + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { bar: { version: '1.0.0' } } + } + } + } + } + }, + 'should output json containing only development deps' + ) + _flatOptions.only = null + t.end() + }) + }) + + t.test('--link', (t) => { + _flatOptions.link = 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([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'linked-dep': { + version: '1.0.0', + resolved: 'file:../linked-dep' + } + } + }, + 'should output json containing linked deps' + ) + _flatOptions.link = false + t.end() + }) + }) + + t.test('--production', (t) => { + _flatOptions.production = 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + lorem: { version: '1.0.0' }, + 'optional-dep': { version: '1.0.0' }, + 'prod-dep': { version: '1.0.0', dependencies: { bar: { version: '2.0.0' } } } + } + }, + 'should output json containing production deps' + ) + _flatOptions.production = false + t.end() + }) + }) + + t.test('--only=prod', (t) => { + _flatOptions.only = 'prod' + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + lorem: { version: '1.0.0' }, + 'optional-dep': { version: '1.0.0' }, + 'prod-dep': { version: '1.0.0', dependencies: { bar: { version: '2.0.0' } } } + } + }, + 'should output json containing only prod deps' + ) + _flatOptions.only = null + t.end() + }) + }) + + 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({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'prod-dep': '^1.0.0', + 'lorem': '^1.0.0' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'peer-dep': { + name: 'peer-dep', + description: 'Peer-dep description here', + version: '1.0.0', + _id: 'peer-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long/node_modules/peer-dep', + extraneous: false + }, + 'dev-dep': { + name: 'dev-dep', + description: 'A DEV dep kind of dep', + version: '1.0.0', + dependencies: { + foo: { + name: 'foo', + version: '1.0.0', + dependencies: { + bar: { + name: 'bar', + version: '1.0.0', + _id: 'bar@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long/node_modules/bar', + extraneous: false + } + }, + _id: 'foo@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: { bar: '^1.0.0' }, + path: '{CWD}/ls-ls-json--long/node_modules/foo', + extraneous: false + } + }, + _id: 'dev-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: { foo: '^1.0.0' }, + path: '{CWD}/ls-ls-json--long/node_modules/dev-dep', + extraneous: false + }, + lorem: { + name: 'lorem', + version: '1.0.0', + _id: 'lorem@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long/node_modules/lorem', + extraneous: false + }, + 'optional-dep': { + name: 'optional-dep', + description: 'Maybe a dep?', + version: '1.0.0', + _id: 'optional-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long/node_modules/optional-dep', + extraneous: false + }, + 'prod-dep': { + name: 'prod-dep', + description: 'A PROD dep kind of dep', + version: '1.0.0', + dependencies: { + bar: { + name: 'bar', + description: 'A dep that bars', + version: '2.0.0', + _id: 'bar@2.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long/node_modules/prod-dep/node_modules/bar', + extraneous: false + } + }, + _id: 'prod-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: { bar: '^2.0.0' }, + path: '{CWD}/ls-ls-json--long/node_modules/prod-dep', + extraneous: false + } + }, + devDependencies: { 'dev-dep': '^1.0.0' }, + optionalDependencies: { 'optional-dep': '^1.0.0' }, + peerDependencies: { 'peer-dep': '^1.0.0' }, + _id: 'test-npm-ls@1.0.0', + _dependencies: { 'prod-dep': '^1.0.0', lorem: '^1.0.0', 'optional-dep': '^1.0.0' }, + path: '{CWD}/ls-ls-json--long', + extraneous: false + }, + 'should output long json info' + ) + _flatOptions.long = true + t.end() + }) + }) + + t.test('--long --depth=0', (t) => { + _flatOptions.all = false + _flatOptions.depth = 0 + _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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...diffDepTypesNmFixture + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'peer-dep': { + name: 'peer-dep', + description: 'Peer-dep description here', + version: '1.0.0', + _id: 'peer-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long-depth-0/node_modules/peer-dep', + extraneous: false + }, + 'dev-dep': { + name: 'dev-dep', + description: 'A DEV dep kind of dep', + version: '1.0.0', + _id: 'dev-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: { foo: '^1.0.0' }, + path: '{CWD}/ls-ls-json--long-depth-0/node_modules/dev-dep', + extraneous: false + }, + lorem: { + name: 'lorem', + version: '1.0.0', + _id: 'lorem@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long-depth-0/node_modules/lorem', + extraneous: false + }, + 'optional-dep': { + name: 'optional-dep', + description: 'Maybe a dep?', + version: '1.0.0', + _id: 'optional-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: {}, + path: '{CWD}/ls-ls-json--long-depth-0/node_modules/optional-dep', + extraneous: false + }, + 'prod-dep': { + name: 'prod-dep', + description: 'A PROD dep kind of dep', + version: '1.0.0', + _id: 'prod-dep@1.0.0', + devDependencies: {}, + peerDependencies: {}, + _dependencies: { bar: '^2.0.0' }, + path: '{CWD}/ls-ls-json--long-depth-0/node_modules/prod-dep', + extraneous: false + } + }, + devDependencies: { 'dev-dep': '^1.0.0' }, + optionalDependencies: { 'optional-dep': '^1.0.0' }, + peerDependencies: { 'peer-dep': '^1.0.0' }, + _id: 'test-npm-ls@1.0.0', + _dependencies: { 'prod-dep': '^1.0.0', lorem: '^1.0.0', 'optional-dep': '^1.0.0' }, + path: '{CWD}/ls-ls-json--long-depth-0', + extraneous: false + }, + 'should output json containing top-level deps in long format' + ) + _flatOptions.all = true + _flatOptions.depth = Infinity + _flatOptions.long = false + t.end() + }) + }) + + t.test('json read problems', (t) => { + prefix = t.testdir({ + 'package.json': '{broken json' + }) + ls([], (err) => { + 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 root package.json' + ] + }, + 'should print empty json result' + ) + t.end() + }) + }) + + t.test('empty location', (t) => { + prefix = t.testdir({}) + ls([], (err) => { + t.ifError(err, 'should not error out on empty locations') + t.deepEqual( + jsonParse(result), + {}, + 'should print empty json result' + ) + t.end() + }) + }) + + t.test('unmet peer dep', (t) => { + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'optional-dep': '^1.0.0' + }, + peerDependencies: { + 'peer-dep': '^2.0.0' // mismatching version # + } + }), + ...diffDepTypesNmFixture + }) + 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: [ + 'invalid: peer-dep@1.0.0 {CWD}/ls-ls-json-unmet-peer-dep/node_modules/peer-dep' + ], + dependencies: { + 'peer-dep': { + 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', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { bar: { version: '1.0.0' } } + } + } + }, + lorem: { version: '1.0.0' }, + 'optional-dep': { version: '1.0.0' }, + 'prod-dep': { version: '1.0.0', dependencies: { bar: { version: '2.0.0' } } } + } + }, + 'should output json signaling missing peer dep in problems' + ) + t.end() + }) + }) + + t.test('unmet optional dep', (t) => { + 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' + }, + devDependencies: { + 'dev-dep': '^1.0.0' + }, + optionalDependencies: { + 'missing-optional-dep': '^1.0.0', + 'optional-dep': '^2.0.0' // mismatching version # + }, + peerDependencies: { + 'peer-dep': '^1.0.0' + } + }), + ...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.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 + ], + dependencies: { + 'optional-dep': { + version: '1.0.0', + invalid: true, + problems: [ + 'invalid: optional-dep@1.0.0 {CWD}/ls-ls-json-unmet-optional-dep/node_modules/optional-dep' + ] + }, + 'peer-dep': { + version: '1.0.0' + }, + 'dev-dep': { + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { bar: { version: '1.0.0' } } + } + } + }, + lorem: { version: '1.0.0' }, + 'prod-dep': { version: '1.0.0', dependencies: { bar: { version: '2.0.0' } } }, + 'missing-optional-dep': {} // missing optional dep has an empty entry in json output + } + }, + 'should output json with empty entry for missing optional deps' + ) + 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([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: { + version: '1.0.0', + dependencies: { + b: { + version: '1.0.0', + dependencies: { + a: { version: '1.0.0' } + } + } + } + } + } + }, + 'should print json output containing deduped ref' + ) + t.end() + }) + }) + + t.test('using aliases', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: 'npm:b@1.0.0' + } + }), + node_modules: { + 'a': { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + _from: 'a@npm:b', + _resolved: 'https://localhost:8080/abbrev/-/abbrev-1.1.1.tgz', + _requested: { + type: 'alias' + } + }) + } + } + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: { + version: '1.0.0', + resolved: 'https://localhost:8080/abbrev/-/abbrev-1.1.1.tgz' + } + } + }, + 'should output json containing aliases' + ) + t.end() + }) + }) + + t.test('resolved points to git ref', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'abbrev': 'git+https://github.com/isaacs/abbrev-js.git' + } + }), + node_modules: { + 'abbrev': { + 'package.json': JSON.stringify({ + name: 'abbrev', + version: '1.1.1', + _id: 'abbrev@1.1.1', + _from: 'git+https://github.com/isaacs/abbrev-js.git', + _resolved: 'git+https://github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + _requested: { + type: 'git', + raw: 'git+https:github.com/isaacs/abbrev-js.git', + rawSpec: 'git+https:github.com/isaacs/abbrev-js.git', + saveSpec: 'git+https://github.com/isaacs/abbrev-js.git', + fetchSpec: 'https://github.com/isaacs/abbrev-js.git', + gitCommittish: null + } + }) + } + } + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + abbrev: { + version: '1.1.1', + resolved: 'git+ssh://git@github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c' + } + } + }, + 'should output json containing git refs' + ) + t.end() + }) + }) + + t.test('from and resolved properties', (t) => { + prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'simple-output': '^2.0.0' + } + }), + node_modules: { + 'simple-output': { + 'package.json': JSON.stringify({ + name: 'simple-output', + version: '2.1.1', + _from: 'simple-output', + _id: 'simple-output@2.1.1', + _resolved: 'https://registry.npmjs.org/simple-output/-/simple-output-2.1.1.tgz', + _requested: { + type: 'tag', + registry: true, + raw: 'simple-output', + name: 'simple-output', + escapedName: 'simple-output', + rawSpec: '', + saveSpec: null, + fetchSpec: 'latest' + }, + _requiredBy: [ + '#USER', + '/' + ], + _shasum: '3c07708ec9ef3e3c985cf0ddd67df09ab8ec2abc', + _spec: 'simple-output' + }) + } + } + }) + ls([], () => { + t.deepEqual( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + 'simple-output': { + version: '2.1.1', + resolved: 'https://registry.npmjs.org/simple-output/-/simple-output-2.1.1.tgz' + } + } + }, + 'should be printed in json output' + ) + t.end() + }) + }) + + 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() +}) |