Skip to content

Commit

Permalink
Fix yarn ls [package] command to filter properly (#1792)
Browse files Browse the repository at this point in the history
  • Loading branch information
wyze authored and Sebastian McKenzie committed Nov 14, 2016
1 parent a5db1cd commit 42dc14e
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 69 deletions.
195 changes: 150 additions & 45 deletions __tests__/commands/ls.js
Original file line number Diff line number Diff line change
@@ -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<string>,
name: string,
checkLs?: ?(config: Config, reporter: BufferReporter) => ?Promise<void>,
): Promise<void> {
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<void> => {
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<void> => {
return runLs({}, [], 'no-args', (config, reporter): ?Promise<void> => {
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<void> => {
return runLs({depth: 1}, [], 'depth-flag', (config, reporter): ?Promise<void> => {
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<void> => {
return runLs({}, ['is-plain-obj'], 'one-arg', (config, reporter): ?Promise<void> => {
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 = {};
Expand All @@ -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);
Expand All @@ -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);
});
5 changes: 5 additions & 0 deletions __tests__/fixtures/ls/depth-flag/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"sort-keys": "^1.1.2"
}
}
13 changes: 13 additions & 0 deletions __tests__/fixtures/ls/depth-flag/yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
5 changes: 5 additions & 0 deletions __tests__/fixtures/ls/lockfile-outdated/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"left-pad": "^1.1.3"
}
}
7 changes: 7 additions & 0 deletions __tests__/fixtures/ls/lockfile-outdated/yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
8 changes: 8 additions & 0 deletions __tests__/fixtures/ls/no-args/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"dependencies": {
"left-pad": "^1.1.3"
},
"devDependencies": {
"sort-keys": "^1.1.2"
}
}
17 changes: 17 additions & 0 deletions __tests__/fixtures/ls/no-args/yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
5 changes: 5 additions & 0 deletions __tests__/fixtures/ls/one-arg/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"sort-keys": "^1.1.2"
}
}
13 changes: 13 additions & 0 deletions __tests__/fixtures/ls/one-arg/yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
39 changes: 15 additions & 24 deletions src/cli/commands/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<string>): 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);

This comment has been minimized.

Copy link
@markstos

markstos Dec 6, 2016

Contributor

It would be helpful if there was a code comment here noting what this dim/notDim section is doing.

}

export async function run(
config: Config,
reporter: Reporter,
Expand All @@ -172,31 +183,11 @@ export async function run(
reqDepth: getReqDepth(flags.depth),
};

let filteredPatterns: Array<string> = [];
let {trees}: {trees: Trees} = await buildTree(install.resolver, install.linker, patterns, opts);

if (args.length) {
const matchedArgs: Array<string> = [];

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);
}

0 comments on commit 42dc14e

Please sign in to comment.