claude-autopm 1.18.0 → 1.20.1
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/README.md +1 -1
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/agents/decision-matrices/python-backend-selection.md +25 -25
- package/autopm/.claude/agents/decision-matrices/ui-framework-selection.md +43 -43
- package/autopm/.claude/agents/devops/github-operations-specialist.md +1 -1
- package/autopm/.claude/agents/frameworks/README.md +5 -5
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -1
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +3 -3
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +1 -1
- package/autopm/.claude/commands/playwright/test-scaffold.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/commands/ui/bootstrap-scaffold.md +6 -5
- package/autopm/.claude/commands/ui/tailwind-system.md +1 -1
- package/autopm/.claude/examples/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/examples/mcp-servers.example.json +2 -2
- package/autopm/.claude/hooks/docker-first-enforcement.sh +1 -1
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/rules/agent-coordination.md +26 -24
- package/autopm/.claude/rules/docker-first-development.md +1 -1
- package/autopm/.claude/rules/infrastructure-pipeline.md +1 -1
- package/autopm/.claude/rules/ui-development-standards.md +1 -1
- package/autopm/.claude/rules/visual-testing.md +3 -3
- 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/autopm/.claude/teams.json +3 -5
- package/autopm/.claude/templates/claude-templates/addons/devops-agents.md +2 -2
- package/autopm/.claude/templates/claude-templates/addons/docker-agents.md +4 -4
- package/autopm/.claude/templates/claude-templates/addons/minimal-agents.md +1 -1
- package/autopm/.claude/templates/issue-decomposition/api.yaml +2 -2
- package/autopm/.claude/templates/issue-decomposition/auth.yaml +4 -4
- package/autopm/.claude/templates/issue-decomposition/crud.yaml +3 -3
- package/autopm/.claude/templates/issue-decomposition/default.yaml +1 -1
- package/autopm/.claude/templates/issue-decomposition/ui-feature.yaml +2 -2
- package/bin/autopm.js +25 -0
- package/package.json +1 -2
- 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,546 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Workflow Manager
|
|
3
|
-
* Centralized GitHub Actions workflow management functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs').promises;
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Configuration
|
|
11
|
-
*/
|
|
12
|
-
const CONFIG = {
|
|
13
|
-
directories: {
|
|
14
|
-
workflows: '.github/workflows'
|
|
15
|
-
},
|
|
16
|
-
defaults: {
|
|
17
|
-
os: 'ubuntu-latest',
|
|
18
|
-
nodeVersion: '18.x'
|
|
19
|
-
},
|
|
20
|
-
fileExtensions: ['.yml', '.yaml']
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Workflow templates
|
|
25
|
-
*/
|
|
26
|
-
const TEMPLATES = {
|
|
27
|
-
node: {
|
|
28
|
-
name: 'Node.js CI',
|
|
29
|
-
content: `name: Node.js CI
|
|
30
|
-
|
|
31
|
-
on:
|
|
32
|
-
push:
|
|
33
|
-
branches: [ main, develop ]
|
|
34
|
-
pull_request:
|
|
35
|
-
branches: [ main ]
|
|
36
|
-
|
|
37
|
-
jobs:
|
|
38
|
-
test:
|
|
39
|
-
runs-on: ubuntu-latest
|
|
40
|
-
|
|
41
|
-
strategy:
|
|
42
|
-
matrix:
|
|
43
|
-
node-version: [16.x, 18.x, 20.x]
|
|
44
|
-
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v3
|
|
47
|
-
- name: Use Node.js \${{ matrix.node-version }}
|
|
48
|
-
uses: actions/setup-node@v3
|
|
49
|
-
with:
|
|
50
|
-
node-version: \${{ matrix.node-version }}
|
|
51
|
-
cache: 'npm'
|
|
52
|
-
- run: npm ci
|
|
53
|
-
- run: npm test
|
|
54
|
-
- run: npm run build --if-present`
|
|
55
|
-
},
|
|
56
|
-
python: {
|
|
57
|
-
name: 'Python CI',
|
|
58
|
-
content: `name: Python CI
|
|
59
|
-
|
|
60
|
-
on:
|
|
61
|
-
push:
|
|
62
|
-
branches: [ main, develop ]
|
|
63
|
-
pull_request:
|
|
64
|
-
branches: [ main ]
|
|
65
|
-
|
|
66
|
-
jobs:
|
|
67
|
-
test:
|
|
68
|
-
runs-on: ubuntu-latest
|
|
69
|
-
|
|
70
|
-
strategy:
|
|
71
|
-
matrix:
|
|
72
|
-
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
|
73
|
-
|
|
74
|
-
steps:
|
|
75
|
-
- uses: actions/checkout@v3
|
|
76
|
-
- name: Set up Python \${{ matrix.python-version }}
|
|
77
|
-
uses: actions/setup-python@v4
|
|
78
|
-
with:
|
|
79
|
-
python-version: \${{ matrix.python-version }}
|
|
80
|
-
- name: Install dependencies
|
|
81
|
-
run: |
|
|
82
|
-
python -m pip install --upgrade pip
|
|
83
|
-
pip install pytest
|
|
84
|
-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
85
|
-
- name: Test with pytest
|
|
86
|
-
run: |
|
|
87
|
-
pytest`
|
|
88
|
-
},
|
|
89
|
-
docker: {
|
|
90
|
-
name: 'Docker Build',
|
|
91
|
-
content: `name: Docker Build
|
|
92
|
-
|
|
93
|
-
on:
|
|
94
|
-
push:
|
|
95
|
-
branches: [ main ]
|
|
96
|
-
tags: [ 'v*' ]
|
|
97
|
-
|
|
98
|
-
jobs:
|
|
99
|
-
docker:
|
|
100
|
-
runs-on: ubuntu-latest
|
|
101
|
-
|
|
102
|
-
steps:
|
|
103
|
-
- uses: actions/checkout@v3
|
|
104
|
-
|
|
105
|
-
- name: Set up Docker Buildx
|
|
106
|
-
uses: docker/setup-buildx-action@v2
|
|
107
|
-
|
|
108
|
-
- name: Log in to Docker Hub
|
|
109
|
-
uses: docker/login-action@v2
|
|
110
|
-
with:
|
|
111
|
-
username: \${{ secrets.DOCKER_USERNAME }}
|
|
112
|
-
password: \${{ secrets.DOCKER_TOKEN }}
|
|
113
|
-
|
|
114
|
-
- name: Build and push
|
|
115
|
-
uses: docker/build-push-action@v4
|
|
116
|
-
with:
|
|
117
|
-
context: .
|
|
118
|
-
push: true
|
|
119
|
-
tags: user/app:latest`
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
class GitHubWorkflowManager {
|
|
124
|
-
constructor(projectRoot = process.cwd()) {
|
|
125
|
-
this.projectRoot = projectRoot;
|
|
126
|
-
this.workflowsDir = path.join(projectRoot, CONFIG.directories.workflows);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Creates a new workflow
|
|
131
|
-
*/
|
|
132
|
-
async createWorkflow(name, options = {}) {
|
|
133
|
-
await fs.mkdir(this.workflowsDir, { recursive: true });
|
|
134
|
-
|
|
135
|
-
const workflow = {
|
|
136
|
-
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
137
|
-
on: options.trigger || ['push', 'pull_request'],
|
|
138
|
-
jobs: {
|
|
139
|
-
build: {
|
|
140
|
-
'runs-on': options.os || CONFIG.defaults.os,
|
|
141
|
-
steps: [
|
|
142
|
-
{ uses: 'actions/checkout@v3' },
|
|
143
|
-
{ name: 'Setup', run: 'echo "Setting up..."' },
|
|
144
|
-
{ name: 'Build', run: 'echo "Building..."' },
|
|
145
|
-
{ name: 'Test', run: 'echo "Testing..."' }
|
|
146
|
-
]
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const workflowPath = path.join(this.workflowsDir, `${name}.yml`);
|
|
152
|
-
await fs.writeFile(workflowPath, this.generateYAML(workflow));
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
path: workflowPath,
|
|
156
|
-
name: workflow.name,
|
|
157
|
-
triggers: Array.isArray(workflow.on) ? workflow.on : [workflow.on]
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Creates a test workflow
|
|
163
|
-
*/
|
|
164
|
-
async createTestWorkflow(options = {}) {
|
|
165
|
-
await fs.mkdir(this.workflowsDir, { recursive: true });
|
|
166
|
-
|
|
167
|
-
const testWorkflow = {
|
|
168
|
-
name: 'Tests',
|
|
169
|
-
on: {
|
|
170
|
-
push: {
|
|
171
|
-
branches: ['main', 'develop']
|
|
172
|
-
},
|
|
173
|
-
pull_request: {
|
|
174
|
-
branches: ['main']
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
jobs: {
|
|
178
|
-
test: {
|
|
179
|
-
'runs-on': options.os || CONFIG.defaults.os,
|
|
180
|
-
steps: [
|
|
181
|
-
{ uses: 'actions/checkout@v3' },
|
|
182
|
-
{
|
|
183
|
-
name: 'Setup Node.js',
|
|
184
|
-
uses: 'actions/setup-node@v3',
|
|
185
|
-
with: {
|
|
186
|
-
'node-version': options.nodeVersion || CONFIG.defaults.nodeVersion,
|
|
187
|
-
cache: 'npm'
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
{ name: 'Install dependencies', run: 'npm ci' },
|
|
191
|
-
{ name: 'Run tests', run: 'npm test' },
|
|
192
|
-
{
|
|
193
|
-
name: 'Upload coverage',
|
|
194
|
-
if: 'success()',
|
|
195
|
-
uses: 'codecov/codecov-action@v3'
|
|
196
|
-
}
|
|
197
|
-
]
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const workflowPath = path.join(this.workflowsDir, 'test.yml');
|
|
203
|
-
await fs.writeFile(workflowPath, this.generateYAML(testWorkflow));
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
path: workflowPath,
|
|
207
|
-
name: testWorkflow.name
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Creates a release workflow
|
|
213
|
-
*/
|
|
214
|
-
async createReleaseWorkflow(options = {}) {
|
|
215
|
-
await fs.mkdir(this.workflowsDir, { recursive: true });
|
|
216
|
-
|
|
217
|
-
let trigger = {};
|
|
218
|
-
if (options.trigger === 'tag') {
|
|
219
|
-
trigger = {
|
|
220
|
-
push: {
|
|
221
|
-
tags: ['v*']
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
} else {
|
|
225
|
-
trigger = {
|
|
226
|
-
release: {
|
|
227
|
-
types: ['created']
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const releaseWorkflow = {
|
|
233
|
-
name: 'Release',
|
|
234
|
-
on: trigger,
|
|
235
|
-
jobs: {
|
|
236
|
-
release: {
|
|
237
|
-
'runs-on': CONFIG.defaults.os,
|
|
238
|
-
steps: [
|
|
239
|
-
{ uses: 'actions/checkout@v3' },
|
|
240
|
-
{
|
|
241
|
-
name: 'Setup Node.js',
|
|
242
|
-
uses: 'actions/setup-node@v3',
|
|
243
|
-
with: {
|
|
244
|
-
'node-version': CONFIG.defaults.nodeVersion,
|
|
245
|
-
'registry-url': 'https://registry.npmjs.org/'
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
{ name: 'Install dependencies', run: 'npm ci' },
|
|
249
|
-
{ name: 'Build', run: 'npm run build --if-present' },
|
|
250
|
-
{ name: 'Test', run: 'npm test' },
|
|
251
|
-
{
|
|
252
|
-
name: 'Publish to npm',
|
|
253
|
-
if: options.publish !== false,
|
|
254
|
-
run: 'npm publish',
|
|
255
|
-
env: {
|
|
256
|
-
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
name: 'Create GitHub Release',
|
|
261
|
-
uses: 'softprops/action-gh-release@v1',
|
|
262
|
-
if: "startsWith(github.ref, 'refs/tags/')",
|
|
263
|
-
with: {
|
|
264
|
-
files: 'dist/*'
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
]
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const workflowPath = path.join(this.workflowsDir, 'release.yml');
|
|
273
|
-
await fs.writeFile(workflowPath, this.generateYAML(releaseWorkflow));
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
path: workflowPath,
|
|
277
|
-
name: releaseWorkflow.name,
|
|
278
|
-
trigger: options.trigger
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Applies a template
|
|
284
|
-
*/
|
|
285
|
-
async applyTemplate(templateName) {
|
|
286
|
-
const template = TEMPLATES[templateName];
|
|
287
|
-
|
|
288
|
-
if (!template) {
|
|
289
|
-
throw new Error(`Unknown template: ${templateName}`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
await fs.mkdir(this.workflowsDir, { recursive: true });
|
|
293
|
-
|
|
294
|
-
const filename = `${templateName}.yml`;
|
|
295
|
-
const workflowPath = path.join(this.workflowsDir, filename);
|
|
296
|
-
await fs.writeFile(workflowPath, template.content);
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
path: workflowPath,
|
|
300
|
-
template: templateName,
|
|
301
|
-
name: template.name
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Lists available templates
|
|
307
|
-
*/
|
|
308
|
-
getTemplates() {
|
|
309
|
-
return Object.entries(TEMPLATES).map(([key, template]) => ({
|
|
310
|
-
key,
|
|
311
|
-
name: template.name,
|
|
312
|
-
description: this.getTemplateDescription(key)
|
|
313
|
-
}));
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Lists existing workflows
|
|
318
|
-
*/
|
|
319
|
-
async listWorkflows() {
|
|
320
|
-
try {
|
|
321
|
-
const files = await fs.readdir(this.workflowsDir);
|
|
322
|
-
const workflows = [];
|
|
323
|
-
|
|
324
|
-
for (const file of files) {
|
|
325
|
-
if (CONFIG.fileExtensions.some(ext => file.endsWith(ext))) {
|
|
326
|
-
const content = await fs.readFile(path.join(this.workflowsDir, file), 'utf8');
|
|
327
|
-
workflows.push({
|
|
328
|
-
file,
|
|
329
|
-
name: this.extractWorkflowName(content),
|
|
330
|
-
triggers: this.extractTriggers(content)
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return workflows;
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return [];
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Validates workflows
|
|
343
|
-
*/
|
|
344
|
-
async validateWorkflows() {
|
|
345
|
-
try {
|
|
346
|
-
const files = await fs.readdir(this.workflowsDir);
|
|
347
|
-
const results = [];
|
|
348
|
-
|
|
349
|
-
for (const file of files) {
|
|
350
|
-
if (CONFIG.fileExtensions.some(ext => file.endsWith(ext))) {
|
|
351
|
-
const content = await fs.readFile(path.join(this.workflowsDir, file), 'utf8');
|
|
352
|
-
const issues = this.validateWorkflow(content);
|
|
353
|
-
results.push({
|
|
354
|
-
file,
|
|
355
|
-
valid: issues.length === 0,
|
|
356
|
-
issues
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return results;
|
|
362
|
-
} catch (error) {
|
|
363
|
-
return [];
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Updates a workflow
|
|
369
|
-
*/
|
|
370
|
-
async updateWorkflow(workflowName, updates = {}) {
|
|
371
|
-
const workflowPath = path.join(this.workflowsDir, `${workflowName}.yml`);
|
|
372
|
-
let content = await fs.readFile(workflowPath, 'utf8');
|
|
373
|
-
|
|
374
|
-
// Add job if requested
|
|
375
|
-
if (updates.addJob) {
|
|
376
|
-
const jobName = updates.addJob;
|
|
377
|
-
const jobDefinition = `
|
|
378
|
-
${jobName}:
|
|
379
|
-
runs-on: ubuntu-latest
|
|
380
|
-
steps:
|
|
381
|
-
- uses: actions/checkout@v3
|
|
382
|
-
- name: ${jobName}
|
|
383
|
-
run: echo "Running ${jobName}"`;
|
|
384
|
-
|
|
385
|
-
// Add before the last line
|
|
386
|
-
const lines = content.split('\n');
|
|
387
|
-
const jobsIndex = lines.findIndex(l => l.startsWith('jobs:'));
|
|
388
|
-
if (jobsIndex >= 0) {
|
|
389
|
-
// Find the end of jobs section
|
|
390
|
-
let insertIndex = lines.length;
|
|
391
|
-
for (let i = jobsIndex + 1; i < lines.length; i++) {
|
|
392
|
-
if (!lines[i].startsWith(' ')) {
|
|
393
|
-
insertIndex = i;
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
lines.splice(insertIndex, 0, jobDefinition);
|
|
398
|
-
content = lines.join('\n');
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
await fs.writeFile(workflowPath, content);
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
path: workflowPath,
|
|
406
|
-
updated: true,
|
|
407
|
-
changes: updates
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Validates workflow content
|
|
413
|
-
*/
|
|
414
|
-
validateWorkflow(content) {
|
|
415
|
-
const issues = [];
|
|
416
|
-
const lines = content.split('\n');
|
|
417
|
-
|
|
418
|
-
// Check for required fields
|
|
419
|
-
if (!content.includes('name:')) {
|
|
420
|
-
issues.push('Missing workflow name');
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (!content.includes('on:')) {
|
|
424
|
-
issues.push('Missing trigger events (on:)');
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (!content.includes('jobs:')) {
|
|
428
|
-
issues.push('Missing jobs section');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Check each job has runs-on
|
|
432
|
-
const jobsIndex = lines.findIndex(l => l.trim().startsWith('jobs:'));
|
|
433
|
-
if (jobsIndex >= 0) {
|
|
434
|
-
let currentJob = null;
|
|
435
|
-
let hasRunsOn = false;
|
|
436
|
-
|
|
437
|
-
for (let i = jobsIndex + 1; i < lines.length; i++) {
|
|
438
|
-
const line = lines[i];
|
|
439
|
-
const trimmed = line.trim();
|
|
440
|
-
|
|
441
|
-
// New job starts
|
|
442
|
-
if (line.match(/^ \w+:/)) {
|
|
443
|
-
if (currentJob && !hasRunsOn) {
|
|
444
|
-
issues.push(`Job '${currentJob}' missing runs-on`);
|
|
445
|
-
}
|
|
446
|
-
currentJob = trimmed.replace(':', '');
|
|
447
|
-
hasRunsOn = false;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Check for runs-on
|
|
451
|
-
if (trimmed.startsWith('runs-on:')) {
|
|
452
|
-
hasRunsOn = true;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// End of jobs section
|
|
456
|
-
if (line.length > 0 && !line.startsWith(' ')) {
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Check last job
|
|
462
|
-
if (currentJob && !hasRunsOn) {
|
|
463
|
-
issues.push(`Job '${currentJob}' missing runs-on`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return issues;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Extracts workflow name from content
|
|
472
|
-
*/
|
|
473
|
-
extractWorkflowName(content) {
|
|
474
|
-
const match = content.match(/name:\s*(.+)/);
|
|
475
|
-
return match ? match[1].trim() : 'Unnamed';
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Extracts triggers from workflow content
|
|
480
|
-
*/
|
|
481
|
-
extractTriggers(content) {
|
|
482
|
-
const match = content.match(/on:\s*(.+)/);
|
|
483
|
-
if (!match) return 'None';
|
|
484
|
-
|
|
485
|
-
const triggerLine = match[1].trim();
|
|
486
|
-
if (triggerLine.startsWith('[')) {
|
|
487
|
-
return triggerLine.slice(1, -1);
|
|
488
|
-
}
|
|
489
|
-
return triggerLine;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Gets template description
|
|
494
|
-
*/
|
|
495
|
-
getTemplateDescription(name) {
|
|
496
|
-
const descriptions = {
|
|
497
|
-
node: 'Node.js CI/CD with matrix testing',
|
|
498
|
-
python: 'Python testing with pytest',
|
|
499
|
-
docker: 'Docker build and push to registry'
|
|
500
|
-
};
|
|
501
|
-
return descriptions[name] || 'Workflow template';
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Generates simple YAML
|
|
506
|
-
*/
|
|
507
|
-
generateYAML(obj, indent = 0) {
|
|
508
|
-
let yaml = '';
|
|
509
|
-
const spaces = ' '.repeat(indent);
|
|
510
|
-
|
|
511
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
512
|
-
if (value === null || value === undefined) {
|
|
513
|
-
yaml += `${spaces}${key}:\n`;
|
|
514
|
-
} else if (typeof value === 'string') {
|
|
515
|
-
// Handle multi-line strings
|
|
516
|
-
if (value.includes('\n')) {
|
|
517
|
-
yaml += `${spaces}${key}: |\n`;
|
|
518
|
-
value.split('\n').forEach(line => {
|
|
519
|
-
yaml += `${spaces} ${line}\n`;
|
|
520
|
-
});
|
|
521
|
-
} else {
|
|
522
|
-
yaml += `${spaces}${key}: ${value}\n`;
|
|
523
|
-
}
|
|
524
|
-
} else if (typeof value === 'boolean') {
|
|
525
|
-
yaml += `${spaces}${key}: ${value}\n`;
|
|
526
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
527
|
-
yaml += `${spaces}${key}:\n${this.generateYAML(value, indent + 1)}`;
|
|
528
|
-
} else if (Array.isArray(value)) {
|
|
529
|
-
yaml += `${spaces}${key}:\n`;
|
|
530
|
-
for (const item of value) {
|
|
531
|
-
if (typeof item === 'object') {
|
|
532
|
-
yaml += `${spaces}- ${this.generateYAML(item, indent + 1).trim()}\n`;
|
|
533
|
-
} else {
|
|
534
|
-
yaml += `${spaces} - ${item}\n`;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
} else {
|
|
538
|
-
yaml += `${spaces}${key}: ${value}\n`;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return yaml;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
module.exports = GitHubWorkflowManager;
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Azure DevOps Batch API Helper
|
|
4
|
-
* Optimizes API calls by batching multiple requests
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const https = require('https');
|
|
8
|
-
|
|
9
|
-
class AzureBatchAPI {
|
|
10
|
-
constructor(org, project, pat) {
|
|
11
|
-
this.org = org;
|
|
12
|
-
this.project = project;
|
|
13
|
-
this.pat = pat;
|
|
14
|
-
this.baseUrl = `dev.azure.com/${org}/${project}/_apis`;
|
|
15
|
-
this.batchSize = 200; // Azure DevOps batch limit
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Execute batch API requests
|
|
20
|
-
* @param {Array} requests - Array of request objects
|
|
21
|
-
* @returns {Promise<Array>} Array of responses
|
|
22
|
-
*/
|
|
23
|
-
async executeBatch(requests) {
|
|
24
|
-
if (!requests || requests.length === 0) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Split into chunks if needed
|
|
29
|
-
const chunks = this.chunkRequests(requests, this.batchSize);
|
|
30
|
-
const results = [];
|
|
31
|
-
|
|
32
|
-
for (const chunk of chunks) {
|
|
33
|
-
const batchResults = await this.executeBatchChunk(chunk);
|
|
34
|
-
results.push(...batchResults);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return results;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Execute a single batch chunk
|
|
42
|
-
* @private
|
|
43
|
-
*/
|
|
44
|
-
async executeBatchChunk(requests) {
|
|
45
|
-
const batchRequest = {
|
|
46
|
-
requests: requests.map((req, index) => ({
|
|
47
|
-
id: index.toString(),
|
|
48
|
-
method: req.method || 'GET',
|
|
49
|
-
url: `/${this.baseUrl}/${req.endpoint}?api-version=7.0`,
|
|
50
|
-
headers: req.headers || {},
|
|
51
|
-
body: req.body
|
|
52
|
-
}))
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return new Promise((resolve, reject) => {
|
|
56
|
-
const options = {
|
|
57
|
-
hostname: 'dev.azure.com',
|
|
58
|
-
path: `/${this.org}/_apis/$batch?api-version=7.0`,
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: {
|
|
61
|
-
'Authorization': `Basic ${Buffer.from(`:${this.pat}`).toString('base64')}`,
|
|
62
|
-
'Content-Type': 'application/json'
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const req = https.request(options, (res) => {
|
|
67
|
-
let data = '';
|
|
68
|
-
res.on('data', chunk => data += chunk);
|
|
69
|
-
res.on('end', () => {
|
|
70
|
-
try {
|
|
71
|
-
const batchResponse = JSON.parse(data);
|
|
72
|
-
const results = batchResponse.responses.map(r => {
|
|
73
|
-
try {
|
|
74
|
-
return JSON.parse(r.body);
|
|
75
|
-
} catch {
|
|
76
|
-
return r.body;
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
resolve(results);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
reject(error);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
req.on('error', reject);
|
|
87
|
-
req.write(JSON.stringify(batchRequest));
|
|
88
|
-
req.end();
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Chunk requests into batches
|
|
94
|
-
* @private
|
|
95
|
-
*/
|
|
96
|
-
chunkRequests(requests, size) {
|
|
97
|
-
const chunks = [];
|
|
98
|
-
for (let i = 0; i < requests.length; i += size) {
|
|
99
|
-
chunks.push(requests.slice(i, i + size));
|
|
100
|
-
}
|
|
101
|
-
return chunks;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Batch fetch work items
|
|
106
|
-
*/
|
|
107
|
-
async batchFetchWorkItems(ids) {
|
|
108
|
-
const requests = ids.map(id => ({
|
|
109
|
-
endpoint: `wit/workitems/${id}`,
|
|
110
|
-
method: 'GET'
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
|
-
return this.executeBatch(requests);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Batch update work items
|
|
118
|
-
*/
|
|
119
|
-
async batchUpdateWorkItems(updates) {
|
|
120
|
-
const requests = updates.map(update => ({
|
|
121
|
-
endpoint: `wit/workitems/${update.id}`,
|
|
122
|
-
method: 'PATCH',
|
|
123
|
-
headers: {
|
|
124
|
-
'Content-Type': 'application/json-patch+json'
|
|
125
|
-
},
|
|
126
|
-
body: update.operations
|
|
127
|
-
}));
|
|
128
|
-
|
|
129
|
-
return this.executeBatch(requests);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = AzureBatchAPI;
|