@itz4blitz/agentful 1.2.0 → 1.3.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 +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import yaml from 'js-yaml';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* CI/CD Integration Adapters
|
|
7
|
-
*
|
|
8
|
-
* Provides adapters for various CI/CD platforms:
|
|
9
|
-
* - GitHub Actions
|
|
10
|
-
* - GitLab CI
|
|
11
|
-
* - Jenkins
|
|
12
|
-
* - Webhook triggers
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* GitHub Actions Integration
|
|
17
|
-
*
|
|
18
|
-
* Converts agentful pipelines to GitHub Actions workflows
|
|
19
|
-
*/
|
|
20
|
-
export class GitHubActionsAdapter {
|
|
21
|
-
/**
|
|
22
|
-
* Convert pipeline to GitHub Actions workflow
|
|
23
|
-
*
|
|
24
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
25
|
-
* @returns {Object} GitHub Actions workflow YAML
|
|
26
|
-
*/
|
|
27
|
-
static convertToWorkflow(pipeline) {
|
|
28
|
-
const workflow = {
|
|
29
|
-
name: pipeline.name,
|
|
30
|
-
on: this._convertTriggers(pipeline.triggers),
|
|
31
|
-
env: pipeline.env || {},
|
|
32
|
-
jobs: {}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Convert each job
|
|
36
|
-
for (const job of pipeline.jobs) {
|
|
37
|
-
const ghJob = {
|
|
38
|
-
'runs-on': job.runsOn || 'ubuntu-latest',
|
|
39
|
-
steps: this._convertJobSteps(job)
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Add dependencies
|
|
43
|
-
if (job.dependsOn) {
|
|
44
|
-
ghJob.needs = Array.isArray(job.dependsOn) ? job.dependsOn : [job.dependsOn];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Add conditionals
|
|
48
|
-
if (job.when) {
|
|
49
|
-
ghJob.if = this._convertCondition(job.when);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Add timeout
|
|
53
|
-
if (job.timeout) {
|
|
54
|
-
ghJob['timeout-minutes'] = Math.ceil(job.timeout / 60000);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
workflow.jobs[job.id] = ghJob;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return workflow;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Internal: Convert pipeline triggers to GitHub Actions triggers
|
|
65
|
-
*
|
|
66
|
-
* @private
|
|
67
|
-
*/
|
|
68
|
-
static _convertTriggers(triggers) {
|
|
69
|
-
if (!triggers) {
|
|
70
|
-
return { workflow_dispatch: {} }; // Manual trigger only
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const ghTriggers = {};
|
|
74
|
-
|
|
75
|
-
for (const trigger of triggers) {
|
|
76
|
-
if (trigger.type === 'push') {
|
|
77
|
-
ghTriggers.push = {
|
|
78
|
-
branches: trigger.branches || ['main']
|
|
79
|
-
};
|
|
80
|
-
} else if (trigger.type === 'pull_request') {
|
|
81
|
-
ghTriggers.pull_request = {
|
|
82
|
-
branches: trigger.branches || ['main']
|
|
83
|
-
};
|
|
84
|
-
} else if (trigger.type === 'schedule') {
|
|
85
|
-
ghTriggers.schedule = [{ cron: trigger.cron }];
|
|
86
|
-
} else if (trigger.type === 'manual') {
|
|
87
|
-
ghTriggers.workflow_dispatch = {};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return ghTriggers;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Internal: Convert job steps
|
|
96
|
-
*
|
|
97
|
-
* @private
|
|
98
|
-
*/
|
|
99
|
-
static _convertJobSteps(job) {
|
|
100
|
-
const steps = [
|
|
101
|
-
{
|
|
102
|
-
name: 'Checkout code',
|
|
103
|
-
uses: 'actions/checkout@v4'
|
|
104
|
-
}
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
// Add setup steps
|
|
108
|
-
if (job.setup) {
|
|
109
|
-
for (const setupStep of job.setup) {
|
|
110
|
-
steps.push({
|
|
111
|
-
name: setupStep.name,
|
|
112
|
-
run: setupStep.command
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Add main agent execution step
|
|
118
|
-
steps.push({
|
|
119
|
-
name: `Execute ${job.name}`,
|
|
120
|
-
run: this._buildAgentExecutionCommand(job),
|
|
121
|
-
env: {
|
|
122
|
-
AGENTFUL_AGENT: job.agent,
|
|
123
|
-
AGENTFUL_CONTEXT: '${{ toJson(github) }}'
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
return steps;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Internal: Build agent execution command
|
|
132
|
-
*
|
|
133
|
-
* @private
|
|
134
|
-
*/
|
|
135
|
-
static _buildAgentExecutionCommand(job) {
|
|
136
|
-
return `
|
|
137
|
-
npx agentful pipeline run \\
|
|
138
|
-
--job ${job.id} \\
|
|
139
|
-
--context-file context.json
|
|
140
|
-
`.trim();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Internal: Convert condition to GitHub Actions format
|
|
145
|
-
*
|
|
146
|
-
* @private
|
|
147
|
-
*/
|
|
148
|
-
static _convertCondition(condition) {
|
|
149
|
-
// Simple mapping - would need more sophisticated conversion
|
|
150
|
-
return condition
|
|
151
|
-
.replace(/status\s*==\s*'completed'/, "success()")
|
|
152
|
-
.replace(/status\s*==\s*'failed'/, "failure()");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Write GitHub Actions workflow file
|
|
157
|
-
*
|
|
158
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
159
|
-
* @param {string} outputPath - Path to write workflow file
|
|
160
|
-
*/
|
|
161
|
-
static async writeWorkflowFile(pipeline, outputPath) {
|
|
162
|
-
const workflow = this.convertToWorkflow(pipeline);
|
|
163
|
-
const yamlContent = yaml.dump(workflow, { lineWidth: 120 });
|
|
164
|
-
|
|
165
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
166
|
-
await fs.writeFile(outputPath, yamlContent);
|
|
167
|
-
|
|
168
|
-
return outputPath;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* GitLab CI Integration
|
|
174
|
-
*
|
|
175
|
-
* Converts agentful pipelines to GitLab CI configuration
|
|
176
|
-
*/
|
|
177
|
-
export class GitLabCIAdapter {
|
|
178
|
-
/**
|
|
179
|
-
* Convert pipeline to GitLab CI configuration
|
|
180
|
-
*
|
|
181
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
182
|
-
* @returns {Object} GitLab CI YAML
|
|
183
|
-
*/
|
|
184
|
-
static convertToConfig(pipeline) {
|
|
185
|
-
const config = {
|
|
186
|
-
stages: this._extractStages(pipeline.jobs),
|
|
187
|
-
variables: pipeline.env || {}
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Convert each job
|
|
191
|
-
for (const job of pipeline.jobs) {
|
|
192
|
-
const glJob = {
|
|
193
|
-
stage: job.stage || 'test',
|
|
194
|
-
script: this._convertJobScript(job)
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// Add dependencies
|
|
198
|
-
if (job.dependsOn) {
|
|
199
|
-
glJob.needs = Array.isArray(job.dependsOn)
|
|
200
|
-
? job.dependsOn.map(dep => ({ job: dep }))
|
|
201
|
-
: [{ job: job.dependsOn }];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Add rules (conditionals)
|
|
205
|
-
if (job.when) {
|
|
206
|
-
glJob.rules = [{ if: this._convertCondition(job.when) }];
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Add timeout
|
|
210
|
-
if (job.timeout) {
|
|
211
|
-
glJob.timeout = `${Math.ceil(job.timeout / 60)}m`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Add retry policy
|
|
215
|
-
if (job.retry) {
|
|
216
|
-
glJob.retry = {
|
|
217
|
-
max: job.retry.maxAttempts || 2,
|
|
218
|
-
when: ['runner_system_failure', 'stuck_or_timeout_failure']
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
config[job.id] = glJob;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return config;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Internal: Extract stages from jobs
|
|
230
|
-
*
|
|
231
|
-
* @private
|
|
232
|
-
*/
|
|
233
|
-
static _extractStages(jobs) {
|
|
234
|
-
const stages = new Set(['build', 'test', 'deploy']);
|
|
235
|
-
|
|
236
|
-
for (const job of jobs) {
|
|
237
|
-
if (job.stage) {
|
|
238
|
-
stages.add(job.stage);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return Array.from(stages);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Internal: Convert job script
|
|
247
|
-
*
|
|
248
|
-
* @private
|
|
249
|
-
*/
|
|
250
|
-
static _convertJobScript(job) {
|
|
251
|
-
const script = [];
|
|
252
|
-
|
|
253
|
-
// Setup commands
|
|
254
|
-
if (job.setup) {
|
|
255
|
-
for (const setupStep of job.setup) {
|
|
256
|
-
script.push(setupStep.command);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Main agent execution
|
|
261
|
-
script.push(
|
|
262
|
-
`npx agentful pipeline run --job ${job.id} --agent ${job.agent}`
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
return script;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Internal: Convert condition to GitLab CI format
|
|
270
|
-
*
|
|
271
|
-
* @private
|
|
272
|
-
*/
|
|
273
|
-
static _convertCondition(condition) {
|
|
274
|
-
// GitLab CI uses different syntax
|
|
275
|
-
return condition
|
|
276
|
-
.replace(/status\s*==\s*'completed'/, '$CI_JOB_STATUS == "success"')
|
|
277
|
-
.replace(/status\s*==\s*'failed'/, '$CI_JOB_STATUS == "failed"');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Write GitLab CI configuration file
|
|
282
|
-
*
|
|
283
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
284
|
-
* @param {string} outputPath - Path to write config file
|
|
285
|
-
*/
|
|
286
|
-
static async writeConfigFile(pipeline, outputPath) {
|
|
287
|
-
const config = this.convertToConfig(pipeline);
|
|
288
|
-
const yamlContent = yaml.dump(config, { lineWidth: 120 });
|
|
289
|
-
|
|
290
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
291
|
-
await fs.writeFile(outputPath, yamlContent);
|
|
292
|
-
|
|
293
|
-
return outputPath;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Jenkins Integration
|
|
299
|
-
*
|
|
300
|
-
* Converts agentful pipelines to Jenkins pipeline syntax
|
|
301
|
-
*/
|
|
302
|
-
export class JenkinsAdapter {
|
|
303
|
-
/**
|
|
304
|
-
* Convert pipeline to Jenkinsfile
|
|
305
|
-
*
|
|
306
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
307
|
-
* @returns {string} Jenkinsfile content
|
|
308
|
-
*/
|
|
309
|
-
static convertToJenkinsfile(pipeline) {
|
|
310
|
-
const stages = this._convertJobsToStages(pipeline.jobs);
|
|
311
|
-
|
|
312
|
-
return `
|
|
313
|
-
pipeline {
|
|
314
|
-
agent any
|
|
315
|
-
|
|
316
|
-
environment {
|
|
317
|
-
${this._formatEnvironment(pipeline.env || {})}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
stages {
|
|
321
|
-
${stages}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
post {
|
|
325
|
-
always {
|
|
326
|
-
echo 'Pipeline completed'
|
|
327
|
-
}
|
|
328
|
-
success {
|
|
329
|
-
echo 'Pipeline succeeded'
|
|
330
|
-
}
|
|
331
|
-
failure {
|
|
332
|
-
echo 'Pipeline failed'
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
`.trim();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Internal: Convert jobs to Jenkins stages
|
|
341
|
-
*
|
|
342
|
-
* @private
|
|
343
|
-
*/
|
|
344
|
-
static _convertJobsToStages(jobs) {
|
|
345
|
-
const stages = [];
|
|
346
|
-
|
|
347
|
-
for (const job of jobs) {
|
|
348
|
-
const stage = `
|
|
349
|
-
stage('${job.name || job.id}') {
|
|
350
|
-
steps {
|
|
351
|
-
${this._formatSteps(job)}
|
|
352
|
-
}
|
|
353
|
-
}`;
|
|
354
|
-
stages.push(stage);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return stages.join('\n');
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Internal: Format environment variables
|
|
362
|
-
*
|
|
363
|
-
* @private
|
|
364
|
-
*/
|
|
365
|
-
static _formatEnvironment(env) {
|
|
366
|
-
return Object.entries(env)
|
|
367
|
-
.map(([key, value]) => ` ${key} = '${value}'`)
|
|
368
|
-
.join('\n');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Internal: Format job steps
|
|
373
|
-
*
|
|
374
|
-
* @private
|
|
375
|
-
*/
|
|
376
|
-
static _formatSteps(job) {
|
|
377
|
-
const steps = [];
|
|
378
|
-
|
|
379
|
-
// Setup steps
|
|
380
|
-
if (job.setup) {
|
|
381
|
-
for (const setupStep of job.setup) {
|
|
382
|
-
steps.push(` sh '${setupStep.command}'`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Main agent execution
|
|
387
|
-
steps.push(` sh 'npx agentful pipeline run --job ${job.id} --agent ${job.agent}'`);
|
|
388
|
-
|
|
389
|
-
return steps.join('\n');
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Write Jenkinsfile
|
|
394
|
-
*
|
|
395
|
-
* @param {Object} pipeline - Agentful pipeline definition
|
|
396
|
-
* @param {string} outputPath - Path to write Jenkinsfile
|
|
397
|
-
*/
|
|
398
|
-
static async writeJenkinsfile(pipeline, outputPath) {
|
|
399
|
-
const content = this.convertToJenkinsfile(pipeline);
|
|
400
|
-
|
|
401
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
402
|
-
await fs.writeFile(outputPath, content);
|
|
403
|
-
|
|
404
|
-
return outputPath;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Webhook Handler
|
|
410
|
-
*
|
|
411
|
-
* Handles webhook triggers for pipelines
|
|
412
|
-
*/
|
|
413
|
-
export class WebhookHandler {
|
|
414
|
-
constructor(pipelineEngine) {
|
|
415
|
-
this.pipelineEngine = pipelineEngine;
|
|
416
|
-
this.webhooks = new Map(); // webhookId -> WebhookConfig
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Register webhook trigger
|
|
421
|
-
*
|
|
422
|
-
* @param {Object} config - Webhook configuration
|
|
423
|
-
* @returns {string} Webhook ID
|
|
424
|
-
*/
|
|
425
|
-
registerWebhook(config) {
|
|
426
|
-
const webhookId = this._generateWebhookId();
|
|
427
|
-
|
|
428
|
-
this.webhooks.set(webhookId, {
|
|
429
|
-
id: webhookId,
|
|
430
|
-
pipeline: config.pipeline,
|
|
431
|
-
secret: config.secret,
|
|
432
|
-
filters: config.filters || {},
|
|
433
|
-
transform: config.transform || null,
|
|
434
|
-
createdAt: new Date().toISOString()
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
return webhookId;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Handle webhook request
|
|
442
|
-
*
|
|
443
|
-
* @param {string} webhookId - Webhook ID
|
|
444
|
-
* @param {Object} payload - Webhook payload
|
|
445
|
-
* @param {Object} headers - Request headers
|
|
446
|
-
* @returns {Promise<Object>} Result
|
|
447
|
-
*/
|
|
448
|
-
async handleWebhook(webhookId, payload, headers = {}) {
|
|
449
|
-
const webhook = this.webhooks.get(webhookId);
|
|
450
|
-
if (!webhook) {
|
|
451
|
-
throw new Error(`Webhook not found: ${webhookId}`);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Verify signature if secret is set
|
|
455
|
-
if (webhook.secret) {
|
|
456
|
-
this._verifySignature(webhook.secret, payload, headers);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Apply filters
|
|
460
|
-
if (!this._matchesFilters(payload, webhook.filters)) {
|
|
461
|
-
return {
|
|
462
|
-
success: true,
|
|
463
|
-
message: 'Webhook ignored due to filters',
|
|
464
|
-
triggered: false
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Transform payload to context
|
|
469
|
-
const context = webhook.transform
|
|
470
|
-
? webhook.transform(payload)
|
|
471
|
-
: { webhook: payload };
|
|
472
|
-
|
|
473
|
-
// Trigger pipeline
|
|
474
|
-
const runId = await this.pipelineEngine.startPipeline(webhook.pipeline, {
|
|
475
|
-
...context,
|
|
476
|
-
triggeredBy: 'webhook',
|
|
477
|
-
webhookId
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
success: true,
|
|
482
|
-
message: 'Pipeline triggered',
|
|
483
|
-
triggered: true,
|
|
484
|
-
runId
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Internal: Verify webhook signature
|
|
490
|
-
*
|
|
491
|
-
* @private
|
|
492
|
-
*/
|
|
493
|
-
_verifySignature(secret, payload, headers) {
|
|
494
|
-
// Implementation depends on the webhook provider
|
|
495
|
-
// GitHub, GitLab, etc. have different signature schemes
|
|
496
|
-
|
|
497
|
-
// For GitHub:
|
|
498
|
-
// const signature = headers['x-hub-signature-256'];
|
|
499
|
-
// const expectedSignature = crypto
|
|
500
|
-
// .createHmac('sha256', secret)
|
|
501
|
-
// .update(JSON.stringify(payload))
|
|
502
|
-
// .digest('hex');
|
|
503
|
-
|
|
504
|
-
// For now, just check if signature header exists
|
|
505
|
-
const signature = headers['x-webhook-signature'] || headers['x-hub-signature-256'];
|
|
506
|
-
if (!signature) {
|
|
507
|
-
throw new Error('Missing webhook signature');
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Internal: Check if payload matches filters
|
|
513
|
-
*
|
|
514
|
-
* @private
|
|
515
|
-
*/
|
|
516
|
-
_matchesFilters(payload, filters) {
|
|
517
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
518
|
-
const payloadValue = this._getNestedValue(payload, key);
|
|
519
|
-
|
|
520
|
-
if (Array.isArray(value)) {
|
|
521
|
-
if (!value.includes(payloadValue)) return false;
|
|
522
|
-
} else if (payloadValue !== value) {
|
|
523
|
-
return false;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return true;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Internal: Get nested value from object
|
|
532
|
-
*
|
|
533
|
-
* @private
|
|
534
|
-
*/
|
|
535
|
-
_getNestedValue(obj, path) {
|
|
536
|
-
return path.split('.').reduce((current, prop) => {
|
|
537
|
-
return current && current[prop] !== undefined ? current[prop] : undefined;
|
|
538
|
-
}, obj);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Internal: Generate webhook ID
|
|
543
|
-
*
|
|
544
|
-
* @private
|
|
545
|
-
*/
|
|
546
|
-
_generateWebhookId() {
|
|
547
|
-
return `webhook-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Export all adapters
|
|
553
|
-
*/
|
|
554
|
-
export default {
|
|
555
|
-
GitHubActionsAdapter,
|
|
556
|
-
GitLabCIAdapter,
|
|
557
|
-
JenkinsAdapter,
|
|
558
|
-
WebhookHandler
|
|
559
|
-
};
|