or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-system.mdcli.mdconsole.mdgenerator-api.mdindex.mdprogrammatic.mdtemplate-system.md

action-system.mddocs/

0

# Action System

1

2

Plop's action system provides built-in file operations and supports custom actions for flexible code generation workflows. Actions are executed after prompts are answered and define what files to create, modify, or manipulate.

3

4

## Capabilities

5

6

### Built-in Action Types

7

8

Plop includes four core action types for common file operations.

9

10

```javascript { .api }

11

// Built-in action types

12

type BuiltInActionType = "add" | "addMany" | "modify" | "append";

13

```

14

15

### Add Action

16

17

Create a single file from a template.

18

19

```javascript { .api }

20

interface AddActionConfig extends ActionConfig {

21

type: "add";

22

path: string; // Destination file path (supports templates)

23

skipIfExists?: boolean; // Skip if file already exists (default: false)

24

transform?: TransformFn<AddActionConfig>;

25

} & TemplateStrOrFile

26

```

27

28

**Usage Examples:**

29

30

```javascript

31

// Basic add action with inline template

32

{

33

type: 'add',

34

path: 'src/components/{{pascalCase name}}.jsx',

35

template: `import React from 'react';

36

37

export const {{pascalCase name}} = () => {

38

return <div>{{name}}</div>;

39

};`

40

}

41

42

// Add action with template file

43

{

44

type: 'add',

45

path: 'src/{{type}}/{{kebabCase name}}.js',

46

templateFile: 'templates/{{type}}.hbs',

47

skipIfExists: true

48

}

49

50

// Add with transform function

51

{

52

type: 'add',

53

path: 'src/{{name}}.ts',

54

templateFile: 'templates/service.hbs',

55

transform: (template, data) => {

56

// Add type annotations for TypeScript

57

return template.replace(/: any/g, `: ${data.returnType || 'unknown'}`);

58

}

59

}

60

```

61

62

### Add Many Action

63

64

Create multiple files from a template pattern or directory.

65

66

```javascript { .api }

67

interface AddManyActionConfig extends ActionConfig {

68

type: "addMany";

69

destination: string; // Base destination directory

70

base: string; // Base template directory

71

templateFiles: string | string[]; // Glob pattern(s) for template files

72

stripExtensions?: string[]; // Extensions to remove from filenames

73

globOptions?: GlobOptions; // Options for glob matching

74

verbose?: boolean; // Log each file operation

75

transform?: TransformFn<AddManyActionConfig>;

76

}

77

```

78

79

**Usage Examples:**

80

81

```javascript

82

// Copy entire template directory

83

{

84

type: 'addMany',

85

destination: 'src/components/{{pascalCase name}}/',

86

base: 'templates/component/',

87

templateFiles: '**/*',

88

stripExtensions: ['hbs']

89

}

90

91

// Copy specific file patterns

92

{

93

type: 'addMany',

94

destination: 'src/modules/{{kebabCase name}}/',

95

base: 'templates/module/',

96

templateFiles: ['*.js.hbs', 'tests/*.test.js.hbs'],

97

stripExtensions: ['hbs'],

98

verbose: true

99

}

100

101

// With glob options

102

{

103

type: 'addMany',

104

destination: 'generated/{{name}}/',

105

base: 'templates/',

106

templateFiles: '**/*.{js,ts,json}',

107

globOptions: {

108

ignore: ['**/*.spec.*', '**/node_modules/**']

109

}

110

}

111

```

112

113

### Modify Action

114

115

Modify existing files using pattern matching and replacement.

116

117

```javascript { .api }

118

interface ModifyActionConfig extends ActionConfig {

119

type: "modify";

120

path: string; // Target file path (supports templates)

121

pattern: string | RegExp; // Pattern to find in file

122

transform?: TransformFn<ModifyActionConfig>;

123

} & TemplateStrOrFile

124

```

125

126

**Usage Examples:**

127

128

```javascript

129

// Add import statement to existing file

130

{

131

type: 'modify',

132

path: 'src/index.js',

133

pattern: /(\/\/ -- PLOP IMPORTS --)/gi,

134

template: "import { {{pascalCase name}} } from './components/{{pascalCase name}}';\n$1"

135

}

136

137

// Replace section in config file

138

{

139

type: 'modify',

140

path: 'config/routes.js',

141

pattern: /(\s*)(\/\/ -- ROUTES --)/gi,

142

templateFile: 'templates/route-entry.hbs'

143

}

144

145

// Modify with regex and transform

146

{

147

type: 'modify',

148

path: 'src/{{name}}.js',

149

pattern: /class\s+(\w+)/,

150

template: 'class Enhanced{{pascalCase name}}',

151

transform: (template, data) => {

152

return template.replace(/Enhanced/, data.prefix || 'Enhanced');

153

}

154

}

155

```

