From 3a18c8101a6ca426d9f23b577c4b8444e8b01e64 Mon Sep 17 00:00:00 2001 From: Neil Kistner Date: Fri, 11 Nov 2016 19:21:15 -0600 Subject: [PATCH] Fix `yarn ls [package]` command to filter properly --- __tests__/commands/ls.js | 195 ++++++++++++++---- __tests__/fixtures/ls/depth-flag/package.json | 5 + __tests__/fixtures/ls/depth-flag/yarn.lock | 13 ++ .../ls/lockfile-outdated/package.json | 5 + .../fixtures/ls/lockfile-outdated/yarn.lock | 7 + __tests__/fixtures/ls/no-args/package.json | 8 + __tests__/fixtures/ls/no-args/yarn.lock | 17 ++ __tests__/fixtures/ls/one-arg/package.json | 5 + __tests__/fixtures/ls/one-arg/yarn.lock | 13 ++ src/cli/commands/ls.js | 39 ++-- 10 files changed, 238 insertions(+), 69 deletions(-) create mode 100644 __tests__/fixtures/ls/depth-flag/package.json create mode 100644 __tests__/fixtures/ls/depth-flag/yarn.lock create mode 100644 __tests__/fixtures/ls/lockfile-outdated/package.json create mode 100644 __tests__/fixtures/ls/lockfile-outdated/yarn.lock create mode 100644 __tests__/fixtures/ls/no-args/package.json create mode 100644 __tests__/fixtures/ls/no-args/yarn.lock create mode 100644 __tests__/fixtures/ls/one-arg/package.json create mode 100644 __tests__/fixtures/ls/one-arg/yarn.lock diff --git a/__tests__/commands/ls.js b/__tests__/commands/ls.js index fce060bc57..5472d7dfd7 100644 --- a/__tests__/commands/ls.js +++ b/__tests__/commands/ls.js @@ -1,6 +1,147 @@ /* @flow */ -import * as ls from '../../src/cli/commands/ls.js'; +import type {Tree} from '../../src/reporters/types.js'; +import {BufferReporter} from '../../src/reporters/index.js'; +import {getParent, getReqDepth, run as ls} from '../../src/cli/commands/ls.js'; +import * as fs from '../../src/util/fs.js'; +import * as reporters from '../../src/reporters/index.js'; +import Config from '../../src/config.js'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000; + +const stream = require('stream'); +const path = require('path'); +const os = require('os'); + +function makeTree( + name, + {children = [], hint = null, color = null, depth = 0}: Object = {}, +): Tree { + return { + name, + children, + hint, + color, + depth, + }; +} + +const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'ls'); + +async function runLs( + flags: Object, + args: Array, + name: string, + checkLs?: ?(config: Config, reporter: BufferReporter) => ?Promise, +): Promise { + const dir = path.join(fixturesLoc, name); + const cwd = path.join( + os.tmpdir(), + `yarn-${path.basename(dir)}-${Math.random()}`, + ); + await fs.unlink(cwd); + await fs.copy(dir, cwd); + + for (const {basename, absolute} of await fs.walk(cwd)) { + if (basename.toLowerCase() === '.ds_store') { + await fs.unlink(absolute); + } + } + + let out = ''; + const stdout = new stream.Writable({ + decodeStrings: false, + write(data, encoding, cb) { + out += data; + cb(); + }, + }); + + const reporter = new reporters.BufferReporter({stdout: null, stdin: null}); + + // create directories + await fs.mkdirp(path.join(cwd, '.yarn')); + await fs.mkdirp(path.join(cwd, 'node_modules')); + + try { + const config = new Config(reporter); + await config.init({ + cwd, + globalFolder: path.join(cwd, '.yarn/.global'), + cacheFolder: path.join(cwd, '.yarn'), + linkFolder: path.join(cwd, '.yarn/.link'), + }); + + await ls(config, reporter, flags, args); + + if (checkLs) { + await checkLs(config, reporter); + } + + } catch (err) { + throw new Error(`${err && err.stack} \nConsole output:\n ${out}`); + } +} + +test.concurrent('throws if lockfile out of date', (): Promise => { + const reporter = new reporters.ConsoleReporter({}); + + return new Promise(async (resolve) => { + try { + await runLs({}, [], 'lockfile-outdated'); + } catch (err) { + expect(err.message).toContain(reporter.lang('lockfileOutdated')); + } finally { + resolve(); + } + }); +}); + +test.concurrent('lists everything with no args', (): Promise => { + return runLs({}, [], 'no-args', (config, reporter): ?Promise => { + const rprtr = new reporters.BufferReporter({}); + const tree = reporter.getBuffer().slice(-1); + const children = [{name: 'is-plain-obj@^1.0.0', color: 'dim', shadow: true}]; + const trees = [ + makeTree('left-pad@1.1.3', {color: 'bold'}), + makeTree('sort-keys@1.1.2', {children, color: 'bold'}), + makeTree('is-plain-obj@1.1.0'), + ]; + + rprtr.tree('ls', trees); + + expect(tree).toEqual(rprtr.getBuffer()); + }); +}); + +test.concurrent('respects depth flag', (): Promise => { + return runLs({depth: 1}, [], 'depth-flag', (config, reporter): ?Promise => { + const rprtr = new reporters.BufferReporter({}); + const tree = reporter.getBuffer().slice(-1); + const trees = [ + makeTree('sort-keys@1.1.2', {color: 'bold'}), + makeTree('is-plain-obj@1.1.0'), + ]; + + rprtr.tree('ls', trees); + + expect(tree).toEqual(rprtr.getBuffer()); + }); +}); + +test.concurrent('accepts an argument', (): Promise => { + return runLs({}, ['is-plain-obj'], 'one-arg', (config, reporter): ?Promise => { + const rprtr = new reporters.BufferReporter({}); + const tree = reporter.getBuffer().slice(-1); + const trees = [ + makeTree('is-plain-obj@1.1.0'), + ]; + + rprtr.tree('ls', trees); + + expect(tree).toEqual(rprtr.getBuffer()); + }); +}); test('getParent should extract a parent object from a hash, if the parent key exists', () => { const mockTreesByKey = {}; @@ -9,8 +150,8 @@ test('getParent should extract a parent object from a hash, if the parent key ex name: 'parent@1.1.1', children: [], }; - const res = ls.getParent('parentPkg#childPkg', mockTreesByKey); - + const res = getParent('parentPkg#childPkg', mockTreesByKey); + expect(res instanceof Object).toBe(true); expect(res.name).toBe('parent@1.1.1'); expect(res.children.length).toBe(0); @@ -20,54 +161,18 @@ test('getParent should return undefined if the key does not exist in hash', () = const mockTreesByKey = {}; mockTreesByKey['parentPkg'] = { }; - const res = ls.getParent('parentPkg#childPkg', mockTreesByKey); + const res = getParent('parentPkg#childPkg', mockTreesByKey); expect(res.name).not.toBeDefined(); expect(res.children).not.toBeDefined(); }); -test('setFlags should set options for --depth', () => { - const flags = ['--depth']; - const commander = require('commander'); - ls.setFlags(commander); - - const commanderOptions = commander.options; - const optsLen = commanderOptions.length; - flags.map((flag) => { - let currFlagExists = false; - for (let i = 0; i < optsLen; i++) { - if (commanderOptions[i].long === flag) { - currFlagExists = true; - } - } - expect(currFlagExists).toBeTruthy(); - }); -}); - -test('setFlags should set options for --depth', () => { - const flags = ['--foo', '--bar', '--baz']; - const commander = require('commander'); - ls.setFlags(commander); - - const commanderOptions = commander.options; - const optsLen = commanderOptions.length; - flags.map((flag) => { - let currFlagExists = false; - for (let i = 0; i < optsLen; i++) { - if (commanderOptions[i].long === flag) { - currFlagExists = true; - } - } - expect(currFlagExists).not.toBeTruthy(); - }); -}); - test('getReqDepth should return a number if valid', () => { - expect(ls.getReqDepth('1')).toEqual(1); - expect(ls.getReqDepth('01')).toEqual(1); + expect(getReqDepth('1')).toEqual(1); + expect(getReqDepth('01')).toEqual(1); }); test('getReqDepth should return -1 if invalid', () => { - expect(ls.getReqDepth('foo')).toEqual(-1); - expect(ls.getReqDepth('bar')).toEqual(-1); - expect(ls.getReqDepth('')).toEqual(-1); + expect(getReqDepth('foo')).toEqual(-1); + expect(getReqDepth('bar')).toEqual(-1); + expect(getReqDepth('')).toEqual(-1); }); diff --git a/__tests__/fixtures/ls/depth-flag/package.json b/__tests__/fixtures/ls/depth-flag/package.json new file mode 100644 index 0000000000..f9d9acde37 --- /dev/null +++ b/__tests__/fixtures/ls/depth-flag/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "sort-keys": "^1.1.2" + } +} diff --git a/__tests__/fixtures/ls/depth-flag/yarn.lock b/__tests__/fixtures/ls/depth-flag/yarn.lock new file mode 100644 index 0000000000..4ac12eabac --- /dev/null +++ b/__tests__/fixtures/ls/depth-flag/yarn.lock @@ -0,0 +1,13 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +sort-keys@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" diff --git a/__tests__/fixtures/ls/lockfile-outdated/package.json b/__tests__/fixtures/ls/lockfile-outdated/package.json new file mode 100644 index 0000000000..839f7608e6 --- /dev/null +++ b/__tests__/fixtures/ls/lockfile-outdated/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "left-pad": "^1.1.3" + } +} diff --git a/__tests__/fixtures/ls/lockfile-outdated/yarn.lock b/__tests__/fixtures/ls/lockfile-outdated/yarn.lock new file mode 100644 index 0000000000..d3eb54abd0 --- /dev/null +++ b/__tests__/fixtures/ls/lockfile-outdated/yarn.lock @@ -0,0 +1,7 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +left-pad@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37" diff --git a/__tests__/fixtures/ls/no-args/package.json b/__tests__/fixtures/ls/no-args/package.json new file mode 100644 index 0000000000..f208422dce --- /dev/null +++ b/__tests__/fixtures/ls/no-args/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "left-pad": "^1.1.3" + }, + "devDependencies": { + "sort-keys": "^1.1.2" + } +} diff --git a/__tests__/fixtures/ls/no-args/yarn.lock b/__tests__/fixtures/ls/no-args/yarn.lock new file mode 100644 index 0000000000..b5494df56b --- /dev/null +++ b/__tests__/fixtures/ls/no-args/yarn.lock @@ -0,0 +1,17 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +left-pad@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" + +sort-keys@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" diff --git a/__tests__/fixtures/ls/one-arg/package.json b/__tests__/fixtures/ls/one-arg/package.json new file mode 100644 index 0000000000..f9d9acde37 --- /dev/null +++ b/__tests__/fixtures/ls/one-arg/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "sort-keys": "^1.1.2" + } +} diff --git a/__tests__/fixtures/ls/one-arg/yarn.lock b/__tests__/fixtures/ls/one-arg/yarn.lock new file mode 100644 index 0000000000..4ac12eabac --- /dev/null +++ b/__tests__/fixtures/ls/one-arg/yarn.lock @@ -0,0 +1,13 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +sort-keys@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" diff --git a/src/cli/commands/ls.js b/src/cli/commands/ls.js index d66f179149..c3ff039cb2 100644 --- a/src/cli/commands/ls.js +++ b/src/cli/commands/ls.js @@ -4,8 +4,7 @@ import type {Reporter} from '../../reporters/index.js'; import type Config from '../../config.js'; import type PackageResolver from '../../package-resolver.js'; import type PackageLinker from '../../package-linker.js'; -import type {Trees} from '../../reporters/types.js'; -import {MessageError} from '../../errors.js'; +import type {Tree, Trees} from '../../reporters/types.js'; import {Install} from './install.js'; import Lockfile from '../../lockfile/wrapper.js'; @@ -156,6 +155,18 @@ export function getReqDepth(inputDepth: string) : number { return inputDepth && /^\d+$/.test(inputDepth) ? Number(inputDepth) : -1; } +export function filterTree(tree: Tree, filters: Array): boolean { + if (tree.children) { + tree.children = tree.children.filter((child) => filterTree(child, filters)); + } + + const notDim = tree.color !== 'dim'; + const found = (filters.includes(tree.name.slice(0, tree.name.lastIndexOf('@'))) : boolean); + const hasChildren = tree.children == null ? false : tree.children.length > 0; + + return notDim && (found || hasChildren); +} + export async function run( config: Config, reporter: Reporter, @@ -172,31 +183,11 @@ export async function run( reqDepth: getReqDepth(flags.depth), }; - let filteredPatterns: Array = []; + let {trees}: {trees: Trees} = await buildTree(install.resolver, install.linker, patterns, opts); if (args.length) { - const matchedArgs: Array = []; - - for (const pattern of patterns) { - const pkg = install.resolver.getStrictResolvedPattern(pattern); - - // ignore patterns if their package names have been specified in arguments - if (args.indexOf(pkg.name) >= 0) { - matchedArgs.push(pkg.name); - filteredPatterns.push(pattern); - } - } - - // throw an error if any package names were passed to filter that don't exist - for (const arg of args) { - if (matchedArgs.indexOf(arg) < 0) { - throw new MessageError(reporter.lang('unknownPackage', arg)); - } - } - } else { - filteredPatterns = patterns; + trees = trees.filter((tree) => filterTree(tree, args)); } - const {trees} = await buildTree(install.resolver, install.linker, filteredPatterns, opts); reporter.tree('ls', trees); }