Skip to content

Commit 8b7c33a

Browse files
committed
Fix yarn ls [package] command to filter properly
1 parent 3ed3039 commit 8b7c33a

File tree

10 files changed

+236
-69
lines changed

10 files changed

+236
-69
lines changed

__tests__/commands/ls.js

Lines changed: 148 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,145 @@
11
/* @flow */
22

3-
import * as ls from '../../src/cli/commands/ls.js';
3+
import type {Tree} from '../../src/reporters/types.js';
4+
import {BufferReporter} from '../../src/reporters/index.js';
5+
import {getParent, getReqDepth, run as ls} from '../../src/cli/commands/ls.js';
6+
import * as fs from '../../src/util/fs.js';
7+
import * as reporters from '../../src/reporters/index.js';
8+
import Config from '../../src/config.js';
9+
10+
const stream = require('stream');
11+
const path = require('path');
12+
const os = require('os');
13+
14+
function makeTree(
15+
name,
16+
{children = [], hint = null, color = null, depth = 0}: Object = {},
17+
): Tree {
18+
return {
19+
name,
20+
children,
21+
hint,
22+
color,
23+
depth,
24+
};
25+
}
26+
27+
const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'ls');
28+
29+
async function runLs(
30+
flags: Object,
31+
args: Array<string>,
32+
name: string,
33+
checkLs?: ?(config: Config, reporter: BufferReporter) => ?Promise<void>,
34+
): Promise<void> {
35+
const dir = path.join(fixturesLoc, name);
36+
const cwd = path.join(
37+
os.tmpdir(),
38+
`yarn-${path.basename(dir)}-${Math.random()}`,
39+
);
40+
await fs.unlink(cwd);
41+
await fs.copy(dir, cwd);
42+
43+
for (const {basename, absolute} of await fs.walk(cwd)) {
44+
if (basename.toLowerCase() === '.ds_store') {
45+
await fs.unlink(absolute);
46+
}
47+
}
48+
49+
let out = '';
50+
const stdout = new stream.Writable({
51+
decodeStrings: false,
52+
write(data, encoding, cb) {
53+
out += data;
54+
cb();
55+
},
56+
});
57+
58+
const reporter = new reporters.BufferReporter({stdout: null, stdin: null});
59+
60+
// create directories
61+
await fs.mkdirp(path.join(cwd, '.yarn'));
62+
await fs.mkdirp(path.join(cwd, 'node_modules'));
63+
64+
try {
65+
const config = new Config(reporter);
66+
await config.init({
67+
cwd,
68+
globalFolder: path.join(cwd, '.yarn/.global'),
69+
cacheFolder: path.join(cwd, '.yarn'),
70+
linkFolder: path.join(cwd, '.yarn/.link'),
71+
});
72+
73+
await ls(config, reporter, flags, args);
74+
75+
if (checkLs) {
76+
await checkLs(config, reporter);
77+
}
78+
79+
} catch (err) {
80+
throw new Error(`${err && err.stack} \nConsole output:\n ${out}`);
81+
}
82+
}
83+
84+
test.concurrent('throws if lockfile out of date', (): Promise<void> => {
85+
const reporter = new reporters.ConsoleReporter({});
86+
87+
return new Promise(async (resolve) => {
88+
try {
89+
await runLs({}, [], 'lockfile-outdated');
90+
} catch (err) {
91+
expect(err.message).toContain(reporter.lang('lockfileOutdated'));
92+
} finally {
93+
resolve();
94+
}
95+
});
96+
});
97+
98+
test.concurrent('lists everything with no args', (): Promise<void> => {
99+
return runLs({}, [], 'no-args', (config, reporter): ?Promise<void> => {
100+
const rprtr = new reporters.BufferReporter({});
101+
const tree = reporter.getBuffer().slice(-1);
102+
const children = [{name: 'is-plain-obj@^1.0.0', color: 'dim', shadow: true}];
103+
const trees = [
104+
makeTree('left-pad@1.1.3', {color: 'bold'}),
105+
makeTree('sort-keys@1.1.2', {children, color: 'bold'}),
106+
makeTree('is-plain-obj@1.1.0'),
107+
];
108+
109+
rprtr.tree('ls', trees);
110+
111+
expect(tree).toEqual(rprtr.getBuffer());
112+
});
113+
});
114+
115+
test.concurrent('respects depth flag', (): Promise<void> => {
116+
return runLs({depth: 1}, [], 'depth-flag', (config, reporter): ?Promise<void> => {
117+
const rprtr = new reporters.BufferReporter({});
118+
const tree = reporter.getBuffer().slice(-1);
119+
const trees = [
120+
makeTree('sort-keys@1.1.2', {color: 'bold'}),
121+
makeTree('is-plain-obj@1.1.0'),
122+
];
123+
124+
rprtr.tree('ls', trees);
125+
126+
expect(tree).toEqual(rprtr.getBuffer());
127+
});
128+
});
129+
130+
test.concurrent('accepts an argument', (): Promise<void> => {
131+
return runLs({}, ['is-plain-obj'], 'one-arg', (config, reporter): ?Promise<void> => {
132+
const rprtr = new reporters.BufferReporter({});
133+
const tree = reporter.getBuffer().slice(-1);
134+
const trees = [
135+
makeTree('is-plain-obj@1.1.0'),
136+
];
137+
138+
rprtr.tree('ls', trees);
139+
140+
expect(tree).toEqual(rprtr.getBuffer());
141+
});
142+
});
4143