156

157

### Append Action

158

159

Append content to existing files with pattern matching.

160

161

```javascript { .api }

162

interface AppendActionConfig extends ActionConfig {

163

type: "append";

164

path: string; // Target file path (supports templates)

165

pattern: string | RegExp; // Pattern to find insertion point

166

unique?: boolean; // Prevent duplicate entries (default: true)

167

separator?: string; // Separator between entries (default: newline)

168

} & TemplateStrOrFile

169

```

170

171

**Usage Examples:**

172

173

```javascript

174

// Append to array in JavaScript file

175

{

176

type: 'append',

177

path: 'src/config.js',

178

pattern: /(const routes = \[)/gi,

179

template: " '{{kebabCase name}}',",

180

separator: '\n'

181

}

182

183

// Append import with uniqueness check

184

{

185

type: 'append',

186

path: 'src/components/index.js',

187

pattern: /(\/\/ -- EXPORTS --)/gi,

188

template: "export { {{pascalCase name}} } from './{{pascalCase name}}';",

189

unique: true

190

}

191

192

// Append to end of file

193

{

194

type: 'append',

195

path: 'README.md',

196

pattern: /$/,

197

templateFile: 'templates/readme-section.hbs',

198

separator: '\n\n'

199

}

200

```

201

202

### Custom Action Types

203

204

Register custom action types for specialized operations.

205

206

```javascript { .api }

207

/**

208

* Register a custom action type

209

* @param name - Action type name

210

* @param fn - Action function

211

*/

212

setActionType(name: string, fn: CustomActionFunction): void;

213

214

/**

215

* Get a registered action type function

216

* @param name - Action type name

217

* @returns Action type function

218

*/

219

getActionType(name: string): ActionType;

220

221

/**

222

* Get list of all registered action type names

223

* @returns Array of action type names

224

*/

225

getActionTypeList(): string[];

226

227

/**

228

* Custom action function signature

229

* @param answers - User answers from prompts

230

* @param config - Action configuration object

231

* @param plopfileApi - Plop API instance

232

* @returns Promise resolving to success message or string result

233

*/

234

type CustomActionFunction = (

235

answers: Answers,

236

config: CustomActionConfig<string>,

237

plopfileApi: NodePlopAPI,

238

) => Promise<string> | string;

239

240

interface CustomActionConfig<T extends string> extends ActionConfig {

241

type: T;

242

[key: string]: any; // Custom properties for action

243

}

244

```

245

246

**Usage Examples:**

247

248

```javascript

249

import { nodePlop } from "plop";

250

import fs from 'fs/promises';

251

import path from 'path';

252

253

const plop = await nodePlop();

254

255

// Register custom action for API documentation

256

plop.setActionType('generateDocs', async (answers, config, plop) => {

257

const apiPath = path.join(config.basePath, answers.name, 'api.json');

258

259

// Generate API documentation

260

const apiSpec = {

261

name: answers.name,

262

version: '1.0.0',

263

endpoints: answers.endpoints || []

264

};

265

266

await fs.writeFile(apiPath, JSON.stringify(apiSpec, null, 2));

267

return `API documentation generated at ${apiPath}`;

268

});

269

270

// Register custom action for database operations

271

plop.setActionType('createMigration', async (answers, config) => {

272

const timestamp = new Date().toISOString().replace(/[-:]/g, '').split('.')[0];

273

const filename = `${timestamp}_create_${answers.tableName}.sql`;

274

const migrationPath = path.join('migrations', filename);

275

276

const migration = `

277

CREATE TABLE ${answers.tableName} (

278

id INTEGER PRIMARY KEY AUTOINCREMENT,

279

${answers.columns.map(col => `${col.name} ${col.type}`).join(',\n ')},

280

created_at DATETIME DEFAULT CURRENT_TIMESTAMP

281

);

282

