@flink-app/github-app-plugin 0.12.1-alpha.38
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/CHANGELOG.md +209 -0
- package/LICENSE +21 -0
- package/README.md +667 -0
- package/SECURITY.md +498 -0
- package/dist/GitHubAppInternalContext.d.ts +44 -0
- package/dist/GitHubAppInternalContext.js +2 -0
- package/dist/GitHubAppPlugin.d.ts +45 -0
- package/dist/GitHubAppPlugin.js +367 -0
- package/dist/GitHubAppPluginContext.d.ts +242 -0
- package/dist/GitHubAppPluginContext.js +2 -0
- package/dist/GitHubAppPluginOptions.d.ts +369 -0
- package/dist/GitHubAppPluginOptions.js +2 -0
- package/dist/handlers/InitiateInstallation.d.ts +32 -0
- package/dist/handlers/InitiateInstallation.js +66 -0
- package/dist/handlers/InstallationCallback.d.ts +42 -0
- package/dist/handlers/InstallationCallback.js +248 -0
- package/dist/handlers/UninstallHandler.d.ts +37 -0
- package/dist/handlers/UninstallHandler.js +153 -0
- package/dist/handlers/WebhookHandler.d.ts +54 -0
- package/dist/handlers/WebhookHandler.js +157 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +23 -0
- package/dist/repos/GitHubAppSessionRepo.d.ts +24 -0
- package/dist/repos/GitHubAppSessionRepo.js +32 -0
- package/dist/repos/GitHubInstallationRepo.d.ts +53 -0
- package/dist/repos/GitHubInstallationRepo.js +83 -0
- package/dist/repos/GitHubWebhookEventRepo.d.ts +29 -0
- package/dist/repos/GitHubWebhookEventRepo.js +42 -0
- package/dist/schemas/GitHubAppSession.d.ts +13 -0
- package/dist/schemas/GitHubAppSession.js +2 -0
- package/dist/schemas/GitHubInstallation.d.ts +28 -0
- package/dist/schemas/GitHubInstallation.js +2 -0
- package/dist/schemas/InstallationCallbackRequest.d.ts +10 -0
- package/dist/schemas/InstallationCallbackRequest.js +2 -0
- package/dist/schemas/WebhookEvent.d.ts +16 -0
- package/dist/schemas/WebhookEvent.js +2 -0
- package/dist/schemas/WebhookPayload.d.ts +35 -0
- package/dist/schemas/WebhookPayload.js +2 -0
- package/dist/services/GitHubAPIClient.d.ts +143 -0
- package/dist/services/GitHubAPIClient.js +167 -0
- package/dist/services/GitHubAuthService.d.ts +85 -0
- package/dist/services/GitHubAuthService.js +160 -0
- package/dist/services/WebhookValidator.d.ts +93 -0
- package/dist/services/WebhookValidator.js +123 -0
- package/dist/utils/error-utils.d.ts +67 -0
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/jwt-utils.d.ts +35 -0
- package/dist/utils/jwt-utils.js +67 -0
- package/dist/utils/state-utils.d.ts +38 -0
- package/dist/utils/state-utils.js +74 -0
- package/dist/utils/token-cache-utils.d.ts +47 -0
- package/dist/utils/token-cache-utils.js +74 -0
- package/dist/utils/webhook-signature-utils.d.ts +22 -0
- package/dist/utils/webhook-signature-utils.js +57 -0
- package/examples/basic-installation.ts +246 -0
- package/examples/create-issue.ts +392 -0
- package/examples/error-handling.ts +396 -0
- package/examples/multi-event-webhook.ts +367 -0
- package/examples/organization-installation.ts +316 -0
- package/examples/repository-access.ts +480 -0
- package/examples/webhook-handling.ts +343 -0
- package/examples/with-jwt-auth.ts +319 -0
- package/package.json +41 -0
- package/spec/core-utilities.spec.ts +243 -0
- package/spec/handlers.spec.ts +216 -0
- package/spec/helpers/reporter.ts +41 -0
- package/spec/integration-and-security.spec.ts +483 -0
- package/spec/plugin-core.spec.ts +258 -0
- package/spec/project-setup.spec.ts +56 -0
- package/spec/repos-and-schemas.spec.ts +288 -0
- package/spec/services.spec.ts +108 -0
- package/spec/support/jasmine.json +7 -0
- package/src/GitHubAppPlugin.ts +411 -0
- package/src/GitHubAppPluginContext.ts +254 -0
- package/src/GitHubAppPluginOptions.ts +412 -0
- package/src/handlers/InstallationCallback.ts +292 -0
- package/src/handlers/WebhookHandler.ts +179 -0
- package/src/index.ts +29 -0
- package/src/repos/GitHubAppSessionRepo.ts +36 -0
- package/src/repos/GitHubInstallationRepo.ts +95 -0
- package/src/repos/GitHubWebhookEventRepo.ts +48 -0
- package/src/schemas/GitHubAppSession.ts +13 -0
- package/src/schemas/GitHubInstallation.ts +28 -0
- package/src/schemas/InstallationCallbackRequest.ts +10 -0
- package/src/schemas/WebhookEvent.ts +16 -0
- package/src/schemas/WebhookPayload.ts +35 -0
- package/src/services/GitHubAPIClient.ts +244 -0
- package/src/services/GitHubAuthService.ts +188 -0
- package/src/services/WebhookValidator.ts +159 -0
- package/src/utils/error-utils.ts +148 -0
- package/src/utils/jwt-utils.ts +64 -0
- package/src/utils/state-utils.ts +72 -0
- package/src/utils/token-cache-utils.ts +89 -0
- package/src/utils/webhook-signature-utils.ts +57 -0
- package/tsconfig.dist.json +4 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Access Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Accessing user's GitHub repositories via API client
|
|
6
|
+
* - Fetching repository contents
|
|
7
|
+
* - Checking repository permissions
|
|
8
|
+
* - Listing branches and commits
|
|
9
|
+
* - Verifying user has access to specific repositories
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { FlinkApp, FlinkContext, GetHandler } from "@flink-app/flink";
|
|
13
|
+
import { githubAppPlugin } from "@flink-app/github-app-plugin";
|
|
14
|
+
|
|
15
|
+
interface AppContext extends FlinkContext {
|
|
16
|
+
plugins: {
|
|
17
|
+
githubApp: any;
|
|
18
|
+
};
|
|
19
|
+
session?: {
|
|
20
|
+
userId?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function start() {
|
|
25
|
+
const app = new FlinkApp<AppContext>({
|
|
26
|
+
name: "GitHub App Repository Access Example",
|
|
27
|
+
port: 3333,
|
|
28
|
+
|
|
29
|
+
db: {
|
|
30
|
+
uri: process.env.MONGODB_URI || "mongodb://localhost:27017/github-app-repo-example",
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
plugins: [
|
|
34
|
+
githubAppPlugin({
|
|
35
|
+
appId: process.env.GITHUB_APP_ID!,
|
|
36
|
+
privateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
|
|
37
|
+
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
|
|
38
|
+
clientId: process.env.GITHUB_APP_CLIENT_ID!,
|
|
39
|
+
clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
|
|
40
|
+
|
|
41
|
+
onInstallationSuccess: async ({ installationId, repositories, account }) => {
|
|
42
|
+
const userId = "demo-user-123";
|
|
43
|
+
|
|
44
|
+
console.log(`\nInstallation successful!`);
|
|
45
|
+
console.log(`Account: ${account.login}`);
|
|
46
|
+
console.log(`Repositories: ${repositories.length}`);
|
|
47
|
+
|
|
48
|
+
return { userId, redirectUrl: "/repos" };
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await app.start();
|
|
55
|
+
|
|
56
|
+
console.log(`
|
|
57
|
+
=================================
|
|
58
|
+
Repository Access Example Started
|
|
59
|
+
=================================
|
|
60
|
+
|
|
61
|
+
Available endpoints:
|
|
62
|
+
|
|
63
|
+
1. GET /repos/list
|
|
64
|
+
List all accessible repositories
|
|
65
|
+
|
|
66
|
+
2. GET /repos/:owner/:repo
|
|
67
|
+
Get repository details
|
|
68
|
+
|
|
69
|
+
3. GET /repos/:owner/:repo/contents?path=README.md
|
|
70
|
+
Get file contents
|
|
71
|
+
|
|
72
|
+
4. GET /repos/:owner/:repo/check-access
|
|
73
|
+
Check if user has access to repository
|
|
74
|
+
|
|
75
|
+
5. GET /repos/:owner/:repo/branches
|
|
76
|
+
List repository branches
|
|
77
|
+
|
|
78
|
+
6. GET /repos/:owner/:repo/commits?branch=main
|
|
79
|
+
List recent commits
|
|
80
|
+
|
|
81
|
+
Install the GitHub App first:
|
|
82
|
+
http://localhost:3333/github-app/install?user_id=demo-user-123
|
|
83
|
+
|
|
84
|
+
=================================
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handler: List all accessible repositories
|
|
89
|
+
const ListRepositories: GetHandler = async ({ ctx }) => {
|
|
90
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Get user's installation
|
|
94
|
+
const installation = await ctx.plugins.githubApp.getInstallation(userId);
|
|
95
|
+
|
|
96
|
+
if (!installation) {
|
|
97
|
+
return {
|
|
98
|
+
status: 404,
|
|
99
|
+
data: {
|
|
100
|
+
error: "github-app-not-installed",
|
|
101
|
+
message: "Please install the GitHub App first",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get API client
|
|
107
|
+
const client = await ctx.plugins.githubApp.getClient(installation.installationId);
|
|
108
|
+
|
|
109
|
+
// Fetch repositories
|
|
110
|
+
const repos = await client.getRepositories();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
status: 200,
|
|
114
|
+
data: {
|
|
115
|
+
installation: {
|
|
116
|
+
account: installation.accountLogin,
|
|
117
|
+
type: installation.accountType,
|
|
118
|
+
},
|
|
119
|
+
repositories: repos.map((repo: any) => ({
|
|
120
|
+
id: repo.id,
|
|
121
|
+
name: repo.name,
|
|
122
|
+
fullName: repo.full_name,
|
|
123
|
+
private: repo.private,
|
|
124
|
+
url: repo.html_url,
|
|
125
|
+
description: repo.description,
|
|
126
|
+
language: repo.language,
|
|
127
|
+
stargazersCount: repo.stargazers_count,
|
|
128
|
+
forksCount: repo.forks_count,
|
|
129
|
+
defaultBranch: repo.default_branch,
|
|
130
|
+
updatedAt: repo.updated_at,
|
|
131
|
+
})),
|
|
132
|
+
total: repos.length,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
} catch (error: any) {
|
|
136
|
+
console.error("Failed to list repositories:", error);
|
|
137
|
+
return {
|
|
138
|
+
status: 500,
|
|
139
|
+
data: {
|
|
140
|
+
error: "repository-access-failed",
|
|
141
|
+
message: error.message,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export { ListRepositories };
|
|
148
|
+
|
|
149
|
+
// Handler: Get repository details
|
|
150
|
+
const GetRepository: GetHandler<any, any, { owner: string; repo: string }> = async ({ ctx, params }) => {
|
|
151
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
152
|
+
const { owner, repo } = params;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// Check if user has access
|
|
156
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess(userId, owner, repo);
|
|
157
|
+
|
|
158
|
+
if (!hasAccess) {
|
|
159
|
+
return {
|
|
160
|
+
status: 403,
|
|
161
|
+
data: {
|
|
162
|
+
error: "access-denied",
|
|
163
|
+
message: "You do not have access to this repository",
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Get installation
|
|
169
|
+
const installation = await ctx.plugins.githubApp.getInstallation(userId);
|
|
170
|
+
if (!installation) {
|
|
171
|
+
return {
|
|
172
|
+
status: 404,
|
|
173
|
+
data: { error: "installation-not-found" },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get API client
|
|
178
|
+
const client = await ctx.plugins.githubApp.getClient(installation.installationId);
|
|
179
|
+
|
|
180
|
+
// Fetch repository details
|
|
181
|
+
const repository = await client.getRepository(owner, repo);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
status: 200,
|
|
185
|
+
data: {
|
|
186
|
+
id: repository.id,
|
|
187
|
+
name: repository.name,
|
|
188
|
+
fullName: repository.full_name,
|
|
189
|
+
private: repository.private,
|
|
190
|
+
description: repository.description,
|
|
191
|
+
url: repository.html_url,
|
|
192
|
+
cloneUrl: repository.clone_url,
|
|
193
|
+
language: repository.language,
|
|
194
|
+
defaultBranch: repository.default_branch,
|
|
195
|
+
stargazersCount: repository.stargazers_count,
|
|
196
|
+
watchersCount: repository.watchers_count,
|
|
197
|
+
forksCount: repository.forks_count,
|
|
198
|
+
openIssuesCount: repository.open_issues_count,
|
|
199
|
+
license: repository.license?.name,
|
|
200
|
+
topics: repository.topics,
|
|
201
|
+
createdAt: repository.created_at,
|
|
202
|
+
updatedAt: repository.updated_at,
|
|
203
|
+
pushedAt: repository.pushed_at,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
} catch (error: any) {
|
|
207
|
+
console.error("Failed to get repository:", error);
|
|
208
|
+
return {
|
|
209
|
+
status: 500,
|
|
210
|
+
data: {
|
|
211
|
+
error: "repository-access-failed",
|
|
212
|
+
message: error.message,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export { GetRepository };
|
|
219
|
+
|
|
220
|
+
// Handler: Get file contents
|
|
221
|
+
const GetContents: GetHandler<any, any, { owner: string; repo: string }, { path?: string }> = async ({
|
|
222
|
+
ctx,
|
|
223
|
+
params,
|
|
224
|
+
query,
|
|
225
|
+
}) => {
|
|
226
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
227
|
+
const { owner, repo } = params;
|
|
228
|
+
const { path = "" } = query;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
// Check access
|
|
232
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess(userId, owner, repo);
|
|
233
|
+
|
|
234
|
+
if (!hasAccess) {
|
|
235
|
+
return {
|
|
236
|
+
status: 403,
|
|
237
|
+
data: {
|
|
238
|
+
error: "access-denied",
|
|
239
|
+
message: "You do not have access to this repository",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Get installation
|
|
245
|
+
const installation = await ctx.plugins.githubApp.getInstallation(userId);
|
|
246
|
+
if (!installation) {
|
|
247
|
+
return {
|
|
248
|
+
status: 404,
|
|
249
|
+
data: { error: "installation-not-found" },
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Get API client
|
|
254
|
+
const client = await ctx.plugins.githubApp.getClient(installation.installationId);
|
|
255
|
+
|
|
256
|
+
// Fetch contents
|
|
257
|
+
const contents = await client.getContents(owner, repo, path);
|
|
258
|
+
|
|
259
|
+
// Handle directory vs file
|
|
260
|
+
if (Array.isArray(contents)) {
|
|
261
|
+
// Directory listing
|
|
262
|
+
return {
|
|
263
|
+
status: 200,
|
|
264
|
+
data: {
|
|
265
|
+
type: "directory",
|
|
266
|
+
path,
|
|
267
|
+
items: contents.map((item: any) => ({
|
|
268
|
+
name: item.name,
|
|
269
|
+
path: item.path,
|
|
270
|
+
type: item.type,
|
|
271
|
+
size: item.size,
|
|
272
|
+
sha: item.sha,
|
|
273
|
+
url: item.html_url,
|
|
274
|
+
})),
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
} else {
|
|
278
|
+
// Single file
|
|
279
|
+
const file = contents as any;
|
|
280
|
+
let content = file.content;
|
|
281
|
+
|
|
282
|
+
// Decode base64 content if present
|
|
283
|
+
if (file.encoding === "base64" && content) {
|
|
284
|
+
content = Buffer.from(content, "base64").toString("utf-8");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
status: 200,
|
|
289
|
+
data: {
|
|
290
|
+
type: "file",
|
|
291
|
+
name: file.name,
|
|
292
|
+
path: file.path,
|
|
293
|
+
sha: file.sha,
|
|
294
|
+
size: file.size,
|
|
295
|
+
encoding: file.encoding,
|
|
296
|
+
content,
|
|
297
|
+
url: file.html_url,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
} catch (error: any) {
|
|
302
|
+
console.error("Failed to get contents:", error);
|
|
303
|
+
return {
|
|
304
|
+
status: 500,
|
|
305
|
+
data: {
|
|
306
|
+
error: "contents-access-failed",
|
|
307
|
+
message: error.message,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export { GetContents };
|
|
314
|
+
|
|
315
|
+
// Handler: Check repository access
|
|
316
|
+
const CheckAccess: GetHandler<any, any, { owner: string; repo: string }> = async ({ ctx, params }) => {
|
|
317
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
318
|
+
const { owner, repo } = params;
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess(userId, owner, repo);
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
status: 200,
|
|
325
|
+
data: {
|
|
326
|
+
owner,
|
|
327
|
+
repo,
|
|
328
|
+
hasAccess,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
} catch (error: any) {
|
|
332
|
+
console.error("Failed to check access:", error);
|
|
333
|
+
return {
|
|
334
|
+
status: 500,
|
|
335
|
+
data: {
|
|
336
|
+
error: "access-check-failed",
|
|
337
|
+
message: error.message,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
export { CheckAccess };
|
|
344
|
+
|
|
345
|
+
// Handler: List branches
|
|
346
|
+
const ListBranches: GetHandler<any, any, { owner: string; repo: string }> = async ({ ctx, params }) => {
|
|
347
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
348
|
+
const { owner, repo } = params;
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
// Check access
|
|
352
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess(userId, owner, repo);
|
|
353
|
+
|
|
354
|
+
if (!hasAccess) {
|
|
355
|
+
return {
|
|
356
|
+
status: 403,
|
|
357
|
+
data: { error: "access-denied" },
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Get installation and client
|
|
362
|
+
const installation = await ctx.plugins.githubApp.getInstallation(userId);
|
|
363
|
+
if (!installation) {
|
|
364
|
+
return {
|
|
365
|
+
status: 404,
|
|
366
|
+
data: { error: "installation-not-found" },
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const client = await ctx.plugins.githubApp.getClient(installation.installationId);
|
|
371
|
+
|
|
372
|
+
// Fetch branches
|
|
373
|
+
const branches = await client.request("GET", `/repos/${owner}/${repo}/branches`);
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
status: 200,
|
|
377
|
+
data: {
|
|
378
|
+
branches: branches.map((branch: any) => ({
|
|
379
|
+
name: branch.name,
|
|
380
|
+
protected: branch.protected,
|
|
381
|
+
commit: {
|
|
382
|
+
sha: branch.commit.sha,
|
|
383
|
+
url: branch.commit.url,
|
|
384
|
+
},
|
|
385
|
+
})),
|
|
386
|
+
total: branches.length,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
} catch (error: any) {
|
|
390
|
+
console.error("Failed to list branches:", error);
|
|
391
|
+
return {
|
|
392
|
+
status: 500,
|
|
393
|
+
data: {
|
|
394
|
+
error: "branches-access-failed",
|
|
395
|
+
message: error.message,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export { ListBranches };
|
|
402
|
+
|
|
403
|
+
// Handler: List commits
|
|
404
|
+
const ListCommits: GetHandler<any, any, { owner: string; repo: string }, { branch?: string; limit?: string }> = async ({
|
|
405
|
+
ctx,
|
|
406
|
+
params,
|
|
407
|
+
query,
|
|
408
|
+
}) => {
|
|
409
|
+
const userId = ctx.session?.userId || "demo-user-123";
|
|
410
|
+
const { owner, repo } = params;
|
|
411
|
+
const { branch = "main", limit = "10" } = query;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
// Check access
|
|
415
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess(userId, owner, repo);
|
|
416
|
+
|
|
417
|
+
if (!hasAccess) {
|
|
418
|
+
return {
|
|
419
|
+
status: 403,
|
|
420
|
+
data: { error: "access-denied" },
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Get installation and client
|
|
425
|
+
const installation = await ctx.plugins.githubApp.getInstallation(userId);
|
|
426
|
+
if (!installation) {
|
|
427
|
+
return {
|
|
428
|
+
status: 404,
|
|
429
|
+
data: { error: "installation-not-found" },
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const client = await ctx.plugins.githubApp.getClient(installation.installationId);
|
|
434
|
+
|
|
435
|
+
// Fetch commits
|
|
436
|
+
const commits = await client.request("GET", `/repos/${owner}/${repo}/commits`, {
|
|
437
|
+
sha: branch,
|
|
438
|
+
per_page: parseInt(limit, 10),
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
status: 200,
|
|
443
|
+
data: {
|
|
444
|
+
branch,
|
|
445
|
+
commits: commits.map((commit: any) => ({
|
|
446
|
+
sha: commit.sha,
|
|
447
|
+
message: commit.commit.message,
|
|
448
|
+
author: {
|
|
449
|
+
name: commit.commit.author.name,
|
|
450
|
+
email: commit.commit.author.email,
|
|
451
|
+
date: commit.commit.author.date,
|
|
452
|
+
},
|
|
453
|
+
committer: {
|
|
454
|
+
name: commit.commit.committer.name,
|
|
455
|
+
date: commit.commit.committer.date,
|
|
456
|
+
},
|
|
457
|
+
url: commit.html_url,
|
|
458
|
+
})),
|
|
459
|
+
total: commits.length,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
console.error("Failed to list commits:", error);
|
|
464
|
+
return {
|
|
465
|
+
status: 500,
|
|
466
|
+
data: {
|
|
467
|
+
error: "commits-access-failed",
|
|
468
|
+
message: error.message,
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
export { ListCommits };
|
|
475
|
+
|
|
476
|
+
// Start application
|
|
477
|
+
start().catch((error) => {
|
|
478
|
+
console.error("Failed to start application:", error);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
});
|