@auxiora/connector-github 1.0.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/LICENSE +191 -0
- package/dist/connector.d.ts +2 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +426 -0
- package/dist/connector.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -0
- package/src/connector.ts +482 -0
- package/src/index.ts +1 -0
- package/tests/connector.test.ts +111 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auxiora/connector-github",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "GitHub connector: Issues, PRs, Actions, Repos",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@auxiora/connectors": "1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.0.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/connector.ts
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { defineConnector } from '@auxiora/connectors';
|
|
2
|
+
import type { TriggerEvent } from '@auxiora/connectors';
|
|
3
|
+
|
|
4
|
+
const GITHUB_API = 'https://api.github.com';
|
|
5
|
+
|
|
6
|
+
async function ghFetch(path: string, token: string, options: RequestInit = {}): Promise<Response> {
|
|
7
|
+
const res = await fetch(`${GITHUB_API}${path}`, {
|
|
8
|
+
...options,
|
|
9
|
+
headers: {
|
|
10
|
+
'Accept': 'application/vnd.github+json',
|
|
11
|
+
'Authorization': `Bearer ${token}`,
|
|
12
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
13
|
+
...options.headers,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const body = await res.text();
|
|
18
|
+
throw new Error(`GitHub API error ${res.status}: ${body}`);
|
|
19
|
+
}
|
|
20
|
+
return res;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function ghJson<T = unknown>(path: string, token: string, options: RequestInit = {}): Promise<T> {
|
|
24
|
+
const res = await ghFetch(path, token, options);
|
|
25
|
+
// 204 No Content (e.g. workflow dispatch) returns no body
|
|
26
|
+
if (res.status === 204) return {} as T;
|
|
27
|
+
return res.json() as Promise<T>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const githubConnector = defineConnector({
|
|
31
|
+
id: 'github',
|
|
32
|
+
name: 'GitHub',
|
|
33
|
+
description: 'Integration with GitHub Issues, PRs, Actions, and Repos',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
category: 'devtools',
|
|
36
|
+
icon: 'github',
|
|
37
|
+
|
|
38
|
+
auth: {
|
|
39
|
+
type: 'oauth2',
|
|
40
|
+
oauth2: {
|
|
41
|
+
authUrl: 'https://github.com/login/oauth/authorize',
|
|
42
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
43
|
+
scopes: ['repo', 'workflow', 'read:org'],
|
|
44
|
+
},
|
|
45
|
+
instructions: 'You can also use a Personal Access Token (PAT) with token auth type.',
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
actions: [
|
|
49
|
+
// --- Issues ---
|
|
50
|
+
{
|
|
51
|
+
id: 'issues-list',
|
|
52
|
+
name: 'List Issues',
|
|
53
|
+
description: 'List issues in a repository',
|
|
54
|
+
trustMinimum: 1,
|
|
55
|
+
trustDomain: 'integrations',
|
|
56
|
+
reversible: false,
|
|
57
|
+
sideEffects: false,
|
|
58
|
+
params: {
|
|
59
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
60
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
61
|
+
state: { type: 'string', description: 'Issue state (open, closed, all)', default: 'open' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'issues-create',
|
|
66
|
+
name: 'Create Issue',
|
|
67
|
+
description: 'Create a new issue',
|
|
68
|
+
trustMinimum: 2,
|
|
69
|
+
trustDomain: 'integrations',
|
|
70
|
+
reversible: true,
|
|
71
|
+
sideEffects: true,
|
|
72
|
+
params: {
|
|
73
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
74
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
75
|
+
title: { type: 'string', description: 'Issue title', required: true },
|
|
76
|
+
body: { type: 'string', description: 'Issue body' },
|
|
77
|
+
labels: { type: 'array', description: 'Issue labels' },
|
|
78
|
+
assignees: { type: 'array', description: 'Assignees' },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'issues-update',
|
|
83
|
+
name: 'Update Issue',
|
|
84
|
+
description: 'Update an existing issue',
|
|
85
|
+
trustMinimum: 2,
|
|
86
|
+
trustDomain: 'integrations',
|
|
87
|
+
reversible: true,
|
|
88
|
+
sideEffects: true,
|
|
89
|
+
params: {
|
|
90
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
91
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
92
|
+
issueNumber: { type: 'number', description: 'Issue number', required: true },
|
|
93
|
+
title: { type: 'string', description: 'Updated title' },
|
|
94
|
+
body: { type: 'string', description: 'Updated body' },
|
|
95
|
+
state: { type: 'string', description: 'State (open, closed)' },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'issues-comment',
|
|
100
|
+
name: 'Comment on Issue',
|
|
101
|
+
description: 'Add a comment to an issue',
|
|
102
|
+
trustMinimum: 2,
|
|
103
|
+
trustDomain: 'integrations',
|
|
104
|
+
reversible: true,
|
|
105
|
+
sideEffects: true,
|
|
106
|
+
params: {
|
|
107
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
108
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
109
|
+
issueNumber: { type: 'number', description: 'Issue number', required: true },
|
|
110
|
+
body: { type: 'string', description: 'Comment body', required: true },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
// --- PRs ---
|
|
114
|
+
{
|
|
115
|
+
id: 'prs-list',
|
|
116
|
+
name: 'List Pull Requests',
|
|
117
|
+
description: 'List pull requests in a repository',
|
|
118
|
+
trustMinimum: 1,
|
|
119
|
+
trustDomain: 'integrations',
|
|
120
|
+
reversible: false,
|
|
121
|
+
sideEffects: false,
|
|
122
|
+
params: {
|
|
123
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
124
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
125
|
+
state: { type: 'string', description: 'PR state', default: 'open' },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'prs-create',
|
|
130
|
+
name: 'Create Pull Request',
|
|
131
|
+
description: 'Create a new pull request',
|
|
132
|
+
trustMinimum: 3,
|
|
133
|
+
trustDomain: 'integrations',
|
|
134
|
+
reversible: true,
|
|
135
|
+
sideEffects: true,
|
|
136
|
+
params: {
|
|
137
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
138
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
139
|
+
title: { type: 'string', description: 'PR title', required: true },
|
|
140
|
+
head: { type: 'string', description: 'Head branch', required: true },
|
|
141
|
+
base: { type: 'string', description: 'Base branch', required: true },
|
|
142
|
+
body: { type: 'string', description: 'PR description' },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'prs-merge',
|
|
147
|
+
name: 'Merge Pull Request',
|
|
148
|
+
description: 'Merge a pull request',
|
|
149
|
+
trustMinimum: 4,
|
|
150
|
+
trustDomain: 'integrations',
|
|
151
|
+
reversible: false,
|
|
152
|
+
sideEffects: true,
|
|
153
|
+
params: {
|
|
154
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
155
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
156
|
+
pullNumber: { type: 'number', description: 'PR number', required: true },
|
|
157
|
+
mergeMethod: { type: 'string', description: 'Merge method (merge, squash, rebase)', default: 'merge' },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
// --- Actions ---
|
|
161
|
+
{
|
|
162
|
+
id: 'actions-list-runs',
|
|
163
|
+
name: 'List Workflow Runs',
|
|
164
|
+
description: 'List recent workflow runs',
|
|
165
|
+
trustMinimum: 1,
|
|
166
|
+
trustDomain: 'integrations',
|
|
167
|
+
reversible: false,
|
|
168
|
+
sideEffects: false,
|
|
169
|
+
params: {
|
|
170
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
171
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'actions-trigger',
|
|
176
|
+
name: 'Trigger Workflow',
|
|
177
|
+
description: 'Trigger a workflow dispatch',
|
|
178
|
+
trustMinimum: 3,
|
|
179
|
+
trustDomain: 'integrations',
|
|
180
|
+
reversible: false,
|
|
181
|
+
sideEffects: true,
|
|
182
|
+
params: {
|
|
183
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
184
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
185
|
+
workflowId: { type: 'string', description: 'Workflow ID or filename', required: true },
|
|
186
|
+
ref: { type: 'string', description: 'Git ref', default: 'main' },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
// --- Repos ---
|
|
190
|
+
{
|
|
191
|
+
id: 'repos-list',
|
|
192
|
+
name: 'List Repositories',
|
|
193
|
+
description: 'List repositories for the authenticated user',
|
|
194
|
+
trustMinimum: 1,
|
|
195
|
+
trustDomain: 'integrations',
|
|
196
|
+
reversible: false,
|
|
197
|
+
sideEffects: false,
|
|
198
|
+
params: {
|
|
199
|
+
type: { type: 'string', description: 'Type (all, owner, member)', default: 'all' },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'repos-get',
|
|
204
|
+
name: 'Get Repository',
|
|
205
|
+
description: 'Get details of a repository',
|
|
206
|
+
trustMinimum: 1,
|
|
207
|
+
trustDomain: 'integrations',
|
|
208
|
+
reversible: false,
|
|
209
|
+
sideEffects: false,
|
|
210
|
+
params: {
|
|
211
|
+
owner: { type: 'string', description: 'Repository owner', required: true },
|
|
212
|
+
repo: { type: 'string', description: 'Repository name', required: true },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
|
|
217
|
+
triggers: [
|
|
218
|
+
{
|
|
219
|
+
id: 'pr-opened',
|
|
220
|
+
name: 'PR Opened',
|
|
221
|
+
description: 'Triggered when a new pull request is opened',
|
|
222
|
+
type: 'poll',
|
|
223
|
+
pollIntervalMs: 120_000,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'issue-created',
|
|
227
|
+
name: 'Issue Created',
|
|
228
|
+
description: 'Triggered when a new issue is created',
|
|
229
|
+
type: 'poll',
|
|
230
|
+
pollIntervalMs: 120_000,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'workflow-failed',
|
|
234
|
+
name: 'Workflow Failed',
|
|
235
|
+
description: 'Triggered when a workflow run fails',
|
|
236
|
+
type: 'poll',
|
|
237
|
+
pollIntervalMs: 120_000,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
|
|
241
|
+
entities: [
|
|
242
|
+
{
|
|
243
|
+
id: 'issue',
|
|
244
|
+
name: 'Issue',
|
|
245
|
+
description: 'A GitHub issue',
|
|
246
|
+
fields: { number: 'number', title: 'string', state: 'string', body: 'string', labels: 'array' },
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'pull-request',
|
|
250
|
+
name: 'Pull Request',
|
|
251
|
+
description: 'A GitHub pull request',
|
|
252
|
+
fields: { number: 'number', title: 'string', state: 'string', head: 'string', base: 'string' },
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: 'repository',
|
|
256
|
+
name: 'Repository',
|
|
257
|
+
description: 'A GitHub repository',
|
|
258
|
+
fields: { name: 'string', fullName: 'string', private: 'boolean', defaultBranch: 'string' },
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
|
|
262
|
+
async executeAction(actionId: string, params: Record<string, unknown>, token: string): Promise<unknown> {
|
|
263
|
+
switch (actionId) {
|
|
264
|
+
// --- Issues ---
|
|
265
|
+
case 'issues-list': {
|
|
266
|
+
const owner = params.owner as string;
|
|
267
|
+
const repo = params.repo as string;
|
|
268
|
+
const state = (params.state as string) ?? 'open';
|
|
269
|
+
const items = await ghJson<Array<{ number: number; title: string; state: string; body: string | null; labels: Array<{ name: string }> }>>(
|
|
270
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?state=${encodeURIComponent(state)}`,
|
|
271
|
+
token,
|
|
272
|
+
);
|
|
273
|
+
return {
|
|
274
|
+
issues: items.map(i => ({
|
|
275
|
+
number: i.number,
|
|
276
|
+
title: i.title,
|
|
277
|
+
state: i.state,
|
|
278
|
+
body: i.body ?? '',
|
|
279
|
+
labels: i.labels.map(l => l.name),
|
|
280
|
+
})),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'issues-create': {
|
|
285
|
+
const owner = params.owner as string;
|
|
286
|
+
const repo = params.repo as string;
|
|
287
|
+
const issue = await ghJson<{ number: number; title: string; state: string; html_url: string }>(
|
|
288
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues`,
|
|
289
|
+
token,
|
|
290
|
+
{
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers: { 'Content-Type': 'application/json' },
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
title: params.title as string,
|
|
295
|
+
body: params.body as string | undefined,
|
|
296
|
+
labels: params.labels as string[] | undefined,
|
|
297
|
+
assignees: params.assignees as string[] | undefined,
|
|
298
|
+
}),
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
return { issueNumber: issue.number, status: 'created', title: issue.title, url: issue.html_url };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case 'issues-update': {
|
|
305
|
+
const owner = params.owner as string;
|
|
306
|
+
const repo = params.repo as string;
|
|
307
|
+
const issueNumber = params.issueNumber as number;
|
|
308
|
+
const body: Record<string, unknown> = {};
|
|
309
|
+
if (params.title !== undefined) body.title = params.title;
|
|
310
|
+
if (params.body !== undefined) body.body = params.body;
|
|
311
|
+
if (params.state !== undefined) body.state = params.state;
|
|
312
|
+
const issue = await ghJson<{ number: number; title: string; state: string }>(
|
|
313
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
314
|
+
token,
|
|
315
|
+
{
|
|
316
|
+
method: 'PATCH',
|
|
317
|
+
headers: { 'Content-Type': 'application/json' },
|
|
318
|
+
body: JSON.stringify(body),
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
return { issueNumber: issue.number, status: 'updated', title: issue.title, state: issue.state };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'issues-comment': {
|
|
325
|
+
const owner = params.owner as string;
|
|
326
|
+
const repo = params.repo as string;
|
|
327
|
+
const issueNumber = params.issueNumber as number;
|
|
328
|
+
const comment = await ghJson<{ id: number; html_url: string }>(
|
|
329
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments`,
|
|
330
|
+
token,
|
|
331
|
+
{
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: { 'Content-Type': 'application/json' },
|
|
334
|
+
body: JSON.stringify({ body: params.body as string }),
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
return { commentId: comment.id, status: 'created', url: comment.html_url };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// --- Pull Requests ---
|
|
341
|
+
case 'prs-list': {
|
|
342
|
+
const owner = params.owner as string;
|
|
343
|
+
const repo = params.repo as string;
|
|
344
|
+
const state = (params.state as string) ?? 'open';
|
|
345
|
+
const items = await ghJson<Array<{ number: number; title: string; state: string; head: { ref: string }; base: { ref: string } }>>(
|
|
346
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls?state=${encodeURIComponent(state)}`,
|
|
347
|
+
token,
|
|
348
|
+
);
|
|
349
|
+
return {
|
|
350
|
+
pullRequests: items.map(pr => ({
|
|
351
|
+
number: pr.number,
|
|
352
|
+
title: pr.title,
|
|
353
|
+
state: pr.state,
|
|
354
|
+
head: pr.head.ref,
|
|
355
|
+
base: pr.base.ref,
|
|
356
|
+
})),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case 'prs-create': {
|
|
361
|
+
const owner = params.owner as string;
|
|
362
|
+
const repo = params.repo as string;
|
|
363
|
+
const pr = await ghJson<{ number: number; title: string; state: string; html_url: string }>(
|
|
364
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls`,
|
|
365
|
+
token,
|
|
366
|
+
{
|
|
367
|
+
method: 'POST',
|
|
368
|
+
headers: { 'Content-Type': 'application/json' },
|
|
369
|
+
body: JSON.stringify({
|
|
370
|
+
title: params.title as string,
|
|
371
|
+
head: params.head as string,
|
|
372
|
+
base: params.base as string,
|
|
373
|
+
body: params.body as string | undefined,
|
|
374
|
+
}),
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
return { pullNumber: pr.number, status: 'created', title: pr.title, url: pr.html_url };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
case 'prs-merge': {
|
|
381
|
+
const owner = params.owner as string;
|
|
382
|
+
const repo = params.repo as string;
|
|
383
|
+
const pullNumber = params.pullNumber as number;
|
|
384
|
+
const result = await ghJson<{ merged: boolean; message: string }>(
|
|
385
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${pullNumber}/merge`,
|
|
386
|
+
token,
|
|
387
|
+
{
|
|
388
|
+
method: 'PUT',
|
|
389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
merge_method: (params.mergeMethod as string) ?? 'merge',
|
|
392
|
+
}),
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
return { pullNumber, status: result.merged ? 'merged' : 'failed', message: result.message };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// --- Actions ---
|
|
399
|
+
case 'actions-list-runs': {
|
|
400
|
+
const owner = params.owner as string;
|
|
401
|
+
const repo = params.repo as string;
|
|
402
|
+
const data = await ghJson<{ workflow_runs: Array<{ id: number; name: string | null; status: string; conclusion: string | null; html_url: string; created_at: string }> }>(
|
|
403
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/actions/runs`,
|
|
404
|
+
token,
|
|
405
|
+
);
|
|
406
|
+
return {
|
|
407
|
+
runs: data.workflow_runs.map(r => ({
|
|
408
|
+
id: r.id,
|
|
409
|
+
name: r.name,
|
|
410
|
+
status: r.status,
|
|
411
|
+
conclusion: r.conclusion,
|
|
412
|
+
url: r.html_url,
|
|
413
|
+
createdAt: r.created_at,
|
|
414
|
+
})),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
case 'actions-trigger': {
|
|
419
|
+
const owner = params.owner as string;
|
|
420
|
+
const repo = params.repo as string;
|
|
421
|
+
const workflowId = params.workflowId as string;
|
|
422
|
+
const ref = (params.ref as string) ?? 'main';
|
|
423
|
+
await ghJson(
|
|
424
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/actions/workflows/${encodeURIComponent(workflowId)}/dispatches`,
|
|
425
|
+
token,
|
|
426
|
+
{
|
|
427
|
+
method: 'POST',
|
|
428
|
+
headers: { 'Content-Type': 'application/json' },
|
|
429
|
+
body: JSON.stringify({ ref }),
|
|
430
|
+
},
|
|
431
|
+
);
|
|
432
|
+
return { status: 'triggered', workflowId, ref };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// --- Repos ---
|
|
436
|
+
case 'repos-list': {
|
|
437
|
+
const type = (params.type as string) ?? 'all';
|
|
438
|
+
const items = await ghJson<Array<{ name: string; full_name: string; private: boolean; default_branch: string }>>(
|
|
439
|
+
`/user/repos?type=${encodeURIComponent(type)}`,
|
|
440
|
+
token,
|
|
441
|
+
);
|
|
442
|
+
return {
|
|
443
|
+
repositories: items.map(r => ({
|
|
444
|
+
name: r.name,
|
|
445
|
+
fullName: r.full_name,
|
|
446
|
+
private: r.private,
|
|
447
|
+
defaultBranch: r.default_branch,
|
|
448
|
+
})),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
case 'repos-get': {
|
|
453
|
+
const owner = params.owner as string;
|
|
454
|
+
const repo = params.repo as string;
|
|
455
|
+
const r = await ghJson<{ name: string; full_name: string; private: boolean; default_branch: string; description: string | null; stargazers_count: number; forks_count: number; html_url: string }>(
|
|
456
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
457
|
+
token,
|
|
458
|
+
);
|
|
459
|
+
return {
|
|
460
|
+
name: r.name,
|
|
461
|
+
fullName: r.full_name,
|
|
462
|
+
private: r.private,
|
|
463
|
+
defaultBranch: r.default_branch,
|
|
464
|
+
description: r.description,
|
|
465
|
+
stars: r.stargazers_count,
|
|
466
|
+
forks: r.forks_count,
|
|
467
|
+
url: r.html_url,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
default:
|
|
472
|
+
throw new Error(`Unknown action: ${actionId}`);
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
async pollTrigger(_triggerId: string, _token: string, _lastPollAt?: number): Promise<TriggerEvent[]> {
|
|
477
|
+
// Triggers require repository configuration (owner/repo) which is not available
|
|
478
|
+
// in the pollTrigger signature. A future version could store monitored repos in
|
|
479
|
+
// connector config. For now, return empty to avoid errors.
|
|
480
|
+
return [];
|
|
481
|
+
},
|
|
482
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { githubConnector } from './connector.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { githubConnector } from '../src/connector.js';
|
|
3
|
+
|
|
4
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
fetchMock = vi.fn();
|
|
7
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('GitHub Connector', () => {
|
|
14
|
+
it('should have correct metadata', () => {
|
|
15
|
+
expect(githubConnector.id).toBe('github');
|
|
16
|
+
expect(githubConnector.name).toBe('GitHub');
|
|
17
|
+
expect(githubConnector.category).toBe('devtools');
|
|
18
|
+
expect(githubConnector.version).toBe('1.0.0');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should use OAuth2 authentication', () => {
|
|
22
|
+
expect(githubConnector.auth.type).toBe('oauth2');
|
|
23
|
+
expect(githubConnector.auth.oauth2?.scopes).toContain('repo');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should define issue actions', () => {
|
|
27
|
+
const issueActions = githubConnector.actions.filter((a) => a.id.startsWith('issues-'));
|
|
28
|
+
expect(issueActions.length).toBe(4);
|
|
29
|
+
expect(issueActions.map((a) => a.id)).toContain('issues-list');
|
|
30
|
+
expect(issueActions.map((a) => a.id)).toContain('issues-create');
|
|
31
|
+
expect(issueActions.map((a) => a.id)).toContain('issues-update');
|
|
32
|
+
expect(issueActions.map((a) => a.id)).toContain('issues-comment');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should define PR actions', () => {
|
|
36
|
+
const prActions = githubConnector.actions.filter((a) => a.id.startsWith('prs-'));
|
|
37
|
+
expect(prActions.length).toBe(3);
|
|
38
|
+
expect(prActions.map((a) => a.id)).toContain('prs-merge');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should define Actions/workflow actions', () => {
|
|
42
|
+
const actionsActions = githubConnector.actions.filter((a) => a.id.startsWith('actions-'));
|
|
43
|
+
expect(actionsActions.length).toBe(2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should define repo actions', () => {
|
|
47
|
+
const repoActions = githubConnector.actions.filter((a) => a.id.startsWith('repos-'));
|
|
48
|
+
expect(repoActions.length).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should define triggers', () => {
|
|
52
|
+
expect(githubConnector.triggers).toHaveLength(3);
|
|
53
|
+
const triggerIds = githubConnector.triggers.map((t) => t.id);
|
|
54
|
+
expect(triggerIds).toContain('pr-opened');
|
|
55
|
+
expect(triggerIds).toContain('issue-created');
|
|
56
|
+
expect(triggerIds).toContain('workflow-failed');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should require high trust for merging PRs', () => {
|
|
60
|
+
const mergeAction = githubConnector.actions.find((a) => a.id === 'prs-merge');
|
|
61
|
+
expect(mergeAction?.trustMinimum).toBe(4);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should execute issues-create action', async () => {
|
|
65
|
+
fetchMock.mockResolvedValueOnce({
|
|
66
|
+
ok: true,
|
|
67
|
+
status: 201,
|
|
68
|
+
json: async () => ({ number: 1, title: 'Bug', state: 'open', html_url: 'https://github.com/test/repo/issues/1' }),
|
|
69
|
+
});
|
|
70
|
+
const result = await githubConnector.executeAction(
|
|
71
|
+
'issues-create',
|
|
72
|
+
{ owner: 'test', repo: 'repo', title: 'Bug' },
|
|
73
|
+
'token',
|
|
74
|
+
) as any;
|
|
75
|
+
expect(result.status).toBe('created');
|
|
76
|
+
expect(result.title).toBe('Bug');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should execute repos-get action', async () => {
|
|
80
|
+
fetchMock.mockResolvedValueOnce({
|
|
81
|
+
ok: true,
|
|
82
|
+
status: 200,
|
|
83
|
+
json: async () => ({
|
|
84
|
+
name: 'repo',
|
|
85
|
+
full_name: 'test/repo',
|
|
86
|
+
private: false,
|
|
87
|
+
default_branch: 'main',
|
|
88
|
+
description: 'A test repo',
|
|
89
|
+
stargazers_count: 10,
|
|
90
|
+
forks_count: 2,
|
|
91
|
+
html_url: 'https://github.com/test/repo',
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
const result = await githubConnector.executeAction(
|
|
95
|
+
'repos-get',
|
|
96
|
+
{ owner: 'test', repo: 'repo' },
|
|
97
|
+
'token',
|
|
98
|
+
) as any;
|
|
99
|
+
expect(result.fullName).toBe('test/repo');
|
|
100
|
+
expect(result.defaultBranch).toBe('main');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should throw for unknown action', async () => {
|
|
104
|
+
await expect(githubConnector.executeAction('unknown', {}, 'token')).rejects.toThrow('Unknown action');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return empty events from pollTrigger', async () => {
|
|
108
|
+
const events = await githubConnector.pollTrigger!('pr-opened', 'token');
|
|
109
|
+
expect(events).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
});
|