claude-autopm 1.17.0 ā 1.20.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 +159 -0
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/bin/autopm.js +25 -0
- package/bin/commands/team.js +86 -0
- package/package.json +1 -1
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GitHub Issue Creation Helper
|
|
5
|
-
* Handles the limitation that gh issue create doesn't support --json flag
|
|
6
|
-
* Provides methods to create issues and get JSON responses
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { execSync } = require('child_process');
|
|
10
|
-
const fs = require('fs-extra');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
class GitHubIssueCreator {
|
|
14
|
-
constructor() {
|
|
15
|
-
this.execSync = execSync;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Set custom execSync for testing
|
|
20
|
-
*/
|
|
21
|
-
_setExecSync(customExecSync) {
|
|
22
|
-
this.execSync = customExecSync;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Extract issue number from GitHub URL
|
|
27
|
-
*/
|
|
28
|
-
_extractIssueNumber(url) {
|
|
29
|
-
if (!url) return null;
|
|
30
|
-
|
|
31
|
-
const match = url.toString().trim().match(/\/issues\/(\d+)/);
|
|
32
|
-
return match ? parseInt(match[1], 10) : null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Validate required options
|
|
37
|
-
*/
|
|
38
|
-
_validateOptions(options, requiredFields = ['title']) {
|
|
39
|
-
for (const field of requiredFields) {
|
|
40
|
-
if (!options[field]) {
|
|
41
|
-
throw new Error(`${field} is required`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Build gh issue create command
|
|
48
|
-
*/
|
|
49
|
-
_buildCreateCommand(options) {
|
|
50
|
-
const parts = ['gh issue create'];
|
|
51
|
-
|
|
52
|
-
// Title
|
|
53
|
-
parts.push(`--title "${options.title.replace(/"/g, '\\"')}"`);
|
|
54
|
-
|
|
55
|
-
// Body or body file
|
|
56
|
-
if (options.bodyFile) {
|
|
57
|
-
parts.push(`--body-file "${options.bodyFile}"`);
|
|
58
|
-
} else if (options.body) {
|
|
59
|
-
parts.push(`--body "${options.body.replace(/"/g, '\\"')}"`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Labels
|
|
63
|
-
if (options.labels && options.labels.length > 0) {
|
|
64
|
-
const labelStr = options.labels.join(',');
|
|
65
|
-
parts.push(`--label "${labelStr}"`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Assignee
|
|
69
|
-
if (options.assignee) {
|
|
70
|
-
parts.push(`--assignee "${options.assignee}"`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Milestone
|
|
74
|
-
if (options.milestone) {
|
|
75
|
-
parts.push(`--milestone "${options.milestone}"`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Project
|
|
79
|
-
if (options.project) {
|
|
80
|
-
parts.push(`--project "${options.project}"`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return parts.join(' ');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create issue using gh CLI and return issue details
|
|
88
|
-
*/
|
|
89
|
-
async createIssue(options) {
|
|
90
|
-
this._validateOptions(options, ['title']);
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
// Create the issue
|
|
94
|
-
const command = this._buildCreateCommand(options);
|
|
95
|
-
const output = this.execSync(command, { encoding: 'utf8' });
|
|
96
|
-
|
|
97
|
-
// Extract issue number from URL
|
|
98
|
-
const number = this._extractIssueNumber(output);
|
|
99
|
-
|
|
100
|
-
if (!number) {
|
|
101
|
-
throw new Error(`Could not extract issue number from output: ${output}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
number,
|
|
106
|
-
url: output.trim()
|
|
107
|
-
};
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error.code === 'ENOENT') {
|
|
110
|
-
throw new Error('GitHub CLI (gh) is not installed or not in PATH');
|
|
111
|
-
}
|
|
112
|
-
if (error.message && error.message.includes('not authenticated')) {
|
|
113
|
-
throw new Error('GitHub CLI not authenticated. Run: gh auth login');
|
|
114
|
-
}
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Create issue using gh api for JSON response
|
|
121
|
-
*/
|
|
122
|
-
async createIssueWithJson(options) {
|
|
123
|
-
this._validateOptions(options, ['title']);
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
// Prepare API payload
|
|
127
|
-
const payload = {
|
|
128
|
-
title: options.title,
|
|
129
|
-
body: options.body || ''
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// Read body from file if specified
|
|
133
|
-
if (options.bodyFile && !options.body) {
|
|
134
|
-
payload.body = await fs.readFile(options.bodyFile, 'utf8');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Add labels
|
|
138
|
-
if (options.labels && options.labels.length > 0) {
|
|
139
|
-
payload.labels = options.labels;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Add assignees
|
|
143
|
-
if (options.assignee && options.assignee !== '@me') {
|
|
144
|
-
payload.assignees = [options.assignee];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Build gh api command
|
|
148
|
-
const fieldArgs = Object.entries(payload)
|
|
149
|
-
.map(([key, value]) => {
|
|
150
|
-
if (Array.isArray(value)) {
|
|
151
|
-
return `--field ${key}='${JSON.stringify(value)}'`;
|
|
152
|
-
}
|
|
153
|
-
return `--field ${key}="${value.replace(/"/g, '\\"')}"`;
|
|
154
|
-
})
|
|
155
|
-
.join(' ');
|
|
156
|
-
|
|
157
|
-
const command = `gh api repos/:owner/:repo/issues --method POST ${fieldArgs}`;
|
|
158
|
-
const output = this.execSync(command, { encoding: 'utf8' });
|
|
159
|
-
|
|
160
|
-
return JSON.parse(output);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
if (error.code === 'ENOENT') {
|
|
163
|
-
throw new Error('GitHub CLI (gh) is not installed or not in PATH');
|
|
164
|
-
}
|
|
165
|
-
if (error.message && error.message.includes('not authenticated')) {
|
|
166
|
-
throw new Error('GitHub CLI not authenticated. Run: gh auth login');
|
|
167
|
-
}
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Create an epic (issue with epic labels)
|
|
174
|
-
*/
|
|
175
|
-
async createEpic(options) {
|
|
176
|
-
const epicName = options.epicName || options.title?.replace(/^Epic:\s*/, '');
|
|
177
|
-
|
|
178
|
-
if (!epicName) {
|
|
179
|
-
throw new Error('epicName or title is required for epic creation');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Prepare epic options
|
|
183
|
-
const epicOptions = {
|
|
184
|
-
title: options.title || `Epic: ${epicName}`,
|
|
185
|
-
body: options.body || this._generateEpicBody(options),
|
|
186
|
-
labels: ['epic', `epic:${epicName}`]
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Add additional labels
|
|
190
|
-
if (options.additionalLabels) {
|
|
191
|
-
epicOptions.labels.push(...options.additionalLabels);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Use JSON method if we need the full response
|
|
195
|
-
if (options.returnJson !== false) {
|
|
196
|
-
return await this.createIssueWithJson(epicOptions);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return await this.createIssue(epicOptions);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Generate epic body from template
|
|
204
|
-
*/
|
|
205
|
-
_generateEpicBody(options) {
|
|
206
|
-
let body = '## Epic Description\n\n';
|
|
207
|
-
|
|
208
|
-
if (options.description) {
|
|
209
|
-
body += `${options.description}\n\n`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (options.acceptanceCriteria && options.acceptanceCriteria.length > 0) {
|
|
213
|
-
body += '## Acceptance Criteria\n\n';
|
|
214
|
-
options.acceptanceCriteria.forEach(criteria => {
|
|
215
|
-
body += `- [ ] ${criteria}\n`;
|
|
216
|
-
});
|
|
217
|
-
body += '\n';
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (options.tasks && options.tasks.length > 0) {
|
|
221
|
-
body += '## Tasks\n\n';
|
|
222
|
-
options.tasks.forEach(task => {
|
|
223
|
-
body += `- [ ] ${task}\n`;
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return body;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Helper method for PM commands - create epic and return just the number
|
|
232
|
-
*/
|
|
233
|
-
async createEpicAndGetNumber(options) {
|
|
234
|
-
const result = await this.createEpic({ ...options, returnJson: false });
|
|
235
|
-
return result.number;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Create singleton instance
|
|
240
|
-
const creator = new GitHubIssueCreator();
|
|
241
|
-
|
|
242
|
-
// Export methods bound to instance
|
|
243
|
-
module.exports = {
|
|
244
|
-
createIssue: creator.createIssue.bind(creator),
|
|
245
|
-
createIssueWithJson: creator.createIssueWithJson.bind(creator),
|
|
246
|
-
createEpic: creator.createEpic.bind(creator),
|
|
247
|
-
createEpicAndGetNumber: creator.createEpicAndGetNumber.bind(creator),
|
|
248
|
-
_setExecSync: creator._setExecSync.bind(creator),
|
|
249
|
-
_extractIssueNumber: creator._extractIssueNumber.bind(creator)
|
|
250
|
-
};
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Interactive Prompt Helper
|
|
4
|
-
* Provides interactive prompts for missing parameters
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
const fs = require('fs').promises;
|
|
9
|
-
|
|
10
|
-
class InteractivePrompt {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
this.rl = null;
|
|
13
|
-
this.history = new Map();
|
|
14
|
-
this.defaults = options.defaults || {};
|
|
15
|
-
this.validation = options.validation || {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Initialize readline interface
|
|
20
|
-
*/
|
|
21
|
-
init() {
|
|
22
|
-
if (!this.rl) {
|
|
23
|
-
this.rl = readline.createInterface({
|
|
24
|
-
input: process.stdin,
|
|
25
|
-
output: process.stdout,
|
|
26
|
-
terminal: true
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Close readline interface
|
|
33
|
-
*/
|
|
34
|
-
close() {
|
|
35
|
-
if (this.rl) {
|
|
36
|
-
this.rl.close();
|
|
37
|
-
this.rl = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Prompt for input with validation
|
|
43
|
-
*/
|
|
44
|
-
async prompt(question, options = {}) {
|
|
45
|
-
this.init();
|
|
46
|
-
|
|
47
|
-
const {
|
|
48
|
-
defaultValue = this.defaults[question],
|
|
49
|
-
required = false,
|
|
50
|
-
type = 'string',
|
|
51
|
-
choices = null,
|
|
52
|
-
mask = false,
|
|
53
|
-
validate = this.validation[question]
|
|
54
|
-
} = options;
|
|
55
|
-
|
|
56
|
-
let promptText = question;
|
|
57
|
-
|
|
58
|
-
// Add choices if available
|
|
59
|
-
if (choices && choices.length > 0) {
|
|
60
|
-
promptText += '\nChoices:\n';
|
|
61
|
-
choices.forEach((choice, index) => {
|
|
62
|
-
promptText += ` ${index + 1}) ${choice}\n`;
|
|
63
|
-
});
|
|
64
|
-
promptText += 'Enter choice';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Add default value indicator
|
|
68
|
-
if (defaultValue !== undefined) {
|
|
69
|
-
promptText += ` [${defaultValue}]`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
promptText += ': ';
|
|
73
|
-
|
|
74
|
-
return new Promise((resolve) => {
|
|
75
|
-
const askQuestion = () => {
|
|
76
|
-
this.rl.question(promptText, async (answer) => {
|
|
77
|
-
// Use default if no answer provided
|
|
78
|
-
if (!answer && defaultValue !== undefined) {
|
|
79
|
-
answer = defaultValue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Check required
|
|
83
|
-
if (required && !answer) {
|
|
84
|
-
console.log('This field is required.');
|
|
85
|
-
return askQuestion();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Handle choices
|
|
89
|
-
if (choices && answer) {
|
|
90
|
-
const choiceIndex = parseInt(answer) - 1;
|
|
91
|
-
if (choiceIndex >= 0 && choiceIndex < choices.length) {
|
|
92
|
-
answer = choices[choiceIndex];
|
|
93
|
-
} else if (!choices.includes(answer)) {
|
|
94
|
-
console.log('Invalid choice. Please select from the list.');
|
|
95
|
-
return askQuestion();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Type conversion
|
|
100
|
-
if (type === 'number' && answer) {
|
|
101
|
-
answer = parseFloat(answer);
|
|
102
|
-
if (isNaN(answer)) {
|
|
103
|
-
console.log('Please enter a valid number.');
|
|
104
|
-
return askQuestion();
|
|
105
|
-
}
|
|
106
|
-
} else if (type === 'boolean' && answer) {
|
|
107
|
-
answer = ['yes', 'y', 'true', '1'].includes(answer.toLowerCase());
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Custom validation
|
|
111
|
-
if (validate && answer) {
|
|
112
|
-
const validationResult = await validate(answer);
|
|
113
|
-
if (validationResult !== true) {
|
|
114
|
-
console.log(validationResult || 'Invalid input.');
|
|
115
|
-
return askQuestion();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Store in history
|
|
120
|
-
this.history.set(question, answer);
|
|
121
|
-
|
|
122
|
-
resolve(answer);
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
askQuestion();
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Prompt for multiple inputs
|
|
132
|
-
*/
|
|
133
|
-
async promptMany(questions) {
|
|
134
|
-
const answers = {};
|
|
135
|
-
|
|
136
|
-
for (const question of questions) {
|
|
137
|
-
if (typeof question === 'string') {
|
|
138
|
-
answers[question] = await this.prompt(question);
|
|
139
|
-
} else {
|
|
140
|
-
const { name, ...options } = question;
|
|
141
|
-
answers[name] = await this.prompt(name, options);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return answers;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Confirm action
|
|
150
|
-
*/
|
|
151
|
-
async confirm(message, defaultValue = false) {
|
|
152
|
-
const answer = await this.prompt(message, {
|
|
153
|
-
type: 'boolean',
|
|
154
|
-
defaultValue: defaultValue ? 'yes' : 'no'
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return answer;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Select from list
|
|
162
|
-
*/
|
|
163
|
-
async select(message, choices, defaultIndex = 0) {
|
|
164
|
-
return this.prompt(message, {
|
|
165
|
-
choices,
|
|
166
|
-
defaultValue: choices[defaultIndex]
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Multi-select from list
|
|
172
|
-
*/
|
|
173
|
-
async multiSelect(message, choices, defaults = []) {
|
|
174
|
-
console.log(message);
|
|
175
|
-
console.log('Select multiple (comma-separated numbers):');
|
|
176
|
-
choices.forEach((choice, index) => {
|
|
177
|
-
const selected = defaults.includes(choice) ? 'ā' : ' ';
|
|
178
|
-
console.log(` [${selected}] ${index + 1}) ${choice}`);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const answer = await this.prompt('Enter choices', {
|
|
182
|
-
defaultValue: defaults.map(d => choices.indexOf(d) + 1).join(',')
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const indices = answer.split(',').map(s => parseInt(s.trim()) - 1);
|
|
186
|
-
return indices
|
|
187
|
-
.filter(i => i >= 0 && i < choices.length)
|
|
188
|
-
.map(i => choices[i]);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Password input (masked)
|
|
193
|
-
*/
|
|
194
|
-
async password(message) {
|
|
195
|
-
this.init();
|
|
196
|
-
|
|
197
|
-
return new Promise((resolve) => {
|
|
198
|
-
const stdin = process.stdin;
|
|
199
|
-
const stdout = process.stdout;
|
|
200
|
-
|
|
201
|
-
stdout.write(message + ': ');
|
|
202
|
-
|
|
203
|
-
stdin.setRawMode(true);
|
|
204
|
-
stdin.resume();
|
|
205
|
-
stdin.setEncoding('utf8');
|
|
206
|
-
|
|
207
|
-
let password = '';
|
|
208
|
-
|
|
209
|
-
const onData = (char) => {
|
|
210
|
-
if (char === '\n' || char === '\r') {
|
|
211
|
-
stdin.setRawMode(false);
|
|
212
|
-
stdin.pause();
|
|
213
|
-
stdin.removeListener('data', onData);
|
|
214
|
-
stdout.write('\n');
|
|
215
|
-
resolve(password);
|
|
216
|
-
} else if (char === '\u0003') {
|
|
217
|
-
// Ctrl+C
|
|
218
|
-
process.exit();
|
|
219
|
-
} else if (char === '\u007f') {
|
|
220
|
-
// Backspace
|
|
221
|
-
if (password.length > 0) {
|
|
222
|
-
password = password.slice(0, -1);
|
|
223
|
-
stdout.clearLine();
|
|
224
|
-
stdout.cursorTo(0);
|
|
225
|
-
stdout.write(message + ': ' + '*'.repeat(password.length));
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
password += char;
|
|
229
|
-
stdout.write('*');
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
stdin.on('data', onData);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Create wizard for complex configurations
|
|
239
|
-
*/
|
|
240
|
-
async wizard(steps) {
|
|
241
|
-
const results = {};
|
|
242
|
-
|
|
243
|
-
console.log('\nš§ Configuration Wizard\n');
|
|
244
|
-
|
|
245
|
-
for (const step of steps) {
|
|
246
|
-
if (step.message) {
|
|
247
|
-
console.log(`\n${step.message}\n`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (step.condition && !step.condition(results)) {
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const answer = await this.prompt(step.name, step.options);
|
|
255
|
-
results[step.name] = answer;
|
|
256
|
-
|
|
257
|
-
if (step.postProcess) {
|
|
258
|
-
await step.postProcess(answer, results);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
console.log('\nā
Configuration complete!\n');
|
|
263
|
-
return results;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Preset prompts for Azure DevOps
|
|
269
|
-
*/
|
|
270
|
-
class AzurePrompts extends InteractivePrompt {
|
|
271
|
-
constructor() {
|
|
272
|
-
super({
|
|
273
|
-
validation: {
|
|
274
|
-
'Work Item ID': (value) => {
|
|
275
|
-
return /^\d+$/.test(value) || 'Please enter a valid work item ID';
|
|
276
|
-
},
|
|
277
|
-
'Sprint Name': (value) => {
|
|
278
|
-
return value.length > 0 || 'Sprint name cannot be empty';
|
|
279
|
-
},
|
|
280
|
-
'Story Points': (value) => {
|
|
281
|
-
const points = parseFloat(value);
|
|
282
|
-
return (!isNaN(points) && points > 0 && points <= 100) ||
|
|
283
|
-
'Please enter a valid number between 1 and 100';
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Prompt for missing work item details
|
|
291
|
-
*/
|
|
292
|
-
async promptWorkItem(existing = {}) {
|
|
293
|
-
const questions = [];
|
|
294
|
-
|
|
295
|
-
if (!existing.title) {
|
|
296
|
-
questions.push({
|
|
297
|
-
name: 'title',
|
|
298
|
-
options: { required: true }
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!existing.type) {
|
|
303
|
-
questions.push({
|
|
304
|
-
name: 'type',
|
|
305
|
-
options: {
|
|
306
|
-
choices: ['Task', 'User Story', 'Bug', 'Feature'],
|
|
307
|
-
defaultValue: 'Task'
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (!existing.assignedTo) {
|
|
313
|
-
questions.push({
|
|
314
|
-
name: 'assignedTo',
|
|
315
|
-
options: {
|
|
316
|
-
defaultValue: process.env.AZURE_DEVOPS_USER || 'me'
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (!existing.priority && existing.type !== 'Feature') {
|
|
322
|
-
questions.push({
|
|
323
|
-
name: 'priority',
|
|
324
|
-
options: {
|
|
325
|
-
choices: ['1 - Critical', '2 - High', '3 - Medium', '4 - Low'],
|
|
326
|
-
defaultValue: '3 - Medium'
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const answers = await this.promptMany(questions);
|
|
332
|
-
return { ...existing, ...answers };
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
module.exports = { InteractivePrompt, AzurePrompts };
|