or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-execution.mdcommand-line.mdconfiguration.mdcore-generator.mdfile-system.mdgit-integration.mdindex.mdpackage-management.mdtask-lifecycle.mduser-interaction.md

command-execution.mddocs/

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

```