Skip to content

Commit 42dc14e

Browse files
wyzeSebastian McKenzie
authored and
Sebastian McKenzie
committed
Fix yarn ls [package] command to filter properly (#1792)
1 parent a5db1cd commit 42dc14e

File tree

10 files changed

+238
-69
lines changed

10 files changed

+238
-69
lines changed

__tests__/commands/ls.js

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

5146
test('getParent should extract a parent object from a hash, if the parent key exists', () => {
6147
const mockTreesByKey = {};
@@ -9,8 +150,8 @@ test('getParent should extract a parent object from a hash, if the parent key ex
9150
10151
children: [],
11152
};
12-
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
13-
153+
const res = getParent('parentPkg#childPkg', mockTreesByKey);
154+
14155
expect(res instanceof Object).toBe(true);
15156
expect(res.name).toBe('[email protected]');
16157
expect(res.children.length).toBe(0);
@@ -20,54 +161,18 @@ test('getParent should return undefined if the key does not exist in hash', () =
20161
const mockTreesByKey = {};
21162
mockTreesByKey['parentPkg'] = { };
22163

23-
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
164+
const res = getParent('parentPkg#childPkg', mockTreesByKey);
24165
expect(res.name).not.toBeDefined();
25166
expect(res.children).not.toBeDefined();
26167
});
27168

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-
64169
test('getReqDepth should return a number if valid', () => {
65-
expect(ls.getReqDepth('1')).toEqual(1);
66-
expect(ls.getReqDepth('01')).toEqual(1);
170+
expect(getReqDepth('1')).toEqual(1);
171+
expect(getReqDepth('01')).toEqual(1);
67172
});
68173

69174
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);
175+
expect(getReqDepth('foo')).toEqual(-1);
176+
expect(getReqDepth('bar')).toEqual(-1);
177+
expect(getReqDepth('')).toEqual(-1);
73178
});
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+
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)