Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix yarn ls [package] command to filter properly #1792

Merged
merged 1 commit into from
Nov 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('[email protected]', {color: 'bold'}),
makeTree('[email protected]', {children, color: 'bold'}),
makeTree('[email protected]'),
];

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('[email protected]', {color: 'bold'}),
makeTree('[email protected]'),
];

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('[email protected]'),
];

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: '[email protected]',
children: [],
};
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
const res = getParent('parentPkg#childPkg', mockTreesByKey);

expect(res instanceof Object).toBe(true);
expect(res.name).toBe('[email protected]');
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


[email protected]:
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);
}

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