Skip to content

Commit 3342c47

Browse files
wyzeSebastian McKenzie
authored andcommitted
Fix yarn run [script] [-- <args>] command usage (#1788)
1 parent ecba5f9 commit 3342c47

File tree

8 files changed

+147
-1
lines changed

8 files changed

+147
-1
lines changed

__tests__/commands/run.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* @flow */
2+
3+
jest.mock('../../src/util/execute-lifecycle-script');
4+
5+
import {BufferReporter} from '../../src/reporters/index.js';
6+
import {run} from '../../src/cli/commands/run.js';
7+
import * as fs from '../../src/util/fs.js';
8+
import * as reporters from '../../src/reporters/index.js';
9+
import Config from '../../src/config.js';
10+
11+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
12+
13+
const execCommand: $FlowFixMe = require('../../src/util/execute-lifecycle-script').execCommand;
14+
15+
const stream = require('stream');
16+
const path = require('path');
17+
const os = require('os');
18+
19+
beforeEach(() => execCommand.mockClear());
20+
21+
const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'run');
22+
23+
async function runRun(
24+
flags: Object,
25+
args: Array<string>,
26+
name: string,
27+
checkRun?: ?(config: Config, reporter: BufferReporter) => ?Promise<void>,
28+
): Promise<void> {
29+
const dir = path.join(fixturesLoc, name);
30+
const cwd = path.join(
31+
os.tmpdir(),
32+
`yarn-${path.basename(dir)}-${Math.random()}`,
33+
);
34+
await fs.unlink(cwd);
35+
await fs.copy(dir, cwd);
36+
37+
for (const {basename, absolute} of await fs.walk(cwd)) {
38+
if (basename.toLowerCase() === '.ds_store') {
39+
await fs.unlink(absolute);
40+
}
41+
}
42+
43+
let out = '';
44+
const stdout = new stream.Writable({
45+
decodeStrings: false,
46+
write(data, encoding, cb) {
47+
out += data;
48+
cb();
49+
},
50+
});
51+
52+
const reporter = new reporters.BufferReporter({stdout: null, stdin: null});
53+
54+
// create directories
55+
await fs.mkdirp(path.join(cwd, '.yarn'));
56+
await fs.mkdirp(path.join(cwd, 'node_modules'));
57+
58+
try {
59+
const config = new Config(reporter);
60+
await config.init({
61+
cwd,
62+
globalFolder: path.join(cwd, '.yarn/.global'),
63+
cacheFolder: path.join(cwd, '.yarn'),
64+
linkFolder: path.join(cwd, '.yarn/.link'),
65+
});
66+
67+
await run(config, reporter, flags, args);
68+
69+
if (checkRun) {
70+
await checkRun(config, reporter);
71+
}
72+
73+
} catch (err) {
74+
throw new Error(`${err && err.stack} \nConsole output:\n ${out}`);
75+
}
76+
}
77+
78+
test('lists all available commands with no arguments', (): Promise<void> => {
79+
return runRun({}, [], 'no-args', (config, reporter): ?Promise<void> => {
80+
const rprtr = new reporters.BufferReporter({stdout: null, stdin: null});
81+
const scripts = ['build', 'prestart', 'start'];
82+
// Notice `cat-names` is below twice as there is a bug with output duplication
83+
const bins = ['cat-names', 'cat-names'];
84+
85+
// Emulate run output
86+
rprtr.error(rprtr.lang('commandNotSpecified'));
87+
rprtr.info(`${rprtr.lang('binCommands')}${bins.join(', ')}`);
88+
rprtr.info(rprtr.lang('possibleCommands'));
89+
rprtr.list('possibleCommands', scripts);
90+
rprtr.error(rprtr.lang('commandNotSpecified'));
91+
92+
expect(reporter.getBuffer()).toEqual(rprtr.getBuffer());
93+
});
94+
});
95+
96+
test('runs script containing spaces', (): Promise<void> => {
97+
return runRun({}, ['build'], 'spaces', async (config): ?Promise<void> => {
98+
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
99+
// The command get's called with a space appended
100+
const args = ['build', config, pkg.scripts.build + ' ', config.cwd];
101+
102+
expect(execCommand).toBeCalledWith(...args);
103+
});
104+
});
105+
106+
test('properly handles extra arguments and pre/post scripts', (): Promise<void> => {
107+
return runRun({}, ['start', '--hello'], 'extra-args', async (config): ?Promise<void> => {
108+
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
109+
const poststart = ['poststart', config, pkg.scripts.poststart, config.cwd];
110+
const prestart = ['prestart', config, pkg.scripts.prestart, config.cwd];
111+
const start = ['start', config, pkg.scripts.start + ' --hello', config.cwd];
112+
113+
expect(execCommand.mock.calls[0]).toEqual(prestart);
114+
expect(execCommand.mock.calls[1]).toEqual(start);
115+
expect(execCommand.mock.calls[2]).toEqual(poststart);
116+
});
117+
});
118+
119+
test('handles bin scripts', (): Promise<void> => {
120+
return runRun({}, ['cat-names'], 'bin', (config) => {
121+
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
122+
const args = ['cat-names', config, `"${script}" `, config.cwd];
123+
124+
expect(execCommand).toBeCalledWith(...args);
125+
});
126+
});

__tests__/fixtures/run/bin/node_modules/.bin/cat-names

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"scripts": {
3+
"start": "node index.js",
4+
"prestart": "echo 'prestart'",
5+
"poststart": "echo 'poststart'"
6+
}
7+
}

__tests__/fixtures/run/no-args/node_modules/.bin/cat-names

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"scripts": {
3+
"build": "echo 'building'",
4+
"start": "node index.js",
5+
"prestart": "echo 'prestart'"
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"scripts": {
3+
"build": "echo 'lint' && echo 'build'"
4+
}
5+
}

src/cli/commands/run.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export async function run(
6262
if (cmds.length) {
6363
for (const [stage, cmd] of cmds) {
6464
// only tack on trailing arguments for default script, ignore for pre and post - #1595
65-
const cmdWithArgs = cmd === action ? `${cmd} ${args.join(' ')}` : cmd;
65+
const cmdWithArgs = stage === action ? `${cmd} ${args.join(' ')}` : cmd;
6666
await execCommand(stage, config, cmdWithArgs, config.cwd);
6767
}
6868
} else {

0 commit comments

Comments
 (0)