5144
test('getParent should extract a parent object from a hash, if the parent key exists', () => {
6145
const mockTreesByKey = {};
@@ -9,8 +148,8 @@ test('getParent should extract a parent object from a hash, if the parent key ex
9148
name: 'parent@1.1.1',
10149
children: [],
11150
};
12-
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
13-
151+
const res = getParent('parentPkg#childPkg', mockTreesByKey);
152+
14153
expect(res instanceof Object).toBe(true);
15154
expect(res.name).toBe('parent@1.1.1');
16155
expect(res.children.length).toBe(0);
@@ -20,54 +159,18 @@ test('getParent should return undefined if the key does not exist in hash', () =
20159
const mockTreesByKey = {};
21160
mockTreesByKey['parentPkg'] = { };
22161

23-
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
162+
const res = getParent('parentPkg#childPkg', mockTreesByKey);
24163
expect(res.name).not.toBeDefined();
25164
expect(res.children).not.toBeDefined();
26165
});
27166

28-
test('setFlags should set options for --depth', () => {
29-
const flags = ['--depth'];
30-
const commander = require('commander');
31-
ls.setFlags(commander);
32-
33-
const commanderOptions = commander.options;
34-
const optsLen = commanderOptions.length;
35-
flags.map((flag) => {
36-
let currFlagExists = false;
37-
for (let i = 0; i < optsLen; i++) {
38-
if (commanderOptions[i].long === flag) {
39-
currFlagExists = true;
40-
}
41-
}
42-
expect(currFlagExists).toBeTruthy();
43-
});
44-
});
45-
46-
test('setFlags should set options for --depth', () => {
47-
const flags = ['--foo', '--bar', '--baz'];
48-
const commander = require('commander');
49-
ls.setFlags(commander);
50-
51-
const commanderOptions = commander.options;
52-
const optsLen = commanderOptions.length;
53-
flags.map((flag) => {
54-
let currFlagExists = false;
55-
for (let i = 0; i < optsLen; i++) {
56-
if (commanderOptions[i].long === flag) {
57-
currFlagExists = true;
58-
}
59-
}
60-
expect(currFlagExists).not.toBeTruthy();
61-
});
62-
});
63-
64167
test('getReqDepth should return a number if valid', () => {
65-
expect(ls.getReqDepth('1')).toEqual(1);
66-
expect(ls.getReqDepth('01')).toEqual(1);
168+
expect(getReqDepth('1')).toEqual(1);
169+
expect(getReqDepth('01')).toEqual(1);
67170
});
68171

69172
test('getReqDepth should return -1 if invalid', () => {
70-
expect(ls.getReqDepth('foo')).toEqual(-1);
71-
expect(ls.getReqDepth('bar')).toEqual(-1);
72-
expect(ls.getReqDepth('')).toEqual(-1);
173+
expect(getReqDepth('foo')).toEqual(-1);
174+
expect(getReqDepth('bar')).toEqual(-1);
175+
expect(getReqDepth('')).toEqual(-1);
73176
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"sort-keys": "^1.1.2"
4+
}
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
is-plain-obj@^1.0.0:
6+
version "1.1.0"
7+
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
8+
9+
sort-keys@^1.1.2:
10+
version "1.1.2"
11+
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
12+
dependencies:
13+
is-plain-obj "^1.0.0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"left-pad": "^1.1.3"
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
left-pad@1.0.0:
6+
version "1.0.0"
7+
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"dependencies": {
3+
"left-pad": "^1.1.3"
4+
},
5+
"devDependencies": {
6+
"sort-keys": "^1.1.2"
7+
}
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
is-plain-obj@^1.0.0:
6+
version "1.1.0"
7+
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
8+
9+
left-pad@^1.1.3:
10+
version "1.1.3"
11+
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"
12+
13+
sort-keys@^1.1.2:
14+
version "1.1.2"
15+
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
16+
dependencies:
17+
is-plain-obj "^1.0.0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"sort-keys": "^1.1.2"
4+
}
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
is-plain-obj@^1.0.0:
6+
version "1.1.0"
7+
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
8+
9+
sort-keys@^1.1.2:
10+
version "1.1.2"
11+
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
12+
dependencies:
13+
is-plain-obj "^1.0.0"

src/cli/commands/ls.js

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import type {Reporter} from '../../reporters/index.js';
44
import type Config from '../../config.js';
55
import type PackageResolver from '../../package-resolver.js';
66
import type PackageLinker from '../../package-linker.js';
7-
import type {Trees} from '../../reporters/types.js';
8-
import {MessageError} from '../../errors.js';
7+
import type {Tree, Trees} from '../../reporters/types.js';
98
import {Install} from './install.js';
109
import Lockfile from '../../lockfile/wrapper.js';
1110

@@ -156,6 +155,18 @@ export function getReqDepth(inputDepth: string) : number {
156155
return inputDepth && /^\d+$/.test(inputDepth) ? Number(inputDepth) : -1;
157156
}
158157

158+
export function filterTree(tree: Tree, filters: Array<string>): boolean {
159+
if (tree.children) {
160+
tree.children = tree.children.filter((child) => filterTree(child, filters));
161+
}
162+
163+
const notDim = tree.color !== 'dim';
164+
const found = (filters.includes(tree.name.slice(0, tree.name.lastIndexOf('@'))) : boolean);
165+
const hasChildren = tree.children == null ? false : tree.children.length > 0;
166+
167+
return notDim && (found || hasChildren);
168+
}
169+
159170
export async function run(
160171
config: Config,
161172
reporter: Reporter,
@@ -172,31 +183,11 @@ export async function run(
172183
reqDepth: getReqDepth(flags.depth),
173184
};
174185

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

177188
if (args.length) {
178-
const matchedArgs: Array<string> = [];
179-
180-
for (const pattern of patterns) {
181-
const pkg = install.resolver.getStrictResolvedPattern(pattern);
182-
183-
// ignore patterns if their package names have been specified in arguments
184-
if (args.indexOf(pkg.name) >= 0) {
185-
matchedArgs.push(pkg.name);
186-
filteredPatterns.push(pattern);
187-
}
188-
}
189-
190-
// throw an error if any package names were passed to filter that don't exist
191-
for (const arg of args) {
192-
if (matchedArgs.indexOf(arg) < 0) {
193-
throw new MessageError(reporter.lang('unknownPackage', arg));
194-
}
195-
}
196-
} else {
197-
filteredPatterns = patterns;
189+
trees = trees.filter((tree) => filterTree(tree, args));
198190
}
199191

200-
const {trees} = await buildTree(install.resolver, install.linker, filteredPatterns, opts);
201192
reporter.tree('ls', trees);
202193
}

0 commit comments

Comments
 (0)