@generacy-ai/generacy-plugin-github-issues 0.0.0-preview-20260304013206
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/LICENSE +191 -0
- package/dist/auth/auth-factory.d.ts +44 -0
- package/dist/auth/auth-factory.d.ts.map +1 -0
- package/dist/auth/auth-factory.js +85 -0
- package/dist/auth/auth-factory.js.map +1 -0
- package/dist/auth/env.d.ts +32 -0
- package/dist/auth/env.d.ts.map +1 -0
- package/dist/auth/env.js +88 -0
- package/dist/auth/env.js.map +1 -0
- package/dist/auth/github-app.d.ts +69 -0
- package/dist/auth/github-app.d.ts.map +1 -0
- package/dist/auth/github-app.js +191 -0
- package/dist/auth/github-app.js.map +1 -0
- package/dist/auth/index.d.ts +10 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +10 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token-cache.d.ts +70 -0
- package/dist/auth/token-cache.d.ts.map +1 -0
- package/dist/auth/token-cache.js +112 -0
- package/dist/auth/token-cache.js.map +1 -0
- package/dist/auth/types.d.ts +119 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +27 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/client.d.ts +101 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +197 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/comments.d.ts +46 -0
- package/dist/operations/comments.d.ts.map +1 -0
- package/dist/operations/comments.js +127 -0
- package/dist/operations/comments.js.map +1 -0
- package/dist/operations/issues.d.ts +46 -0
- package/dist/operations/issues.d.ts.map +1 -0
- package/dist/operations/issues.js +188 -0
- package/dist/operations/issues.js.map +1 -0
- package/dist/operations/labels.d.ts +46 -0
- package/dist/operations/labels.d.ts.map +1 -0
- package/dist/operations/labels.js +132 -0
- package/dist/operations/labels.js.map +1 -0
- package/dist/operations/pull-requests.d.ts +41 -0
- package/dist/operations/pull-requests.d.ts.map +1 -0
- package/dist/operations/pull-requests.js +162 -0
- package/dist/operations/pull-requests.js.map +1 -0
- package/dist/plugin.d.ts +181 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +324 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types/config.d.ts +126 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +22 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +72 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +2 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/issues.d.ts +178 -0
- package/dist/types/issues.d.ts.map +1 -0
- package/dist/types/issues.js +29 -0
- package/dist/types/issues.js.map +1 -0
- package/dist/types/responses.d.ts +61 -0
- package/dist/types/responses.d.ts.map +1 -0
- package/dist/types/responses.js +5 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/errors.d.ts +69 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +123 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/validation.d.ts +33 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +69 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/webhooks/handler.d.ts +66 -0
- package/dist/webhooks/handler.d.ts.map +1 -0
- package/dist/webhooks/handler.js +176 -0
- package/dist/webhooks/handler.js.map +1 -0
- package/dist/webhooks/parser.d.ts +36 -0
- package/dist/webhooks/parser.d.ts.map +1 -0
- package/dist/webhooks/parser.js +220 -0
- package/dist/webhooks/parser.js.map +1 -0
- package/dist/webhooks/triggers.d.ts +25 -0
- package/dist/webhooks/triggers.d.ts.map +1 -0
- package/dist/webhooks/triggers.js +119 -0
- package/dist/webhooks/triggers.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GitHubValidationError } from './errors.js';
|
|
2
|
+
import { GitHubIssuesConfigSchema, CreateIssueParamsSchema, UpdateIssueParamsSchema, IssueFilterSchema, } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Format Zod errors into a structured details object
|
|
5
|
+
*/
|
|
6
|
+
function formatZodErrors(error) {
|
|
7
|
+
const details = {};
|
|
8
|
+
for (const issue of error.issues) {
|
|
9
|
+
const path = issue.path.join('.') || '_root';
|
|
10
|
+
if (!details[path]) {
|
|
11
|
+
details[path] = [];
|
|
12
|
+
}
|
|
13
|
+
details[path].push(issue.message);
|
|
14
|
+
}
|
|
15
|
+
return details;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate data against a Zod schema
|
|
19
|
+
* @throws GitHubValidationError if validation fails
|
|
20
|
+
*/
|
|
21
|
+
export function validate(schema, data, context) {
|
|
22
|
+
const result = schema.safeParse(data);
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
const details = formatZodErrors(result.error);
|
|
25
|
+
const prefix = context ? `${context}: ` : '';
|
|
26
|
+
const message = `${prefix}Validation failed`;
|
|
27
|
+
throw new GitHubValidationError(message, details, result.error);
|
|
28
|
+
}
|
|
29
|
+
return result.data;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate configuration
|
|
33
|
+
*/
|
|
34
|
+
export function validateConfig(config) {
|
|
35
|
+
return validate(GitHubIssuesConfigSchema, config, 'Configuration');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate create issue parameters
|
|
39
|
+
*/
|
|
40
|
+
export function validateCreateIssueParams(params) {
|
|
41
|
+
return validate(CreateIssueParamsSchema, params, 'CreateIssueParams');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate update issue parameters
|
|
45
|
+
*/
|
|
46
|
+
export function validateUpdateIssueParams(params) {
|
|
47
|
+
return validate(UpdateIssueParamsSchema, params, 'UpdateIssueParams');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate issue filter parameters
|
|
51
|
+
*/
|
|
52
|
+
export function validateIssueFilter(filter) {
|
|
53
|
+
return validate(IssueFilterSchema, filter, 'IssueFilter');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a value is a non-empty string
|
|
57
|
+
*/
|
|
58
|
+
export function isNonEmptyString(value) {
|
|
59
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a value is a positive integer
|
|
63
|
+
*/
|
|
64
|
+
export function isPositiveInteger(value) {
|
|
65
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
66
|
+
}
|
|
67
|
+
// Re-export schemas for direct use
|
|
68
|
+
export { GitHubIssuesConfigSchema, CreateIssueParamsSchema, UpdateIssueParamsSchema, IssueFilterSchema, };
|
|
69
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,SAAS,eAAe,CAAC,KAAe;IACtC,MAAM,OAAO,GAA6B,EAAE,CAAC;IAE7C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAI,MAAoB,EAAE,IAAa,EAAE,OAAgB;IAC/E,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,GAAG,MAAM,mBAAmB,CAAC;QAC7C,MAAM,IAAI,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,OAAO,QAAQ,CAAC,wBAAwB,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;AACrE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAe;IAEf,OAAO,QAAQ,CAAC,uBAAuB,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAe;IAEf,OAAO,QAAQ,CAAC,uBAAuB,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,OAAO,QAAQ,CAAC,iBAAiB,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,mCAAmC;AACnC,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,GAClB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { TypedWebhookEvent, WorkflowAction } from '../types/index.js';
|
|
2
|
+
import { type TriggerConfig } from './triggers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Webhook handler configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface WebhookHandlerConfig extends TriggerConfig {
|
|
7
|
+
/** Webhook secret for signature verification */
|
|
8
|
+
webhookSecret?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Webhook delivery headers
|
|
12
|
+
*/
|
|
13
|
+
export interface WebhookHeaders {
|
|
14
|
+
/** Event type (X-GitHub-Event) */
|
|
15
|
+
'x-github-event'?: string;
|
|
16
|
+
/** Delivery ID (X-GitHub-Delivery) */
|
|
17
|
+
'x-github-delivery'?: string;
|
|
18
|
+
/** HMAC signature (X-Hub-Signature-256) */
|
|
19
|
+
'x-hub-signature-256'?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Result of webhook processing
|
|
23
|
+
*/
|
|
24
|
+
export interface WebhookResult {
|
|
25
|
+
/** Whether the webhook was processed successfully */
|
|
26
|
+
success: boolean;
|
|
27
|
+
/** The resulting workflow action */
|
|
28
|
+
action: WorkflowAction;
|
|
29
|
+
/** The parsed event (if successful) */
|
|
30
|
+
event?: TypedWebhookEvent;
|
|
31
|
+
/** Error message (if failed) */
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Webhook event handler
|
|
36
|
+
*/
|
|
37
|
+
export declare class WebhookHandler {
|
|
38
|
+
private readonly config;
|
|
39
|
+
constructor(config: WebhookHandlerConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Verify the webhook signature
|
|
42
|
+
* @throws WebhookVerificationError if verification fails
|
|
43
|
+
*/
|
|
44
|
+
verifySignature(payload: string | Buffer, signature: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Process a raw webhook delivery
|
|
47
|
+
*/
|
|
48
|
+
processRaw(headers: WebhookHeaders, body: string | Buffer, rawPayload?: unknown): Promise<WebhookResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Process a pre-parsed webhook event
|
|
51
|
+
*/
|
|
52
|
+
processEvent(event: TypedWebhookEvent): Promise<WebhookResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Handle a webhook delivery (convenience method)
|
|
55
|
+
* Returns the workflow action or null if no action needed
|
|
56
|
+
*/
|
|
57
|
+
handle(eventName: string, payload: unknown, deliveryId?: string): Promise<WorkflowAction>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a webhook handler instance
|
|
61
|
+
*/
|
|
62
|
+
export declare function createWebhookHandler(config: WebhookHandlerConfig): WebhookHandler;
|
|
63
|
+
export { parseWebhookEvent, isSupportedEvent } from './parser.js';
|
|
64
|
+
export { evaluateTriggers, requiresProcessing, getActionIssueNumber } from './triggers.js';
|
|
65
|
+
export type { TriggerConfig } from './triggers.js';
|
|
66
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/webhooks/handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAG3E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,sCAAsC;IACtC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,MAAM,EAAE,cAAc,CAAC;IAEvB,uCAAuC;IACvC,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAE1B,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwBD;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;gBAElC,MAAM,EAAE,oBAAoB;IAIxC;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAelE;;OAEG;IACG,UAAU,CACd,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,CAAC,aAAa,CAAC;IAiFzB;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBpE;;;OAGG;IACG,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC;CAe3B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAEjF;AAGD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { WebhookVerificationError, GitHubValidationError } from '../utils/errors.js';
|
|
3
|
+
import { parseWebhookEvent, isSupportedEvent } from './parser.js';
|
|
4
|
+
import { evaluateTriggers } from './triggers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Verify webhook signature using HMAC SHA-256
|
|
7
|
+
*/
|
|
8
|
+
function verifySignature(payload, signature, secret) {
|
|
9
|
+
const expected = `sha256=${createHmac('sha256', secret)
|
|
10
|
+
.update(payload)
|
|
11
|
+
.digest('hex')}`;
|
|
12
|
+
const expectedBuffer = Buffer.from(expected);
|
|
13
|
+
const signatureBuffer = Buffer.from(signature);
|
|
14
|
+
if (expectedBuffer.length !== signatureBuffer.length) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return timingSafeEqual(expectedBuffer, signatureBuffer);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Webhook event handler
|
|
21
|
+
*/
|
|
22
|
+
export class WebhookHandler {
|
|
23
|
+
config;
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify the webhook signature
|
|
29
|
+
* @throws WebhookVerificationError if verification fails
|
|
30
|
+
*/
|
|
31
|
+
verifySignature(payload, signature) {
|
|
32
|
+
if (!this.config.webhookSecret) {
|
|
33
|
+
// No secret configured, skip verification
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!signature) {
|
|
37
|
+
throw new WebhookVerificationError('Missing webhook signature');
|
|
38
|
+
}
|
|
39
|
+
if (!verifySignature(payload, signature, this.config.webhookSecret)) {
|
|
40
|
+
throw new WebhookVerificationError('Invalid webhook signature');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Process a raw webhook delivery
|
|
45
|
+
*/
|
|
46
|
+
async processRaw(headers, body, rawPayload) {
|
|
47
|
+
const eventName = headers['x-github-event'];
|
|
48
|
+
const deliveryId = headers['x-github-delivery'];
|
|
49
|
+
const signature = headers['x-hub-signature-256'];
|
|
50
|
+
// Verify signature if configured
|
|
51
|
+
if (this.config.webhookSecret && signature) {
|
|
52
|
+
try {
|
|
53
|
+
this.verifySignature(body, signature);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
action: { type: 'no_action', reason: 'Signature verification failed' },
|
|
59
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Validate event type
|
|
64
|
+
if (!eventName) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
action: { type: 'no_action', reason: 'Missing event type header' },
|
|
68
|
+
error: 'Missing X-GitHub-Event header',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Check if event is supported
|
|
72
|
+
if (!isSupportedEvent(eventName)) {
|
|
73
|
+
return {
|
|
74
|
+
success: true, // Not an error, just not handled
|
|
75
|
+
action: { type: 'no_action', reason: `Unsupported event type: ${eventName}` },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Parse the payload
|
|
79
|
+
let payload;
|
|
80
|
+
if (rawPayload !== undefined) {
|
|
81
|
+
payload = rawPayload;
|
|
82
|
+
}
|
|
83
|
+
else if (typeof body === 'string') {
|
|
84
|
+
try {
|
|
85
|
+
payload = JSON.parse(body);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
action: { type: 'no_action', reason: 'Invalid JSON payload' },
|
|
91
|
+
error: 'Failed to parse webhook payload as JSON',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
try {
|
|
97
|
+
payload = JSON.parse(body.toString('utf-8'));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
action: { type: 'no_action', reason: 'Invalid JSON payload' },
|
|
103
|
+
error: 'Failed to parse webhook payload as JSON',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Parse and evaluate the event
|
|
108
|
+
try {
|
|
109
|
+
const event = parseWebhookEvent(eventName, payload, deliveryId);
|
|
110
|
+
const action = evaluateTriggers(event, this.config);
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
action,
|
|
114
|
+
event,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
action: { type: 'no_action', reason: `Failed to process event: ${message}` },
|
|
122
|
+
error: message,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Process a pre-parsed webhook event
|
|
128
|
+
*/
|
|
129
|
+
async processEvent(event) {
|
|
130
|
+
try {
|
|
131
|
+
const action = evaluateTriggers(event, this.config);
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
action,
|
|
135
|
+
event,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
action: { type: 'no_action', reason: `Failed to process event: ${message}` },
|
|
143
|
+
error: message,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Handle a webhook delivery (convenience method)
|
|
149
|
+
* Returns the workflow action or null if no action needed
|
|
150
|
+
*/
|
|
151
|
+
async handle(eventName, payload, deliveryId) {
|
|
152
|
+
if (!isSupportedEvent(eventName)) {
|
|
153
|
+
return { type: 'no_action', reason: `Unsupported event type: ${eventName}` };
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const event = parseWebhookEvent(eventName, payload, deliveryId);
|
|
157
|
+
return evaluateTriggers(event, this.config);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error instanceof GitHubValidationError) {
|
|
161
|
+
return { type: 'no_action', reason: error.message };
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create a webhook handler instance
|
|
169
|
+
*/
|
|
170
|
+
export function createWebhookHandler(config) {
|
|
171
|
+
return new WebhookHandler(config);
|
|
172
|
+
}
|
|
173
|
+
// Re-export types and utilities for convenience
|
|
174
|
+
export { parseWebhookEvent, isSupportedEvent } from './parser.js';
|
|
175
|
+
export { evaluateTriggers, requiresProcessing, getActionIssueNumber } from './triggers.js';
|
|
176
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/webhooks/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WebhookEvent, IssuesEventPayload, IssueCommentEventPayload, PullRequestEventPayload, WebhookEventName, TypedWebhookEvent } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Raw webhook payload from GitHub
|
|
4
|
+
*/
|
|
5
|
+
export interface RawWebhookPayload {
|
|
6
|
+
action?: string;
|
|
7
|
+
issue?: Record<string, unknown>;
|
|
8
|
+
comment?: Record<string, unknown>;
|
|
9
|
+
pull_request?: Record<string, unknown>;
|
|
10
|
+
sender?: Record<string, unknown>;
|
|
11
|
+
repository?: Record<string, unknown>;
|
|
12
|
+
assignee?: Record<string, unknown>;
|
|
13
|
+
label?: Record<string, unknown>;
|
|
14
|
+
changes?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse a raw webhook event into a typed event
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseWebhookEvent(eventName: string, payload: unknown, deliveryId?: string): TypedWebhookEvent;
|
|
20
|
+
/**
|
|
21
|
+
* Check if an event name is a supported webhook event
|
|
22
|
+
*/
|
|
23
|
+
export declare function isSupportedEvent(eventName: string): eventName is WebhookEventName;
|
|
24
|
+
/**
|
|
25
|
+
* Type guard for IssuesEventPayload
|
|
26
|
+
*/
|
|
27
|
+
export declare function isIssuesEvent(event: TypedWebhookEvent): event is WebhookEvent<IssuesEventPayload>;
|
|
28
|
+
/**
|
|
29
|
+
* Type guard for IssueCommentEventPayload
|
|
30
|
+
*/
|
|
31
|
+
export declare function isIssueCommentEvent(event: TypedWebhookEvent): event is WebhookEvent<IssueCommentEventPayload>;
|
|
32
|
+
/**
|
|
33
|
+
* Type guard for PullRequestEventPayload
|
|
34
|
+
*/
|
|
35
|
+
export declare function isPullRequestEvent(event: TypedWebhookEvent): event is WebhookEvent<PullRequestEventPayload>;
|
|
36
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/webhooks/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EAOlB,MAAM,mBAAmB,CAAC;AAG3B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAyLD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,iBAAiB,CA4BnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,IAAI,gBAAgB,CAEjF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,iBAAiB,GACvB,KAAK,IAAI,YAAY,CAAC,kBAAkB,CAAC,CAE3C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,iBAAiB,GACvB,KAAK,IAAI,YAAY,CAAC,wBAAwB,CAAC,CAEjD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,GACvB,KAAK,IAAI,YAAY,CAAC,uBAAuB,CAAC,CAEhD"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { GitHubValidationError } from '../utils/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Transform raw user data to User type
|
|
4
|
+
*/
|
|
5
|
+
function parseUser(raw) {
|
|
6
|
+
return {
|
|
7
|
+
id: raw.id ?? 0,
|
|
8
|
+
login: raw.login ?? 'unknown',
|
|
9
|
+
avatarUrl: raw.avatar_url ?? '',
|
|
10
|
+
type: (raw.type ?? 'User'),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Transform raw label data to Label type
|
|
15
|
+
*/
|
|
16
|
+
function parseLabel(raw) {
|
|
17
|
+
return {
|
|
18
|
+
id: raw.id ?? 0,
|
|
19
|
+
name: raw.name ?? '',
|
|
20
|
+
color: raw.color ?? '',
|
|
21
|
+
description: raw.description ?? null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Transform raw repository data to Repository type
|
|
26
|
+
*/
|
|
27
|
+
function parseRepository(raw) {
|
|
28
|
+
const owner = raw.owner;
|
|
29
|
+
return {
|
|
30
|
+
id: raw.id ?? 0,
|
|
31
|
+
name: raw.name ?? '',
|
|
32
|
+
fullName: raw.full_name ?? '',
|
|
33
|
+
owner: owner ? parseUser(owner) : { id: 0, login: 'unknown', avatarUrl: '', type: 'User' },
|
|
34
|
+
htmlUrl: raw.html_url ?? '',
|
|
35
|
+
private: raw.private ?? false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Transform raw issue data to Issue type
|
|
40
|
+
*/
|
|
41
|
+
function parseIssue(raw) {
|
|
42
|
+
const labels = raw.labels ?? [];
|
|
43
|
+
const assignees = raw.assignees ?? [];
|
|
44
|
+
const milestone = raw.milestone;
|
|
45
|
+
const user = raw.user;
|
|
46
|
+
return {
|
|
47
|
+
number: raw.number ?? 0,
|
|
48
|
+
title: raw.title ?? '',
|
|
49
|
+
body: raw.body ?? null,
|
|
50
|
+
state: (raw.state ?? 'open'),
|
|
51
|
+
labels: labels.map(parseLabel),
|
|
52
|
+
assignees: assignees.map(parseUser),
|
|
53
|
+
milestone: milestone
|
|
54
|
+
? {
|
|
55
|
+
id: milestone.id ?? 0,
|
|
56
|
+
number: milestone.number ?? 0,
|
|
57
|
+
title: milestone.title ?? '',
|
|
58
|
+
description: milestone.description ?? null,
|
|
59
|
+
state: (milestone.state ?? 'open'),
|
|
60
|
+
dueOn: milestone.due_on ?? null,
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
createdAt: raw.created_at ?? '',
|
|
64
|
+
updatedAt: raw.updated_at ?? '',
|
|
65
|
+
closedAt: raw.closed_at ?? null,
|
|
66
|
+
author: user ? parseUser(user) : { id: 0, login: 'unknown', avatarUrl: '', type: 'User' },
|
|
67
|
+
url: raw.url ?? '',
|
|
68
|
+
htmlUrl: raw.html_url ?? '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Transform raw comment data to Comment type
|
|
73
|
+
*/
|
|
74
|
+
function parseComment(raw) {
|
|
75
|
+
const user = raw.user;
|
|
76
|
+
return {
|
|
77
|
+
id: raw.id ?? 0,
|
|
78
|
+
body: raw.body ?? '',
|
|
79
|
+
author: user ? parseUser(user) : { id: 0, login: 'unknown', avatarUrl: '', type: 'User' },
|
|
80
|
+
createdAt: raw.created_at ?? '',
|
|
81
|
+
updatedAt: raw.updated_at ?? '',
|
|
82
|
+
htmlUrl: raw.html_url ?? '',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Transform raw pull request data to PullRequest type
|
|
87
|
+
*/
|
|
88
|
+
function parsePullRequest(raw) {
|
|
89
|
+
const user = raw.user;
|
|
90
|
+
const body = raw.body;
|
|
91
|
+
// Extract linked issues from body
|
|
92
|
+
const linkedIssues = [];
|
|
93
|
+
if (body) {
|
|
94
|
+
const patterns = [
|
|
95
|
+
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi,
|
|
96
|
+
];
|
|
97
|
+
for (const pattern of patterns) {
|
|
98
|
+
let match;
|
|
99
|
+
while ((match = pattern.exec(body)) !== null) {
|
|
100
|
+
const issueNum = parseInt(match[1] ?? '', 10);
|
|
101
|
+
if (!isNaN(issueNum) && !linkedIssues.includes(issueNum)) {
|
|
102
|
+
linkedIssues.push(issueNum);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Determine state
|
|
108
|
+
let state = (raw.state ?? 'open');
|
|
109
|
+
if (raw.merged === true) {
|
|
110
|
+
state = 'merged';
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
number: raw.number ?? 0,
|
|
114
|
+
title: raw.title ?? '',
|
|
115
|
+
state,
|
|
116
|
+
author: user ? parseUser(user) : { id: 0, login: 'unknown', avatarUrl: '', type: 'User' },
|
|
117
|
+
htmlUrl: raw.html_url ?? '',
|
|
118
|
+
linkedIssues,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parse an issues webhook event payload
|
|
123
|
+
*/
|
|
124
|
+
function parseIssuesEvent(payload) {
|
|
125
|
+
if (!payload.action || !payload.issue || !payload.sender || !payload.repository) {
|
|
126
|
+
throw new GitHubValidationError('Invalid issues event payload');
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
action: payload.action,
|
|
130
|
+
issue: parseIssue(payload.issue),
|
|
131
|
+
sender: parseUser(payload.sender),
|
|
132
|
+
repository: parseRepository(payload.repository),
|
|
133
|
+
assignee: payload.assignee ? parseUser(payload.assignee) : undefined,
|
|
134
|
+
label: payload.label ? parseLabel(payload.label) : undefined,
|
|
135
|
+
changes: payload.changes,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Parse an issue_comment webhook event payload
|
|
140
|
+
*/
|
|
141
|
+
function parseIssueCommentEvent(payload) {
|
|
142
|
+
if (!payload.action || !payload.issue || !payload.comment || !payload.sender || !payload.repository) {
|
|
143
|
+
throw new GitHubValidationError('Invalid issue_comment event payload');
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
action: payload.action,
|
|
147
|
+
issue: parseIssue(payload.issue),
|
|
148
|
+
comment: parseComment(payload.comment),
|
|
149
|
+
sender: parseUser(payload.sender),
|
|
150
|
+
repository: parseRepository(payload.repository),
|
|
151
|
+
changes: payload.changes,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Parse a pull_request webhook event payload
|
|
156
|
+
*/
|
|
157
|
+
function parsePullRequestEvent(payload) {
|
|
158
|
+
if (!payload.action || !payload.pull_request || !payload.sender || !payload.repository) {
|
|
159
|
+
throw new GitHubValidationError('Invalid pull_request event payload');
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
action: payload.action,
|
|
163
|
+
pull_request: parsePullRequest(payload.pull_request),
|
|
164
|
+
sender: parseUser(payload.sender),
|
|
165
|
+
repository: parseRepository(payload.repository),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parse a raw webhook event into a typed event
|
|
170
|
+
*/
|
|
171
|
+
export function parseWebhookEvent(eventName, payload, deliveryId) {
|
|
172
|
+
const rawPayload = payload;
|
|
173
|
+
switch (eventName) {
|
|
174
|
+
case 'issues':
|
|
175
|
+
return {
|
|
176
|
+
name: eventName,
|
|
177
|
+
payload: parseIssuesEvent(rawPayload),
|
|
178
|
+
deliveryId,
|
|
179
|
+
};
|
|
180
|
+
case 'issue_comment':
|
|
181
|
+
return {
|
|
182
|
+
name: eventName,
|
|
183
|
+
payload: parseIssueCommentEvent(rawPayload),
|
|
184
|
+
deliveryId,
|
|
185
|
+
};
|
|
186
|
+
case 'pull_request':
|
|
187
|
+
return {
|
|
188
|
+
name: eventName,
|
|
189
|
+
payload: parsePullRequestEvent(rawPayload),
|
|
190
|
+
deliveryId,
|
|
191
|
+
};
|
|
192
|
+
default:
|
|
193
|
+
throw new GitHubValidationError(`Unsupported webhook event type: ${eventName}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if an event name is a supported webhook event
|
|
198
|
+
*/
|
|
199
|
+
export function isSupportedEvent(eventName) {
|
|
200
|
+
return ['issues', 'issue_comment', 'pull_request'].includes(eventName);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Type guard for IssuesEventPayload
|
|
204
|
+
*/
|
|
205
|
+
export function isIssuesEvent(event) {
|
|
206
|
+
return event.name === 'issues';
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Type guard for IssueCommentEventPayload
|
|
210
|
+
*/
|
|
211
|
+
export function isIssueCommentEvent(event) {
|
|
212
|
+
return event.name === 'issue_comment';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Type guard for PullRequestEventPayload
|
|
216
|
+
*/
|
|
217
|
+
export function isPullRequestEvent(event) {
|
|
218
|
+
return event.name === 'pull_request';
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/webhooks/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TypedWebhookEvent, WorkflowAction } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for workflow triggers
|
|
4
|
+
*/
|
|
5
|
+
export interface TriggerConfig {
|
|
6
|
+
/** Username of the agent account for assignment detection */
|
|
7
|
+
agentAccount?: string;
|
|
8
|
+
/** Labels that trigger workflow start */
|
|
9
|
+
triggerLabels?: string[];
|
|
10
|
+
/** Comment patterns that resume paused workflows */
|
|
11
|
+
resumePatterns?: RegExp[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Evaluate a webhook event against trigger configuration
|
|
15
|
+
*/
|
|
16
|
+
export declare function evaluateTriggers(event: TypedWebhookEvent, config: TriggerConfig): WorkflowAction;
|
|
17
|
+
/**
|
|
18
|
+
* Check if an action requires workflow processing
|
|
19
|
+
*/
|
|
20
|
+
export declare function requiresProcessing(action: WorkflowAction): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Get the issue number from a workflow action
|
|
23
|
+
*/
|
|
24
|
+
export declare function getActionIssueNumber(action: WorkflowAction): number | null;
|
|
25
|
+
//# sourceMappingURL=triggers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triggers.d.ts","sourceRoot":"","sources":["../../src/webhooks/triggers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAGjB,cAAc,EAKf,MAAM,mBAAmB,CAAC;AAM3B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAuHD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,aAAa,GACpB,cAAc,CAWhB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAElE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAK1E"}
|