claude-autopm 1.27.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/autopm/.claude/scripts/pm/prd-new.js +292 -2
- package/autopm/.claude/scripts/pm/template-list.js +119 -0
- package/autopm/.claude/scripts/pm/template-new.js +344 -0
- package/autopm/.claude/templates/prds/README.md +334 -0
- package/autopm/.claude/templates/prds/api-feature.md +306 -0
- package/autopm/.claude/templates/prds/bug-fix.md +413 -0
- package/autopm/.claude/templates/prds/data-migration.md +483 -0
- package/autopm/.claude/templates/prds/documentation.md +439 -0
- package/autopm/.claude/templates/prds/ui-feature.md +365 -0
- package/lib/template-engine.js +347 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,46 @@ PRD → Epic Decomposition → Parallel Development → Testing → Production
|
|
|
44
44
|
|
|
45
45
|
## ✨ Key Features
|
|
46
46
|
|
|
47
|
+
### 🆕 **NEW in v1.28.0: Templates & Scaffolding!**
|
|
48
|
+
|
|
49
|
+
**PRD Templates (Quick Start)**
|
|
50
|
+
- 📋 **5 Built-in Templates** - api-feature, ui-feature, bug-fix, data-migration, documentation
|
|
51
|
+
- 🚀 **70% Faster** - Create PRDs in 9 minutes instead of 30
|
|
52
|
+
- 🎯 **Context7-Verified** - All templates use 2025 best practices
|
|
53
|
+
- ✨ **Smart Variables** - Auto-generate IDs, timestamps, authors
|
|
54
|
+
- 🎨 **Custom Templates** - Create your own team-specific templates
|
|
55
|
+
|
|
56
|
+
**Template Commands**
|
|
57
|
+
```bash
|
|
58
|
+
# Create PRD from template
|
|
59
|
+
autopm prd:new --template api-feature "Payment API"
|
|
60
|
+
|
|
61
|
+
# Interactive template selection
|
|
62
|
+
autopm prd:new "my-feature"
|
|
63
|
+
|
|
64
|
+
# List available templates
|
|
65
|
+
autopm template:list
|
|
66
|
+
|
|
67
|
+
# Create custom template
|
|
68
|
+
autopm template:new prd my-custom-template
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**260+ Tests Passing** - Production-ready with comprehensive test coverage!
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### 🎉 **v1.27.0: Phase 2 Complete!**
|
|
76
|
+
|
|
77
|
+
**GitHub Sync (Bidirectional)**
|
|
78
|
+
- 📤 **Upload to GitHub Issues** - Sync PRDs/Epics/Tasks with smart conflict detection
|
|
79
|
+
- 📥 **Download from GitHub** - Pull Issues back to local files with reverse mapping
|
|
80
|
+
- 🔄 **Bidirectional Mapping** - Maintain consistency with `sync-map.json`
|
|
81
|
+
|
|
82
|
+
**Task Management**
|
|
83
|
+
- ✅ **Complete Task Lifecycle** - List, show, update tasks within epics
|
|
84
|
+
- 🔗 **Dependency Tracking** - Validate task dependencies automatically
|
|
85
|
+
- 📊 **Progress Auto-update** - Epic progress updates on task completion
|
|
86
|
+
|
|
47
87
|
### 🤖 **39 Specialized AI Agents**
|
|
48
88
|
|
|
49
89
|
Organized into dynamic teams that load based on your work context:
|
|
@@ -1,16 +1,282 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* PRD New - Launch brainstorming for new product requirement
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Template-based creation (--template flag)
|
|
7
|
+
* - Interactive template selection
|
|
8
|
+
* - Traditional brainstorming mode (backwards compatible)
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
const fs = require('fs');
|
|
7
12
|
const path = require('path');
|
|
8
13
|
const readline = require('readline');
|
|
9
14
|
|
|
15
|
+
// Dynamically resolve template engine path
|
|
16
|
+
// This works both in installed projects and during testing
|
|
17
|
+
let TemplateEngine;
|
|
18
|
+
try {
|
|
19
|
+
// Try relative path from .claude/scripts/pm/
|
|
20
|
+
TemplateEngine = require(path.join(__dirname, '..', '..', '..', '..', 'lib', 'template-engine'));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
try {
|
|
23
|
+
// Try from project root
|
|
24
|
+
TemplateEngine = require(path.join(process.cwd(), 'lib', 'template-engine'));
|
|
25
|
+
} catch (err2) {
|
|
26
|
+
// Fallback to relative
|
|
27
|
+
TemplateEngine = require('../../../../lib/template-engine');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
class PrdCreator {
|
|
11
32
|
constructor() {
|
|
12
33
|
this.prdsDir = path.join('.claude', 'prds');
|
|
13
34
|
this.templatesDir = path.join(__dirname, '..', '..', 'templates');
|
|
35
|
+
this.templateEngine = new TemplateEngine();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create PRD from template
|
|
40
|
+
*/
|
|
41
|
+
async createPrdFromTemplate(prdName, templateName) {
|
|
42
|
+
console.log(`\n🚀 Creating PRD from Template: ${templateName}`);
|
|
43
|
+
console.log(`${'═'.repeat(50)}\n`);
|
|
44
|
+
|
|
45
|
+
// Ensure PRDs directory exists
|
|
46
|
+
if (!fs.existsSync(this.prdsDir)) {
|
|
47
|
+
fs.mkdirSync(this.prdsDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if PRD already exists
|
|
51
|
+
const prdFile = path.join(this.prdsDir, `${prdName}.md`);
|
|
52
|
+
if (fs.existsSync(prdFile)) {
|
|
53
|
+
console.error(`❌ PRD already exists: ${prdName}`);
|
|
54
|
+
console.log(`💡 Edit file: .claude/prds/${prdName}.md`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Find template
|
|
59
|
+
const templatePath = this.templateEngine.findTemplate('prds', templateName);
|
|
60
|
+
if (!templatePath) {
|
|
61
|
+
console.error(`❌ Template not found: ${templateName}`);
|
|
62
|
+
console.log(`💡 List available templates: autopm template:list`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read template to find required variables
|
|
67
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
68
|
+
const requiredVars = this.extractTemplateVariables(templateContent);
|
|
69
|
+
|
|
70
|
+
// Prompt for variables
|
|
71
|
+
const rl = readline.createInterface({
|
|
72
|
+
input: process.stdin,
|
|
73
|
+
output: process.stdout
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const prompt = (question) => new Promise((resolve) => {
|
|
77
|
+
rl.question(question, resolve);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
console.log(`📋 Template: ${templateName}`);
|
|
82
|
+
console.log(`Fill in the following details:\n`);
|
|
83
|
+
|
|
84
|
+
const variables = {
|
|
85
|
+
title: prdName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
86
|
+
type: 'prd'
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Prompt for common variables
|
|
90
|
+
const title = await prompt(`Title [${variables.title}]: `);
|
|
91
|
+
if (title) variables.title = title;
|
|
92
|
+
|
|
93
|
+
const priority = await prompt('Priority (P0/P1/P2/P3) [P2]: ');
|
|
94
|
+
variables.priority = priority || 'P2';
|
|
95
|
+
|
|
96
|
+
const timeline = await prompt('Timeline [TBD]: ');
|
|
97
|
+
variables.timeline = timeline || 'TBD';
|
|
98
|
+
|
|
99
|
+
// Prompt for template-specific variables
|
|
100
|
+
const templateSpecific = this.getTemplateSpecificPrompts(templateName);
|
|
101
|
+
for (const varName of templateSpecific) {
|
|
102
|
+
if (!variables[varName]) {
|
|
103
|
+
const value = await prompt(`${varName.replace(/_/g, ' ')}: `);
|
|
104
|
+
variables[varName] = value || '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Render template
|
|
109
|
+
const rendered = this.templateEngine.renderFile(templatePath, variables);
|
|
110
|
+
|
|
111
|
+
// Write PRD file
|
|
112
|
+
fs.writeFileSync(prdFile, rendered);
|
|
113
|
+
|
|
114
|
+
console.log('\n✅ PRD created successfully!');
|
|
115
|
+
console.log(`📄 File: ${prdFile}`);
|
|
116
|
+
|
|
117
|
+
// Show next steps
|
|
118
|
+
this.showNextSteps(prdName);
|
|
119
|
+
|
|
120
|
+
return true;
|
|
121
|
+
} finally {
|
|
122
|
+
rl.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extract variables from template
|
|
128
|
+
*/
|
|
129
|
+
extractTemplateVariables(template) {
|
|
130
|
+
const varRegex = /\{\{(\w+)\}\}/g;
|
|
131
|
+
const vars = new Set();
|
|
132
|
+
let match;
|
|
133
|
+
|
|
134
|
+
while ((match = varRegex.exec(template)) !== null) {
|
|
135
|
+
vars.add(match[1]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Remove auto-generated variables
|
|
139
|
+
vars.delete('id');
|
|
140
|
+
vars.delete('timestamp');
|
|
141
|
+
vars.delete('date');
|
|
142
|
+
vars.delete('author');
|
|
143
|
+
|
|
144
|
+
return Array.from(vars);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get template-specific prompts
|
|
149
|
+
*/
|
|
150
|
+
getTemplateSpecificPrompts(templateName) {
|
|
151
|
+
const prompts = {
|
|
152
|
+
'api-feature': [
|
|
153
|
+
'api_purpose',
|
|
154
|
+
'problem',
|
|
155
|
+
'business_value',
|
|
156
|
+
'http_method',
|
|
157
|
+
'api_endpoint',
|
|
158
|
+
'auth_method',
|
|
159
|
+
'rate_limit',
|
|
160
|
+
'user_role',
|
|
161
|
+
'api_action',
|
|
162
|
+
'user_benefit'
|
|
163
|
+
],
|
|
164
|
+
'ui-feature': [
|
|
165
|
+
'component_type',
|
|
166
|
+
'feature_purpose',
|
|
167
|
+
'problem',
|
|
168
|
+
'user_need',
|
|
169
|
+
'user_goal',
|
|
170
|
+
'user_role',
|
|
171
|
+
'user_action',
|
|
172
|
+
'user_benefit'
|
|
173
|
+
],
|
|
174
|
+
'bug-fix': [
|
|
175
|
+
'bug_summary',
|
|
176
|
+
'severity',
|
|
177
|
+
'user_impact',
|
|
178
|
+
'step_1',
|
|
179
|
+
'step_2',
|
|
180
|
+
'step_3',
|
|
181
|
+
'expected_behavior',
|
|
182
|
+
'actual_behavior',
|
|
183
|
+
'root_cause',
|
|
184
|
+
'solution_approach'
|
|
185
|
+
],
|
|
186
|
+
'data-migration': [
|
|
187
|
+
'migration_purpose',
|
|
188
|
+
'current_state',
|
|
189
|
+
'desired_state',
|
|
190
|
+
'affected_tables',
|
|
191
|
+
'data_volume',
|
|
192
|
+
'migration_strategy'
|
|
193
|
+
],
|
|
194
|
+
'documentation': [
|
|
195
|
+
'doc_type',
|
|
196
|
+
'target_audience',
|
|
197
|
+
'documentation_scope',
|
|
198
|
+
'current_gaps'
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return prompts[templateName] || [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Show interactive template selection
|
|
207
|
+
*/
|
|
208
|
+
async selectTemplate() {
|
|
209
|
+
const templates = this.templateEngine.listTemplates('prds');
|
|
210
|
+
|
|
211
|
+
if (templates.length === 0) {
|
|
212
|
+
console.log('No templates available');
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log('\n📋 Available Templates:');
|
|
217
|
+
|
|
218
|
+
const builtIn = templates.filter(t => !t.custom);
|
|
219
|
+
const custom = templates.filter(t => t.custom);
|
|
220
|
+
|
|
221
|
+
let index = 1;
|
|
222
|
+
const options = [];
|
|
223
|
+
|
|
224
|
+
builtIn.forEach(t => {
|
|
225
|
+
const description = this.getTemplateDescription(t.name);
|
|
226
|
+
console.log(`${index}. ${t.name.padEnd(20)} - ${description}`);
|
|
227
|
+
options.push(t.name);
|
|
228
|
+
index++;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (custom.length > 0) {
|
|
232
|
+
console.log('\nCustom Templates:');
|
|
233
|
+
custom.forEach(t => {
|
|
234
|
+
console.log(`${index}. ${t.name.padEnd(20)} - [Custom]`);
|
|
235
|
+
options.push(t.name);
|
|
236
|
+
index++;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(`${index}. none${' '.repeat(20)} - Create empty PRD\n`);
|
|
241
|
+
options.push('none');
|
|
242
|
+
|
|
243
|
+
const rl = readline.createInterface({
|
|
244
|
+
input: process.stdin,
|
|
245
|
+
output: process.stdout
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const prompt = (question) => new Promise((resolve) => {
|
|
249
|
+
rl.question(question, resolve);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const selection = await prompt(`Select template (1-${options.length}): `);
|
|
254
|
+
const selectionNum = parseInt(selection, 10);
|
|
255
|
+
|
|
256
|
+
if (selectionNum < 1 || selectionNum > options.length) {
|
|
257
|
+
console.error('Invalid selection');
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return options[selectionNum - 1];
|
|
262
|
+
} finally {
|
|
263
|
+
rl.close();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get template description
|
|
269
|
+
*/
|
|
270
|
+
getTemplateDescription(templateName) {
|
|
271
|
+
const descriptions = {
|
|
272
|
+
'api-feature': 'REST/GraphQL API development',
|
|
273
|
+
'ui-feature': 'Frontend component/page',
|
|
274
|
+
'bug-fix': 'Bug resolution workflow',
|
|
275
|
+
'data-migration': 'Database schema changes',
|
|
276
|
+
'documentation': 'Documentation updates'
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return descriptions[templateName] || 'Template';
|
|
14
280
|
}
|
|
15
281
|
|
|
16
282
|
async createPrd(prdName) {
|
|
@@ -319,7 +585,19 @@ ${data.technical || 'Technical requirements to be specified...'}
|
|
|
319
585
|
}
|
|
320
586
|
|
|
321
587
|
async run(args) {
|
|
322
|
-
|
|
588
|
+
// Parse arguments
|
|
589
|
+
let prdName = null;
|
|
590
|
+
let templateName = null;
|
|
591
|
+
|
|
592
|
+
// Check for --template or -t flag
|
|
593
|
+
for (let i = 0; i < args.length; i++) {
|
|
594
|
+
if (args[i] === '--template' || args[i] === '-t') {
|
|
595
|
+
templateName = args[i + 1];
|
|
596
|
+
i++; // Skip next arg
|
|
597
|
+
} else if (!prdName) {
|
|
598
|
+
prdName = args[i];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
323
601
|
|
|
324
602
|
if (!prdName) {
|
|
325
603
|
// Interactive mode
|
|
@@ -345,12 +623,24 @@ ${data.technical || 'Technical requirements to be specified...'}
|
|
|
345
623
|
console.error('❌ Error: PRD name required');
|
|
346
624
|
process.exit(1);
|
|
347
625
|
}
|
|
626
|
+
|
|
627
|
+
// Ask for template if not provided
|
|
628
|
+
if (!templateName) {
|
|
629
|
+
templateName = await this.selectTemplate();
|
|
630
|
+
}
|
|
348
631
|
}
|
|
349
632
|
|
|
350
633
|
// Sanitize PRD name
|
|
351
634
|
prdName = prdName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
352
635
|
|
|
353
|
-
|
|
636
|
+
// Create PRD from template or traditional mode
|
|
637
|
+
let success;
|
|
638
|
+
if (templateName && templateName !== 'none') {
|
|
639
|
+
success = await this.createPrdFromTemplate(prdName, templateName);
|
|
640
|
+
} else {
|
|
641
|
+
success = await this.createPrd(prdName);
|
|
642
|
+
}
|
|
643
|
+
|
|
354
644
|
process.exit(success ? 0 : 1);
|
|
355
645
|
}
|
|
356
646
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Template List - Show available templates
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* autopm template:list
|
|
7
|
+
* autopm template:list prd
|
|
8
|
+
* autopm template:list epic
|
|
9
|
+
* autopm template:list task
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Dynamically resolve template engine path
|
|
15
|
+
let TemplateEngine;
|
|
16
|
+
try {
|
|
17
|
+
TemplateEngine = require(path.join(__dirname, '..', '..', '..', '..', 'lib', 'template-engine'));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
try {
|
|
20
|
+
TemplateEngine = require(path.join(process.cwd(), 'lib', 'template-engine'));
|
|
21
|
+
} catch (err2) {
|
|
22
|
+
TemplateEngine = require('../../../lib/template-engine');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class TemplateLister {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.templateEngine = new TemplateEngine();
|
|
29
|
+
this.descriptions = {
|
|
30
|
+
// PRD templates
|
|
31
|
+
'api-feature': 'REST/GraphQL API development',
|
|
32
|
+
'ui-feature': 'Frontend component/page',
|
|
33
|
+
'bug-fix': 'Bug resolution workflow',
|
|
34
|
+
'data-migration': 'Database schema changes',
|
|
35
|
+
'documentation': 'Documentation updates',
|
|
36
|
+
|
|
37
|
+
// Epic templates (future)
|
|
38
|
+
'sprint': 'Sprint planning',
|
|
39
|
+
'release': 'Release epic',
|
|
40
|
+
|
|
41
|
+
// Task templates (future)
|
|
42
|
+
'development': 'Development task',
|
|
43
|
+
'testing': 'Testing task'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
list(type = null) {
|
|
48
|
+
const types = type ? [type] : ['prds', 'epics', 'tasks'];
|
|
49
|
+
|
|
50
|
+
console.log('\n📋 Available Templates\n');
|
|
51
|
+
console.log('═'.repeat(60));
|
|
52
|
+
|
|
53
|
+
for (const templateType of types) {
|
|
54
|
+
const templates = this.templateEngine.listTemplates(templateType);
|
|
55
|
+
|
|
56
|
+
if (templates.length === 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const typeName = templateType.charAt(0).toUpperCase() + templateType.slice(1);
|
|
61
|
+
console.log(`\n${typeName} Templates:`);
|
|
62
|
+
|
|
63
|
+
const builtIn = templates.filter(t => !t.custom);
|
|
64
|
+
const custom = templates.filter(t => t.custom);
|
|
65
|
+
|
|
66
|
+
if (builtIn.length > 0) {
|
|
67
|
+
console.log('\nBuilt-in:');
|
|
68
|
+
builtIn.forEach(t => {
|
|
69
|
+
const desc = this.descriptions[t.name] || 'Template';
|
|
70
|
+
console.log(` • ${t.name.padEnd(20)} - ${desc}`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (custom.length > 0) {
|
|
75
|
+
console.log('\nCustom:');
|
|
76
|
+
custom.forEach(t => {
|
|
77
|
+
console.log(` • ${t.name.padEnd(20)} - [Custom Template]`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('\n' + '═'.repeat(60));
|
|
83
|
+
console.log('\nUsage:');
|
|
84
|
+
console.log(' autopm prd:new --template <name> "<title>"');
|
|
85
|
+
console.log(' autopm prd:new -t api-feature "User Authentication API"');
|
|
86
|
+
console.log('\nCreate custom template:');
|
|
87
|
+
console.log(' autopm template:new prd my-custom-template\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
run(args) {
|
|
91
|
+
const type = args[0]; // Optional: 'prd', 'epic', 'task'
|
|
92
|
+
const validTypes = ['prd', 'prds', 'epic', 'epics', 'task', 'tasks'];
|
|
93
|
+
|
|
94
|
+
if (type && !validTypes.includes(type)) {
|
|
95
|
+
console.error(`❌ Invalid type: ${type}`);
|
|
96
|
+
console.log('Valid types: prd, epic, task');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Normalize type
|
|
101
|
+
let normalizedType = null;
|
|
102
|
+
if (type) {
|
|
103
|
+
if (type === 'prd') normalizedType = 'prds';
|
|
104
|
+
else if (type === 'epic') normalizedType = 'epics';
|
|
105
|
+
else if (type === 'task') normalizedType = 'tasks';
|
|
106
|
+
else normalizedType = type;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.list(normalizedType);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Main execution
|
|
114
|
+
if (require.main === module) {
|
|
115
|
+
const lister = new TemplateLister();
|
|
116
|
+
lister.run(process.argv.slice(2));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = TemplateLister;
|