0
# Command Execution
1
2
Process spawning and command execution with both synchronous and asynchronous support using execa for cross-platform compatibility.
3
4
## Capabilities
5
6
### Asynchronous Command Execution
7
8
Execute commands asynchronously with full control over input/output streams.
9
10
```typescript { .api }
11
/**
12
* Normalize a command across OS and spawn it (asynchronously)
13
* @param command - Program to execute
14
* @param options - Execa options for command execution
15
* @returns ExecaChildProcess for command interaction
16
*/
17
spawnCommand(command: string, options?: ExecaOptions): ExecaChildProcess;
18
19
/**
20
* Normalize a command across OS and spawn it (asynchronously)
21
* @param command - Program to execute
22
* @param args - List of arguments to pass to the program
23
* @param options - Execa options for command execution
24
* @returns ExecaChildProcess for command interaction
25
*/
26
spawn(command: string, args?: readonly string[], options?: ExecaOptions): ExecaChildProcess;
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
export default class MyGenerator extends Generator {
33
async install() {
34
// Execute command with default options (inherits stdio)
35
await this.spawnCommand('npm install');
36
37
// Execute with custom options
38
await this.spawnCommand('npm run build', {
39
stdio: 'pipe', // Capture output
40
cwd: this.destinationPath()
41
});
42
43
// Execute with arguments array
44
await this.spawn('git', ['init'], {
45
cwd: this.destinationPath()
46
});
47
48
// Execute with environment variables
49
await this.spawn('npm', ['run', 'test'], {
50
env: {
51
...process.env,
52
NODE_ENV: 'test',
53
CI: '1'
54
}
55
});
56
}
57
58
async writing() {
59
// Conditional command execution
60
if (this.answers.initGit) {
61
await this.spawn('git', ['init']);
62
await this.spawn('git', ['add', '.']);
63
await this.spawn('git', ['commit', '-m', 'Initial commit']);
64
}
65
66
if (this.answers.installDeps && !this.options.skipInstall) {
67
const packageManager = this.answers.packageManager || 'npm';
68
await this.spawnCommand(`${packageManager} install`);
69
}
70
}
71
}
72
```
73
74
### Synchronous Command Execution
75
76
Execute commands synchronously when you need to block execution.
77
78
```typescript { .api }
79
/**
80
* Normalize a command across OS and spawn it (synchronously)
81
* @param command - Program to execute
82
* @param options - Execa sync options for command execution
83
* @returns ExecaSyncReturnValue with command results
84
*/
85
spawnCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue;
86
87
/**
88
* Normalize a command across OS and spawn it (synchronously)
89
* @param command - Program to execute
90
* @param args - List of arguments to pass to the program
91
* @param options - Execa sync options for command execution
92
* @returns ExecaSyncReturnValue with command results
93
*/
94
spawnSync(command: string, args?: readonly string[], options?: SyncOptions): ExecaSyncReturnValue;
95
```
96
97
**Usage Examples:**
98
99
```typescript
100
export default class MyGenerator extends Generator {
101
initializing() {
102
// Check if tools are available synchronously
103
try {
104
const gitResult = this.spawnCommandSync('git --version', {
105
stdio: 'pipe'
106
});
107
this.log(`Git available: ${gitResult.stdout}`);
108
} catch (error) {
109
this.log('Git not available, skipping git operations');
110
this.options.skipGit = true;
111
}
112
113
// Get current directory info
114
const lsResult = this.spawnSync('ls', ['-la'], {
115
stdio: 'pipe',
116
cwd: this.destinationRoot()
117
});
118
119
if (lsResult.stdout.includes('package.json')) {
120
this.log('Existing package.json found');
121
}
122
}
123
124
configuring() {
125
// Synchronous operations for validation
126
if (this.answers.framework === 'react') {
127
try {
128
const nodeVersion = this.spawnCommandSync('node --version', {
129
stdio: 'pipe'
130
});
131
const version = nodeVersion.stdout.slice(1); // Remove 'v' prefix
132
133
if (parseInt(version.split('.')[0]) < 16) {
134
throw new Error('React requires Node.js 16 or higher');
135
}
136
} catch (error) {
137
this.log('Warning: Could not verify Node.js version');
138
}
139
}
140
}
141
}
142
```
143
144
### Command Output Handling
145
146
Capture and process command output for decision making.
147
148
**Usage Examples:**
149
150
```typescript
151
export default class MyGenerator extends Generator {
152
async prompting() {
153
// Get git user info for defaults
154
let defaultAuthor = 'Anonymous';
155
let defaultEmail = '';
156
157
try {
158
const nameResult = await this.spawn('git', ['config', 'user.name'], {
159
stdio: 'pipe'
160
});
161
defaultAuthor = nameResult.stdout.trim();
162
163
const emailResult = await this.spawn('git', ['config', 'user.email'], {
164
stdio: 'pipe'
165
});
166
defaultEmail = emailResult.stdout.trim();
167
} catch (error) {
168
// Git not configured, use defaults
169
}
170
171
this.answers = await this.prompt([
172
{
173
name: 'author',
174
message: 'Author name:',
175
default: defaultAuthor
176
},
177
{
178
name: 'email',
179
message: 'Author email:',
180
default: defaultEmail
181
}
182
]);
183
}
184
185
async writing() {
186
// Check if dependencies are already installed
187
try {
188
const result = await this.spawn('npm', ['list', '--depth=0'], {
189
stdio: 'pipe',
190
cwd: this.destinationPath()
191
});
192
193
if (result.stdout.includes('express')) {
194
this.log('Express already installed, skipping');
195
return;
196
}
197
} catch (error) {
198
// No package.json or no dependencies, continue
199
}
200
201
await this.addDependencies(['express']);
202
}
203
}
204
```
205
206
### Error Handling and Validation
207
208
Handle command failures and validate execution environments.
209
210
**Usage Examples:**
211
212
```typescript
213
export default class MyGenerator extends Generator {
214
async initializing() {
215
// Validate required tools
216
const requiredTools = [
217
{ cmd: 'node --version', name: 'Node.js' },
218
{ cmd: 'npm --version', name: 'npm' }
219
];
220
221
if (this.answers.useGit) {
222
requiredTools.push({ cmd: 'git --version', name: 'Git' });
223
}
224
225
for (const tool of requiredTools) {
226
try {
227
await this.spawnCommand(tool.cmd, { stdio: 'pipe' });
228
this.log(`✓ ${tool.name} is available`);
229
} catch (error) {
230
throw new Error(`${tool.name} is required but not available`);
231
}
232
}
233
}
234
235
async install() {
236
// Install with retry logic
237
const maxRetries = 3;
238
let retries = 0;
239
240
while (retries < maxRetries) {
241
try {
242
await this.spawnCommand('npm install', {
243
cwd: this.destinationPath()
244
});
245
this.log('Dependencies installed successfully');
246
break;
247
} catch (error) {
248
retries++;
249
this.log(`Install failed (attempt ${retries}/${maxRetries})`);
250
251
if (retries >= maxRetries) {
252
if (this.options.forceInstall) {
253
throw error; // Fail hard if force-install is set
254
} else {
255
this.log('Skipping install due to errors. Run npm install manually.');
256
break;
257
}
258
}
259
260
// Wait before retry
261
await new Promise(resolve => setTimeout(resolve, 1000));
262
}
263
}
264
}
265
}
266
```
267
268
### Advanced Process Management
269
270
Complex command execution scenarios with streaming and process control.
271
272
**Usage Examples:**
273
274
```typescript
275
export default class MyGenerator extends Generator {
276
async install() {
277
// Stream command output in real-time
278
const child = this.spawn('npm', ['install', '--verbose'], {
279
stdio: ['ignore', 'pipe', 'pipe']
280
});
281
282
child.stdout.on('data', (data) => {
283
this.log(`npm: ${data.toString().trim()}`);
284
});
285
286
child.stderr.on('data', (data) => {
287
this.log(`npm error: ${data.toString().trim()}`);
288
});
289
290
await child;
291
}
292
293
async writing() {
294
// Parallel command execution
295
const commands = [
296
this.spawn('npm', ['run', 'lint']),
297
this.spawn('npm', ['run', 'test']),
298
this.spawn('npm', ['run', 'build'])
299
];
300
301
try {
302
await Promise.all(commands);
303
this.log('All commands completed successfully');
304
} catch (error) {
305
this.log('Some commands failed, check output above');
306
}
307
}
308
309
async end() {
310
// Interactive command execution
311
if (this.answers.openEditor) {
312
const editor = process.env.EDITOR || 'code';
313
314
await this.spawn(editor, ['.'], {
315
stdio: 'inherit', // Allow user interaction
316
cwd: this.destinationPath()
317
});
318
}
319
320
// Conditional post-install commands
321
if (this.answers.framework === 'react') {
322
await this.spawnCommand('npm run start', {
323
detached: true, // Run in background
324
stdio: 'ignore'
325
});
326
327
this.log('Development server started at http://localhost:3000');
328
}
329
}
330
}
331
```
332
333
### Platform-Specific Commands
334
335
Handle cross-platform compatibility for different operating systems.
336
337
**Usage Examples:**
338
339
```typescript
340
export default class MyGenerator extends Generator {
341
async writing() {
342
// Platform-specific commands
343
const isWindows = process.platform === 'win32';
344
345
if (this.answers.createDesktopShortcut) {
346
if (isWindows) {
347
// Windows-specific shortcut creation
348
await this.spawnCommand('powershell', [
349
'-Command',
350
`$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut("$Home\\Desktop\\${this.answers.name}.lnk"); $Shortcut.TargetPath = "${this.destinationPath('run.bat')}"; $Shortcut.Save()`
351
]);
352
} else {
353
// Unix-like systems
354
await this.spawn('ln', ['-sf',
355
this.destinationPath('run.sh'),
356
`${process.env.HOME}/Desktop/${this.answers.name}`
357
]);
358
}
359
}
360
361
// Cross-platform file permissions
362
if (!isWindows && this.answers.createExecutable) {
363
await this.spawn('chmod', ['+x', this.destinationPath('bin/cli.js')]);
364
}
365
}
366
367
async install() {
368
// Platform-specific package managers
369
const isWindows = process.platform === 'win32';
370
const packageManager = this.answers.packageManager;
371
372
const command = isWindows && packageManager === 'npm'
373
? 'npm.cmd'
374
: packageManager;
375
376
await this.spawn(command, ['install'], {
377
cwd: this.destinationPath()
378
});
379
}
380
}
381
```
382
383
## Types
384
385
```typescript { .api }
386
// Execa child process (async)
387
interface ExecaChildProcess<StdoutStderrType = string> extends Promise<ExecaReturnValue<StdoutStderrType>> {
388
pid?: number;
389
stdout: NodeJS.ReadableStream;
390
stderr: NodeJS.ReadableStream;
391
stdin: NodeJS.WritableStream;
392
kill(signal?: string): void;
393
}
394
395
// Execa return value (async)
396
interface ExecaReturnValue<StdoutStderrType = string> {
397
stdout: StdoutStderrType;
398
stderr: StdoutStderrType;
399
exitCode: number;
400
command: string;
401
killed: boolean;
402
signal?: string;
403
}
404
405
// Execa sync return value
406
interface ExecaSyncReturnValue<StdoutStderrType = string> {
407
stdout: StdoutStderrType;
408
stderr: StdoutStderrType;
409
exitCode: number;
410
command: string;
411
killed: boolean;
412
signal?: string;
413
}
414
415
// Execa options (async)
416
interface ExecaOptions<EncodingType = string> {
417
cwd?: string;
418
env?: Record<string, string>;
419
stdio?: 'pipe' | 'inherit' | 'ignore' | readonly StdioOption[];
420
timeout?: number;
421
killSignal?: string;
422
encoding?: EncodingType;
423
shell?: boolean | string;
424
windowsHide?: boolean;
425
}
426
427
// Execa sync options
428
interface SyncOptions<EncodingType = string> {
429
cwd?: string;
430
env?: Record<string, string>;
431
stdio?: 'pipe' | 'inherit' | 'ignore' | readonly StdioOption[];
432
timeout?: number;
433
killSignal?: string;
434
encoding?: EncodingType;
435
shell?: boolean | string;
436
windowsHide?: boolean;
437
}
438
```