-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathrun.js
232 lines (199 loc) · 9.18 KB
/
run.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/* @flow */
jest.mock('../../src/util/execute-lifecycle-script', () => {
return {
// $FlowFixMe
...require.requireActual('../../src/util/execute-lifecycle-script'),
execCommand: jest.fn(),
};
});
import path from 'path';
import {run as buildRun} from './_helpers.js';
import {BufferReporter} from '../../src/reporters/index.js';
import {run} from '../../src/cli/commands/run.js';
import * as fs from '../../src/util/fs.js';
import * as reporters from '../../src/reporters/index.js';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
const {execCommand}: $FlowFixMe = require('../../src/util/execute-lifecycle-script');
beforeEach(() => execCommand.mockClear());
const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'run');
const runRun = buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
return run(config, reporter, flags, args);
});
const runRunInWorkspacePackage = function(cwd, ...args): Promise<void> {
return buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
const originalCwd = config.cwd;
config.cwd = path.join(originalCwd, cwd);
const retVal = run(config, reporter, flags, args);
retVal.then(() => {
config.cwd = originalCwd;
});
return retVal;
})(...args);
};
const runRunWithCustomShell = function(customShell, ...args): Promise<void> {
return buildRun.bind(null, BufferReporter, fixturesLoc, (args, flags, config, reporter): Promise<void> => {
const yarnRegistry = config.registries.yarn;
const originalCustomShell = yarnRegistry.config['script-shell'];
yarnRegistry.config['script-shell'] = customShell;
const retVal = run(config, reporter, flags, args);
retVal.then(() => {
yarnRegistry.config['script-shell'] = originalCustomShell;
});
return retVal;
})(...args);
};
test('lists all available commands with no arguments', (): Promise<void> =>
runRun([], {}, 'no-args', (config, reporter): ?Promise<void> => {
const rprtr = new reporters.BufferReporter({stdout: null, stdin: null});
const scripts = ['build', 'prestart', 'start'];
const hints = {
build: "echo 'building'",
prestart: "echo 'prestart'",
start: 'node index.js',
};
const bins = ['cat-names'];
// Emulate run output
rprtr.info(`${rprtr.lang('binCommands')}${bins.join(', ')}`);
rprtr.info(rprtr.lang('possibleCommands'));
rprtr.list('possibleCommands', scripts, hints);
rprtr.error(rprtr.lang('commandNotSpecified'));
expect(reporter.getBuffer()).toEqual(rprtr.getBuffer());
}));
test('lists all available commands with no arguments and --non-interactive', (): Promise<void> =>
runRun([], {nonInteractive: true}, 'no-args', (config, reporter): ?Promise<void> => {
const rprtr = new reporters.BufferReporter({stdout: null, stdin: null});
const scripts = ['build', 'prestart', 'start'];
const hints = {
build: "echo 'building'",
prestart: "echo 'prestart'",
start: 'node index.js',
};
const bins = ['cat-names'];
// Emulate run output
rprtr.info(`${rprtr.lang('binCommands')}${bins.join(', ')}`);
rprtr.info(rprtr.lang('possibleCommands'));
rprtr.list('possibleCommands', scripts, hints);
expect(reporter.getBuffer()).toEqual(rprtr.getBuffer());
}));
test('runs script containing spaces', (): Promise<void> =>
runRun(['build'], {}, 'spaces', async (config): ?Promise<void> => {
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
// The command gets called with a space appended
expect(execCommand).toBeCalledWith({
stage: 'build',
config,
cmd: pkg.scripts.build,
cwd: config.cwd,
isInteractive: true,
});
}));
test('properly handles extra arguments and pre/post scripts', (): Promise<void> =>
runRun(['start', '--hello'], {}, 'extra-args', async (config): ?Promise<void> => {
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
const poststart = {stage: 'poststart', config, cmd: pkg.scripts.poststart, cwd: config.cwd, isInteractive: true};
const prestart = {stage: 'prestart', config, cmd: pkg.scripts.prestart, cwd: config.cwd, isInteractive: true};
const start = {stage: 'start', config, cmd: pkg.scripts.start + ' --hello', cwd: config.cwd, isInteractive: true};
expect(execCommand.mock.calls[0]).toEqual([prestart]);
expect(execCommand.mock.calls[1]).toEqual([start]);
expect(execCommand.mock.calls[2]).toEqual([poststart]);
}));
test('properly handle bin scripts', (): Promise<void> =>
runRun(['cat-names'], {}, 'bin', config => {
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
expect(execCommand).toBeCalledWith({
stage: 'cat-names',
config,
cmd: script,
cwd: config.cwd,
isInteractive: true,
});
}));
test('properly handle env command', (): Promise<void> =>
runRun(['env'], {}, 'no-args', (config, reporter): ?Promise<void> => {
// $FlowFixMe
const result = JSON.parse(reporter.getBuffer()[0].data);
const env = {};
let pathVarName = 'PATH';
for (const key of Object.keys(process.env)) {
// Filter out yarn-added `npm_` variables since we run tests through yarn already
if (key.startsWith('npm_')) {
continue;
}
// We need this below for Windows which has case-insensitive env vars
// If we used `process.env` directly, node takes care of this for us,
// but since we use a subset of it, we need to get the "real" path key
// name for Jest's case-sensitive object comparison below.
if (key.toUpperCase() === 'PATH') {
pathVarName = key;
}
env[key] = process.env[key];
}
result[pathVarName] = result[pathVarName] ? result[pathVarName].split(path.delimiter) : [];
// $FlowFixMe
env[pathVarName] = env[pathVarName] ? expect.arrayContaining(env[pathVarName].split(path.delimiter)) : [];
expect(result).toMatchObject(env);
expect(result).toHaveProperty('npm_lifecycle_event');
expect(result).toHaveProperty('npm_execpath');
expect(result).toHaveProperty('npm_node_execpath');
}));
test('adds string delimiters if args have spaces', (): Promise<void> =>
runRun(['cat-names', '--filter', 'cat names'], {}, 'bin', config => {
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
const q = process.platform === 'win32' ? '"' : "'";
expect(execCommand).toBeCalledWith({
stage: 'cat-names',
config,
cmd: `${script} --filter ${q}cat names${q}`,
cwd: config.cwd,
isInteractive: true,
});
}));
test('adds quotes if args have spaces and quotes', (): Promise<void> =>
runRun(['cat-names', '--filter', '"cat names"'], {}, 'bin', config => {
const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
const quotedCatNames = process.platform === 'win32' ? '^"\\^"cat^ names\\^"^"' : `'"cat names"'`;
expect(execCommand).toBeCalledWith({
stage: 'cat-names',
config,
cmd: `${script} --filter ${quotedCatNames}`,
cwd: config.cwd,
isInteractive: true,
});
}));
test('returns noScriptsAvailable with no scripts', (): Promise<void> =>
runRun([], {}, 'no-scripts', (config, reporter) => {
expect(reporter.getBuffer()).toMatchSnapshot();
}));
test('returns noBinAvailable with no bins', (): Promise<void> =>
runRun([], {}, 'no-bin', (config, reporter) => {
expect(reporter.getBuffer()).toMatchSnapshot();
}));
test('adds workspace root node_modules/.bin to path when in a workspace', (): Promise<void> =>
runRunInWorkspacePackage('packages/pkg1', ['env'], {}, 'workspace', (config, reporter): ?Promise<void> => {
const logEntry = reporter.getBuffer().find(entry => entry.type === 'log');
const parsedLogData = JSON.parse(logEntry ? logEntry.data.toString() : '{}');
const envPaths = (parsedLogData.PATH || parsedLogData.Path).split(path.delimiter);
expect(envPaths).toContain(path.join(config.cwd, 'node_modules', '.bin'));
expect(envPaths).toContain(path.join(config.cwd, 'packages', 'pkg1', 'node_modules', '.bin'));
}));
test('adds cwd node_modules/.bin to path when in a workspace usig nohoist', (): Promise<void> =>
runRunInWorkspacePackage('packages/pkg1', ['env'], {}, 'nohoist-workspace', (config, reporter): ?Promise<void> => {
const logEntry = reporter.getBuffer().find(entry => entry.type === 'log');
const parsedLogData = JSON.parse(logEntry ? logEntry.data.toString() : '{}');
const envPaths = (parsedLogData.PATH || parsedLogData.Path).split(path.delimiter);
expect(envPaths).toContain(path.join(config.cwd, 'node_modules', '.bin'));
expect(envPaths).toContain(path.join(config.cwd, 'packages', 'pkg1', 'node_modules', '.bin'));
}));
test('runs script with custom script-shell', (): Promise<void> =>
runRunWithCustomShell('/usr/bin/dummy', ['start'], {}, 'script-shell', async (config): ?Promise<void> => {
const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
// The command gets called with the provided customShell
expect(execCommand).toBeCalledWith({
stage: 'start',
config,
cmd: pkg.scripts.start,
cwd: config.cwd,
isInteractive: true,
customShell: '/usr/bin/dummy',
});
}));