`;

283

284

await fs.writeFile(migrationPath, migration.trim());

285

return `Migration created: ${filename}`;

286

});

287

288

// Use custom actions in generator

289

plop.setGenerator('api-module', {

290

description: 'Generate API module with documentation',

291

prompts: [

292

{ type: 'input', name: 'name', message: 'Module name:' },

293

{ type: 'input', name: 'tableName', message: 'Database table:' }

294

],

295

actions: [

296

{

297

type: 'add',

298

path: 'src/{{name}}/index.js',

299

templateFile: 'templates/api-module.hbs'

300

},

301

{

302

type: 'generateDocs',

303

basePath: 'src'

304

},

305

{

306

type: 'createMigration',

307

tableName: '{{tableName}}'

308

}

309

]

310

});

311

```

312

313

### Action Configuration

314

315

Common configuration options for all action types.

316

317

```javascript { .api }

318

interface ActionConfig {

319

type: string; // Action type name

320

force?: boolean; // Force overwrite (overrides global setting)

321

data?: object; // Additional data for template rendering

322

abortOnFail?: boolean; // Abort generation if action fails

323

skip?: Function; // Function to conditionally skip action

324

}

325

```

326

327

**Usage Examples:**

328

329

```javascript

330

// Action with conditional execution

331

{

332

type: 'add',

333

path: 'src/{{name}}.test.js',

334

templateFile: 'templates/test.hbs',

335

skip: (data) => {

336

if (!data.includeTests) {

337

return 'Tests disabled by user';

338

}

339

return false; // Don't skip

340

}

341

}

342

343

// Action with additional template data

344

{

345

type: 'add',

346

path: 'src/{{name}}.js',

347

templateFile: 'templates/component.hbs',

348

data: {

349

timestamp: new Date().toISOString(),

350

author: 'Plop Generator',

351

version: '1.0.0'

352

}

353

}

354

355

// Action that aborts on failure

356

{

357

type: 'modify',

358

path: 'package.json',

359

pattern: /"dependencies":\s*{/,

360

template: '"dependencies": {\n "{{name}}": "^1.0.0",',

361

abortOnFail: true

362

}

363

```

364

365

### Dynamic Actions

366

367

Use functions to dynamically generate actions based on user input.

368

369

```javascript { .api }

370

/**

371

* Function that returns actions array based on user answers

372

* @param data - User answers object

373

* @returns Array of actions to execute

374

*/

375

type DynamicActionsFunction = (data?: Answers) => ActionType[];

376

```

377

378

**Usage Examples:**

379

380

```javascript

381

plop.setGenerator('flexible', {

382

description: 'Flexible generator with dynamic actions',

383

prompts: [

384

{ type: 'input', name: 'name', message: 'Component name:' },

385

{ type: 'confirm', name: 'includeStyles', message: 'Include CSS?' },

386

{ type: 'confirm', name: 'includeTests', message: 'Include tests?' },

387

{ type: 'list', name: 'framework', choices: ['react', 'vue', 'angular'] }

388

],

389

390

// Dynamic actions based on user choices

391

actions: (data) => {

392

const actions = [];

393

394

// Always add main component

395

actions.push({

396

type: 'add',

397

path: `src/components/{{pascalCase name}}.${data.framework === 'react' ? 'jsx' : 'js'}`,

398

templateFile: `templates/${data.framework}-component.hbs`

399

});

400

401

// Conditionally add styles

402

if (data.includeStyles) {

403

actions.push({

404

type: 'add',

405

path: 'src/components/{{pascalCase name}}.css',

406

templateFile: 'templates/component-styles.hbs'

407

});

408

}

409

410

// Conditionally add tests

411

if (data.includeTests) {

412

actions.push({

413

type: 'add',

414

path: 'src/components/__tests__/{{pascalCase name}}.test.js',

415

templateFile: `templates/${data.framework}-test.hbs`

416

});

417

}

418

419

// Framework-specific additions

420

if (data.framework === 'react') {

421

actions.push({

422

type: 'modify',

423

path: 'src/components/index.js',

424

pattern: /(\/\/ -- REACT EXPORTS --)/gi,

425

template: "export { {{pascalCase name}} } from './{{pascalCase name}}';\n$1"

426

});

427

}

428

429

return actions;

430

}

431

});

432

```

433

434

## Action Execution

435

436

### Execution Order

437

438

Actions execute sequentially in the order they are defined, with each action waiting for the previous to complete.

439

440

### Error Handling

441

442

Actions can handle errors in several ways:

443

444

- **Continue on error** (default): Log error and continue with next action

445

- **Abort on fail**: Stop execution if action fails (`abortOnFail: true`)

446

- **Skip action**: Conditionally skip actions with `skip` function

447

448

### Progress Feedback

449

450

Actions provide visual feedback during execution with customizable progress indicators and success/failure messages.