Skip to content

Commit 0af1780

Browse files
authored
fix(core): improve run-commands dx for ready-when (#6448)
1 parent 9326742 commit 0af1780

File tree

8 files changed

+129
-49
lines changed

8 files changed

+129
-49
lines changed

docs/angular/api-workspace/executors/run-commands.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,18 @@ that sets the `forwardAllArgs` option to `false` as shown below:
106106

107107
##### Custom **done** conditions
108108

109-
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string, that considers the command finished the moment the string appears in `stdout` or `stderr`:
109+
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string that considers the commands finished the moment the string appears in `stdout` or `stderr`:
110110

111111
```json
112112
"finish-when-ready": {
113113
"builder": "@nrwl/workspace:run-commands",
114114
"options": {
115-
"command": "echo 'READY' && sleep 5 && echo 'FINISHED'",
116-
"readyWhen": "READY"
115+
"commands": [
116+
"sleep 5 && echo 'FINISHED'",
117+
"echo 'READY'"
118+
],
119+
"readyWhen": "READY",
120+
"parallel": true
117121
}
118122
}
119123
```
@@ -122,7 +126,7 @@ Normally, `run-commands` considers the commands done when all of them have finis
122126
nx run frontend:finish-when-ready
123127
```
124128

125-
The above command will finish immediately, instead of waiting for 5 seconds.
129+
The above commands will finish immediately, instead of waiting for 5 seconds.
126130

127131
##### Nx Affected
128132

@@ -218,4 +222,4 @@ Run commands in parallel
218222

219223
Type: `string`
220224

221-
String to appear in stdout or stderr that indicates that the task is done. This option can only be used when parallel is set to true. If not specified, the task is done when all the child processes complete.
225+
String to appear in `stdout` or `stderr` that indicates that the task is done. This option can only be used when multiple commands are run and `parallel` is set to `true`. If not specified, the task is done when all the child processes complete.

docs/node/api-workspace/executors/run-commands.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,18 @@ that sets the `forwardAllArgs` option to `false` as shown below:
107107

108108
##### Custom **done** conditions
109109

110-
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string, that considers the command finished the moment the string appears in `stdout` or `stderr`:
110+
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string that considers the commands finished the moment the string appears in `stdout` or `stderr`:
111111

112112
```json
113113
"finish-when-ready": {
114114
"builder": "@nrwl/workspace:run-commands",
115115
"options": {
116-
"command": "echo 'READY' && sleep 5 && echo 'FINISHED'",
117-
"readyWhen": "READY"
116+
"commands": [
117+
"sleep 5 && echo 'FINISHED'",
118+
"echo 'READY'"
119+
],
120+
"readyWhen": "READY",
121+
"parallel": true
118122
}
119123
}
120124
```
@@ -123,7 +127,7 @@ Normally, `run-commands` considers the commands done when all of them have finis
123127
nx run frontend:finish-when-ready
124128
```
125129

126-
The above command will finish immediately, instead of waiting for 5 seconds.
130+
The above commands will finish immediately, instead of waiting for 5 seconds.
127131

128132
##### Nx Affected
129133

@@ -219,4 +223,4 @@ Run commands in parallel
219223

220224
Type: `string`
221225

222-
String to appear in stdout or stderr that indicates that the task is done. This option can only be used when parallel is set to true. If not specified, the task is done when all the child processes complete.
226+
String to appear in `stdout` or `stderr` that indicates that the task is done. This option can only be used when multiple commands are run and `parallel` is set to `true`. If not specified, the task is done when all the child processes complete.

docs/react/api-workspace/executors/run-commands.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,18 @@ that sets the `forwardAllArgs` option to `false` as shown below:
107107

108108
##### Custom **done** conditions
109109

110-
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string, that considers the command finished the moment the string appears in `stdout` or `stderr`:
110+
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string that considers the commands finished the moment the string appears in `stdout` or `stderr`:
111111

112112
```json
113113
"finish-when-ready": {
114114
"builder": "@nrwl/workspace:run-commands",
115115
"options": {
116-
"command": "echo 'READY' && sleep 5 && echo 'FINISHED'",
117-
"readyWhen": "READY"
116+
"commands": [
117+
"sleep 5 && echo 'FINISHED'",
118+
"echo 'READY'"
119+
],
120+
"readyWhen": "READY",
121+
"parallel": true
118122
}
119123
}
120124
```
@@ -123,7 +127,7 @@ Normally, `run-commands` considers the commands done when all of them have finis
123127
nx run frontend:finish-when-ready
124128
```
125129

126-
The above command will finish immediately, instead of waiting for 5 seconds.
130+
The above commands will finish immediately, instead of waiting for 5 seconds.
127131

128132
##### Nx Affected
129133

@@ -219,4 +223,4 @@ Run commands in parallel
219223

220224
Type: `string`
221225

222-
String to appear in stdout or stderr that indicates that the task is done. This option can only be used when parallel is set to true. If not specified, the task is done when all the child processes complete.
226+
String to appear in `stdout` or `stderr` that indicates that the task is done. This option can only be used when multiple commands are run and `parallel` is set to `true`. If not specified, the task is done when all the child processes complete.

packages/tao/src/shared/params.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export async function handleErrors(isVerbose: boolean, fn: Function) {
5151
logger.error('The generator workflow failed. See above.');
5252
} else if (err.message) {
5353
logger.error(err.message);
54+
} else {
55+
logger.error(err);
5456
}
5557
if (isVerbose && err.stack) {
5658
logger.info(err.stack);

packages/workspace/docs/run-commands-examples.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,18 @@ that sets the `forwardAllArgs` option to `false` as shown below:
9898

9999
##### Custom **done** conditions
100100

101-
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string, that considers the command finished the moment the string appears in `stdout` or `stderr`:
101+
Normally, `run-commands` considers the commands done when all of them have finished running. If you don't need to wait until they're all done, you can set a special string that considers the commands finished the moment the string appears in `stdout` or `stderr`:
102102

103103
```json
104104
"finish-when-ready": {
105105
"builder": "@nrwl/workspace:run-commands",
106106
"options": {
107-
"command": "echo 'READY' && sleep 5 && echo 'FINISHED'",
108-
"readyWhen": "READY"
107+
"commands": [
108+
"sleep 5 && echo 'FINISHED'",
109+
"echo 'READY'"
110+
],
111+
"readyWhen": "READY",
112+
"parallel": true
109113
}
110114
}
111115
```
@@ -114,7 +118,7 @@ Normally, `run-commands` considers the commands done when all of them have finis
114118
<%= cli %> run frontend:finish-when-ready
115119
```
116120

117-
The above command will finish immediately, instead of waiting for 5 seconds.
121+
The above commands will finish immediately, instead of waiting for 5 seconds.
118122

119123
##### Nx Affected
120124

packages/workspace/src/executors/run-commands/run-commands.impl.spec.ts

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ function readFile(f: string) {
1313
describe('Command Runner Builder', () => {
1414
const context = {} as any;
1515

16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
1620
it('should run one command', async () => {
1721
const f = fileSync().name;
1822
const result = await runCommands({ command: `echo 1 >> ${f}` }, context);
@@ -67,15 +71,20 @@ describe('Command Runner Builder', () => {
6771

6872
await runCommands(
6973
{
70-
commands: [{ command: 'echo' }],
74+
commands: [{ command: 'echo' }, { command: 'echo foo' }],
7175
parallel: true,
7276
a: 123,
7377
b: 456,
7478
},
7579
context
7680
);
7781

78-
expect(exec).toHaveBeenCalledWith('echo --a=123 --b=456', {
82+
expect(exec).toHaveBeenCalledTimes(2);
83+
expect(exec).toHaveBeenNthCalledWith(1, 'echo --a=123 --b=456', {
84+
maxBuffer: LARGE_BUFFER,
85+
env: { ...process.env },
86+
});
87+
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo --a=123 --b=456', {
7988
maxBuffer: LARGE_BUFFER,
8089
env: { ...process.env },
8190
});
@@ -86,15 +95,23 @@ describe('Command Runner Builder', () => {
8695

8796
await runCommands(
8897
{
89-
commands: [{ command: 'echo', forwardAllArgs: true }],
98+
commands: [
99+
{ command: 'echo', forwardAllArgs: true },
100+
{ command: 'echo foo', forwardAllArgs: true },
101+
],
90102
parallel: true,
91103
a: 123,
92104
b: 456,
93105
},
94106
context
95107
);
96108

97-
expect(exec).toHaveBeenCalledWith('echo --a=123 --b=456', {
109+
expect(exec).toHaveBeenCalledTimes(2);
110+
expect(exec).toHaveBeenNthCalledWith(1, 'echo --a=123 --b=456', {
111+
maxBuffer: LARGE_BUFFER,
112+
env: { ...process.env },
113+
});
114+
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo --a=123 --b=456', {
98115
maxBuffer: LARGE_BUFFER,
99116
env: { ...process.env },
100117
});
@@ -105,15 +122,23 @@ describe('Command Runner Builder', () => {
105122

106123
await runCommands(
107124
{
108-
commands: [{ command: 'echo', forwardAllArgs: false }],
125+
commands: [
126+
{ command: 'echo', forwardAllArgs: false },
127+
{ command: 'echo foo', forwardAllArgs: false },
128+
],
109129
parallel: true,
110130
a: 123,
111131
b: 456,
112132
},
113133
context
114134
);
115135

116-
expect(exec).toHaveBeenCalledWith('echo', {
136+
expect(exec).toHaveBeenCalledTimes(2);
137+
expect(exec).toHaveBeenNthCalledWith(1, 'echo', {
138+
maxBuffer: LARGE_BUFFER,
139+
env: { ...process.env },
140+
});
141+
expect(exec).toHaveBeenNthCalledWith(2, 'echo foo', {
117142
maxBuffer: LARGE_BUFFER,
118143
env: { ...process.env },
119144
});
@@ -169,11 +194,41 @@ describe('Command Runner Builder', () => {
169194
});
170195

171196
describe('readyWhen', () => {
197+
it('should warn when command is specified instead of commands', async () => {
198+
jest.spyOn(process.stderr, 'write');
199+
await runCommands(
200+
{
201+
command: `echo 'Hello World'`,
202+
readyWhen: 'READY',
203+
},
204+
context
205+
);
206+
207+
expect(process.stderr.write).toHaveBeenCalledWith(
208+
'Warning: "readyWhen" has no effect when a single command is run. The task will be done when the command process is completed. Ignoring.'
209+
);
210+
});
211+
212+
it('should warn when a single command is specified', async () => {
213+
jest.spyOn(process.stderr, 'write');
214+
await runCommands(
215+
{
216+
commands: [`echo 'Hello World'`],
217+
readyWhen: 'READY',
218+
},
219+
context
220+
);
221+
222+
expect(process.stderr.write).toHaveBeenCalledWith(
223+
'Warning: "readyWhen" has no effect when a single command is run. The task will be done when the command process is completed. Ignoring.'
224+
);
225+
});
226+
172227
it('should error when parallel = false', async () => {
173228
try {
174229
await runCommands(
175230
{
176-
commands: [{ command: 'some command' }],
231+
commands: [{ command: 'echo foo' }, { command: 'echo bar' }],
177232
parallel: false,
178233
readyWhen: 'READY',
179234
},
@@ -182,20 +237,16 @@ describe('Command Runner Builder', () => {
182237
fail('should throw');
183238
} catch (e) {
184239
expect(e.message).toEqual(
185-
`ERROR: Bad builder config for @nrwl/run-commands - "readyWhen" can only be used when parallel=true`
240+
`ERROR: Bad builder config for @nrwl/run-commands - "readyWhen" can only be used when parallel=true.`
186241
);
187242
}
188243
});
189244

190-
it('should return success true when the string specified is ready condition is found', async () => {
245+
it('should return success true when the string specified as ready condition is found', async () => {
191246
const f = fileSync().name;
192247
const result = await runCommands(
193248
{
194-
commands: [
195-
{
196-
command: `echo READY && sleep 0.1 && echo 1 >> ${f}`,
197-
},
198-
],
249+
commands: [`echo READY && sleep 0.1 && echo 1 >> ${f}`, `echo foo`],
199250
parallel: true,
200251
readyWhen: 'READY',
201252
},
@@ -231,17 +282,18 @@ describe('Command Runner Builder', () => {
231282
const exec = jest.spyOn(require('child_process'), 'exec');
232283
await runCommands(
233284
{
234-
commands: [
235-
{
236-
command: `echo 'Hello World'`,
237-
},
238-
],
285+
commands: [`echo 'Hello World'`, `echo 'Hello Universe'`],
239286
parallel: true,
240287
},
241288
context
242289
);
243290

244-
expect(exec).toHaveBeenCalledWith(`echo 'Hello World'`, {
291+
expect(exec).toHaveBeenCalledTimes(2);
292+
expect(exec).toHaveBeenNthCalledWith(1, `echo 'Hello World'`, {
293+
maxBuffer: LARGE_BUFFER,
294+
env: { ...process.env },
295+
});
296+
expect(exec).toHaveBeenNthCalledWith(2, `echo 'Hello Universe'`, {
245297
maxBuffer: LARGE_BUFFER,
246298
env: { ...process.env },
247299
});
@@ -251,18 +303,19 @@ describe('Command Runner Builder', () => {
251303
const exec = jest.spyOn(require('child_process'), 'exec');
252304
await runCommands(
253305
{
254-
commands: [
255-
{
256-
command: `echo 'Hello World'`,
257-
},
258-
],
306+
commands: [`echo 'Hello World'`, `echo 'Hello Universe'`],
259307
parallel: true,
260308
color: true,
261309
},
262310
context
263311
);
264312

265-
expect(exec).toHaveBeenCalledWith(`echo 'Hello World'`, {
313+
expect(exec).toHaveBeenCalledTimes(2);
314+
expect(exec).toHaveBeenNthCalledWith(1, `echo 'Hello World'`, {
315+
maxBuffer: LARGE_BUFFER,
316+
env: { ...process.env, FORCE_COLOR: `true` },
317+
});
318+
expect(exec).toHaveBeenNthCalledWith(2, `echo 'Hello Universe'`, {
266319
maxBuffer: LARGE_BUFFER,
267320
env: { ...process.env, FORCE_COLOR: `true` },
268321
});

packages/workspace/src/executors/run-commands/run-commands.impl.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,15 @@ export default async function (
7171
const normalized = normalizeOptions(options);
7272

7373
if (options.readyWhen && !options.parallel) {
74-
throw new Error(
75-
'ERROR: Bad builder config for @nrwl/run-commands - "readyWhen" can only be used when parallel=true'
76-
);
74+
if (options.command || options.commands?.length === 1) {
75+
process.stderr.write(
76+
'Warning: "readyWhen" has no effect when a single command is run. The task will be done when the command process is completed. Ignoring.'
77+
);
78+
} else {
79+
throw new Error(
80+
'ERROR: Bad builder config for @nrwl/run-commands - "readyWhen" can only be used when parallel=true.'
81+
);
82+
}
7783
}
7884

7985
try {
@@ -145,6 +151,9 @@ function normalizeOptions(
145151
options.commands = options.commands.map((c) =>
146152
typeof c === 'string' ? { command: c } : c
147153
);
154+
if (options.commands.length === 1) {
155+
options.parallel = false;
156+
}
148157
}
149158
(options as NormalizedRunCommandsBuilderOptions).commands.forEach((c) => {
150159
c.command = transformCommand(

packages/workspace/src/executors/run-commands/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"readyWhen": {
4848
"type": "string",
49-
"description": "String to appear in stdout or stderr that indicates that the task is done. This option can only be used when parallel is set to true. If not specified, the task is done when all the child processes complete."
49+
"description": "String to appear in `stdout` or `stderr` that indicates that the task is done. This option can only be used when multiple commands are run and `parallel` is set to `true`. If not specified, the task is done when all the child processes complete."
5050
},
5151
"args": {
5252
"type": "string",

0 commit comments

Comments
 (0)