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.