@flink-app/github-app-plugin 0.12.1-alpha.42 → 0.12.1-alpha.43
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 +0 -53
- package/dist/services/GitHubAPIClient.d.ts +0 -21
- package/dist/services/GitHubAPIClient.js +0 -23
- package/package.json +2 -2
- package/spec/services.spec.ts +0 -166
- package/src/services/GitHubAPIClient.ts +0 -24
package/README.md
CHANGED
|
@@ -417,63 +417,10 @@ const issue = await client.createIssue("facebook", "react", {
|
|
|
417
417
|
body: "Found a bug...",
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
-
// Remove repository from installation
|
|
421
|
-
await client.removeRepository(987654321);
|
|
422
|
-
|
|
423
420
|
// Generic API call
|
|
424
421
|
const response = await client.request("GET", "/rate_limit");
|
|
425
422
|
```
|
|
426
423
|
|
|
427
|
-
### Removing Repositories from Installation
|
|
428
|
-
|
|
429
|
-
You can remove a repository from a GitHub App installation to revoke access to that specific repository while keeping the installation active for other repositories:
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
const client = await ctx.plugins.githubApp.getClient(installationId);
|
|
433
|
-
|
|
434
|
-
// Remove repository by ID (numeric ID, not name)
|
|
435
|
-
await client.removeRepository(987654321);
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
**Important Notes:**
|
|
439
|
-
- Use the numeric repository ID, not the repository name or full_name
|
|
440
|
-
- This only removes the repository from the installation; it does not delete the repository itself
|
|
441
|
-
- The installation remains active for other repositories
|
|
442
|
-
- If the repository was already removed manually, the API will return a 404 error
|
|
443
|
-
- Requires appropriate installation permissions
|
|
444
|
-
|
|
445
|
-
**Example: Repository Disconnect Handler**
|
|
446
|
-
|
|
447
|
-
```typescript
|
|
448
|
-
// In a Flink handler (e.g., DeleteRepository.ts)
|
|
449
|
-
const DeleteRepository: Handler = async ({ ctx, req }) => {
|
|
450
|
-
const { id } = req.params;
|
|
451
|
-
|
|
452
|
-
// Fetch repository from database
|
|
453
|
-
const repository = await ctx.repos.repositoryRepo.getById(id);
|
|
454
|
-
if (!repository) {
|
|
455
|
-
return notFound(`Repository with id ${id} not found`);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Remove repository from GitHub App installation
|
|
459
|
-
try {
|
|
460
|
-
const client = await ctx.plugins.githubApp.getClient(
|
|
461
|
-
repository.githubInstallationId
|
|
462
|
-
);
|
|
463
|
-
await client.removeRepository(repository.githubRepoId);
|
|
464
|
-
} catch (error) {
|
|
465
|
-
console.error('Failed to remove repository from GitHub:', error);
|
|
466
|
-
// Continue with local cleanup even if GitHub API call fails
|
|
467
|
-
// (repository may have already been removed manually)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Update database status
|
|
471
|
-
await ctx.repos.repositoryRepo.updateStatus(id, "disconnected");
|
|
472
|
-
|
|
473
|
-
return { data: { success: true } };
|
|
474
|
-
};
|
|
475
|
-
```
|
|
476
|
-
|
|
477
424
|
## Authentication Integration
|
|
478
425
|
|
|
479
426
|
This plugin is **auth-agnostic** and works with any authentication system. You implement your own installation callback handler with your own auth logic.
|
|
@@ -125,27 +125,6 @@ export declare class GitHubAPIClient {
|
|
|
125
125
|
* @returns Created issue
|
|
126
126
|
*/
|
|
127
127
|
createIssue(owner: string, repo: string, params: CreateIssueParams): Promise<Issue>;
|
|
128
|
-
/**
|
|
129
|
-
* Remove a repository from this installation
|
|
130
|
-
*
|
|
131
|
-
* Revokes the GitHub App's access to a specific repository while keeping
|
|
132
|
-
* the installation active for other repositories.
|
|
133
|
-
*
|
|
134
|
-
* Note: This requires the installation to have been granted access to the
|
|
135
|
-
* repository. If the app was installed at the account level with access
|
|
136
|
-
* to all repositories, this method can be used to selectively revoke
|
|
137
|
-
* access to specific repositories.
|
|
138
|
-
*
|
|
139
|
-
* @param repositoryId - GitHub repository ID (numeric ID, not full_name)
|
|
140
|
-
* @throws Error if repository not found or access cannot be revoked
|
|
141
|
-
*
|
|
142
|
-
* @example
|
|
143
|
-
* ```typescript
|
|
144
|
-
* const client = await ctx.plugins.githubApp.getClient(12345);
|
|
145
|
-
* await client.removeRepository(987654321);
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
removeRepository(repositoryId: number): Promise<void>;
|
|
149
128
|
/**
|
|
150
129
|
* Sleep utility for retry delays
|
|
151
130
|
*
|
|
@@ -136,29 +136,6 @@ class GitHubAPIClient {
|
|
|
136
136
|
async createIssue(owner, repo, params) {
|
|
137
137
|
return this.request("POST", `/repos/${owner}/${repo}/issues`, params);
|
|
138
138
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Remove a repository from this installation
|
|
141
|
-
*
|
|
142
|
-
* Revokes the GitHub App's access to a specific repository while keeping
|
|
143
|
-
* the installation active for other repositories.
|
|
144
|
-
*
|
|
145
|
-
* Note: This requires the installation to have been granted access to the
|
|
146
|
-
* repository. If the app was installed at the account level with access
|
|
147
|
-
* to all repositories, this method can be used to selectively revoke
|
|
148
|
-
* access to specific repositories.
|
|
149
|
-
*
|
|
150
|
-
* @param repositoryId - GitHub repository ID (numeric ID, not full_name)
|
|
151
|
-
* @throws Error if repository not found or access cannot be revoked
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```typescript
|
|
155
|
-
* const client = await ctx.plugins.githubApp.getClient(12345);
|
|
156
|
-
* await client.removeRepository(987654321);
|
|
157
|
-
* ```
|
|
158
|
-
*/
|
|
159
|
-
async removeRepository(repositoryId) {
|
|
160
|
-
await this.request("DELETE", `/user/installations/${this.installationId}/repositories/${repositoryId}`);
|
|
161
|
-
}
|
|
162
139
|
/**
|
|
163
140
|
* Sleep utility for retry delays
|
|
164
141
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flink-app/github-app-plugin",
|
|
3
|
-
"version": "0.12.1-alpha.
|
|
3
|
+
"version": "0.12.1-alpha.43",
|
|
4
4
|
"description": "Flink plugin for GitHub App integration with installation management and webhook handling",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --preserve-symlinks -r ts-node/register -- node_modules/jasmine/bin/jasmine --config=./spec/support/jasmine.json",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"tsc-watch": "^4.2.9",
|
|
38
38
|
"typescript": "5.4.5"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "e5fc78243a97075ce0272f287f3f89fd44681715"
|
|
41
41
|
}
|
package/spec/services.spec.ts
CHANGED
|
@@ -52,172 +52,6 @@ describe("Services", () => {
|
|
|
52
52
|
expect(apiClient).toBeTruthy();
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
describe("removeRepository", () => {
|
|
56
|
-
it("should construct correct DELETE request", () => {
|
|
57
|
-
const appId = "123456";
|
|
58
|
-
const installationId = 12345;
|
|
59
|
-
const repositoryId = 987654321;
|
|
60
|
-
const authService = new GitHubAuthService(appId, testPrivateKeyBase64, "https://api.github.com");
|
|
61
|
-
const apiClient = new GitHubAPIClient(installationId, authService);
|
|
62
|
-
|
|
63
|
-
// Test that the method exists and has the correct signature
|
|
64
|
-
expect(typeof apiClient.removeRepository).toBe("function");
|
|
65
|
-
expect(apiClient.removeRepository.length).toBe(1);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Note: Testing actual API calls with mocked responses
|
|
69
|
-
it("should handle successful removal (204 No Content)", async () => {
|
|
70
|
-
const appId = "123456";
|
|
71
|
-
const installationId = 12345;
|
|
72
|
-
const repositoryId = 987654321;
|
|
73
|
-
const authService = new GitHubAuthService(appId, testPrivateKeyBase64, "https://api.github.com");
|
|
74
|
-
const apiClient = new GitHubAPIClient(installationId, authService);
|
|
75
|
-
|
|
76
|
-
// Mock successful installation token retrieval
|
|
77
|
-
spyOn(authService, "getInstallationToken").and.returnValue(Promise.resolve("test-token"));
|
|
78
|
-
|
|
79
|
-
// Mock fetch to return 204 No Content
|
|
80
|
-
const originalFetch = global.fetch;
|
|
81
|
-
global.fetch = jasmine.createSpy("fetch").and.returnValue(
|
|
82
|
-
Promise.resolve({
|
|
83
|
-
ok: true,
|
|
84
|
-
status: 204,
|
|
85
|
-
headers: new Headers(),
|
|
86
|
-
} as Response)
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
await apiClient.removeRepository(repositoryId);
|
|
91
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
92
|
-
`https://api.github.com/user/installations/${installationId}/repositories/${repositoryId}`,
|
|
93
|
-
jasmine.objectContaining({
|
|
94
|
-
method: "DELETE",
|
|
95
|
-
headers: jasmine.objectContaining({
|
|
96
|
-
Authorization: "Bearer test-token",
|
|
97
|
-
}),
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
} finally {
|
|
101
|
-
global.fetch = originalFetch;
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("should handle repository not found error (404)", async () => {
|
|
106
|
-
const appId = "123456";
|
|
107
|
-
const installationId = 12345;
|
|
108
|
-
const repositoryId = 999999999;
|
|
109
|
-
const authService = new GitHubAuthService(appId, testPrivateKeyBase64, "https://api.github.com");
|
|
110
|
-
const apiClient = new GitHubAPIClient(installationId, authService);
|
|
111
|
-
|
|
112
|
-
// Mock installation token retrieval
|
|
113
|
-
spyOn(authService, "getInstallationToken").and.returnValue(Promise.resolve("test-token"));
|
|
114
|
-
|
|
115
|
-
// Mock fetch to return 404
|
|
116
|
-
const originalFetch = global.fetch;
|
|
117
|
-
global.fetch = jasmine.createSpy("fetch").and.returnValue(
|
|
118
|
-
Promise.resolve({
|
|
119
|
-
ok: false,
|
|
120
|
-
status: 404,
|
|
121
|
-
statusText: "Not Found",
|
|
122
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Repository not found" })),
|
|
123
|
-
headers: new Headers(),
|
|
124
|
-
} as any)
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
let errorThrown = false;
|
|
129
|
-
try {
|
|
130
|
-
await apiClient.removeRepository(repositoryId);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
errorThrown = true;
|
|
133
|
-
}
|
|
134
|
-
expect(errorThrown).toBe(true);
|
|
135
|
-
} finally {
|
|
136
|
-
global.fetch = originalFetch;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("should handle forbidden error (403)", async () => {
|
|
141
|
-
const appId = "123456";
|
|
142
|
-
const installationId = 12345;
|
|
143
|
-
const repositoryId = 987654321;
|
|
144
|
-
const authService = new GitHubAuthService(appId, testPrivateKeyBase64, "https://api.github.com");
|
|
145
|
-
const apiClient = new GitHubAPIClient(installationId, authService);
|
|
146
|
-
|
|
147
|
-
// Mock installation token retrieval
|
|
148
|
-
spyOn(authService, "getInstallationToken").and.returnValue(Promise.resolve("test-token"));
|
|
149
|
-
|
|
150
|
-
let callCount = 0;
|
|
151
|
-
// Mock fetch to return 403 and track retries
|
|
152
|
-
const originalFetch = global.fetch;
|
|
153
|
-
global.fetch = jasmine.createSpy("fetch").and.callFake(() => {
|
|
154
|
-
callCount++;
|
|
155
|
-
return Promise.resolve({
|
|
156
|
-
ok: false,
|
|
157
|
-
status: 403,
|
|
158
|
-
statusText: "Forbidden",
|
|
159
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Insufficient permissions" })),
|
|
160
|
-
headers: new Headers({ "x-ratelimit-remaining": "100" }),
|
|
161
|
-
} as any);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
let errorThrown = false;
|
|
166
|
-
try {
|
|
167
|
-
await apiClient.removeRepository(repositoryId);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
errorThrown = true;
|
|
170
|
-
}
|
|
171
|
-
expect(errorThrown).toBe(true);
|
|
172
|
-
// Should have retried due to 403 status
|
|
173
|
-
expect(callCount).toBeGreaterThan(1);
|
|
174
|
-
} finally {
|
|
175
|
-
global.fetch = originalFetch;
|
|
176
|
-
}
|
|
177
|
-
}, 10000); // Increase timeout since it will retry
|
|
178
|
-
|
|
179
|
-
it("should retry on rate limit (403 with rate limit)", async () => {
|
|
180
|
-
const appId = "123456";
|
|
181
|
-
const installationId = 12345;
|
|
182
|
-
const repositoryId = 987654321;
|
|
183
|
-
const authService = new GitHubAuthService(appId, testPrivateKeyBase64, "https://api.github.com");
|
|
184
|
-
const apiClient = new GitHubAPIClient(installationId, authService);
|
|
185
|
-
|
|
186
|
-
// Mock installation token retrieval
|
|
187
|
-
spyOn(authService, "getInstallationToken").and.returnValue(Promise.resolve("test-token"));
|
|
188
|
-
|
|
189
|
-
let callCount = 0;
|
|
190
|
-
const originalFetch = global.fetch;
|
|
191
|
-
global.fetch = jasmine.createSpy("fetch").and.callFake(() => {
|
|
192
|
-
callCount++;
|
|
193
|
-
if (callCount === 1) {
|
|
194
|
-
// First call: rate limited
|
|
195
|
-
return Promise.resolve({
|
|
196
|
-
ok: false,
|
|
197
|
-
status: 403,
|
|
198
|
-
statusText: "Forbidden",
|
|
199
|
-
text: () => Promise.resolve(JSON.stringify({ message: "Rate limit exceeded" })),
|
|
200
|
-
headers: new Headers({ "x-ratelimit-remaining": "0" }),
|
|
201
|
-
} as Response);
|
|
202
|
-
} else {
|
|
203
|
-
// Second call: success
|
|
204
|
-
return Promise.resolve({
|
|
205
|
-
ok: true,
|
|
206
|
-
status: 204,
|
|
207
|
-
headers: new Headers(),
|
|
208
|
-
} as Response);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
await apiClient.removeRepository(repositoryId);
|
|
214
|
-
expect(callCount).toBe(2); // Should have retried
|
|
215
|
-
} finally {
|
|
216
|
-
global.fetch = originalFetch;
|
|
217
|
-
}
|
|
218
|
-
}, 10000); // Increase timeout for retry delays
|
|
219
|
-
});
|
|
220
|
-
|
|
221
55
|
// Note: Testing actual API calls requires mocking GitHub API
|
|
222
56
|
// These are tested in integration tests instead
|
|
223
57
|
});
|
|
@@ -211,30 +211,6 @@ export class GitHubAPIClient {
|
|
|
211
211
|
return this.request<Issue>("POST", `/repos/${owner}/${repo}/issues`, params);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
/**
|
|
215
|
-
* Remove a repository from this installation
|
|
216
|
-
*
|
|
217
|
-
* Revokes the GitHub App's access to a specific repository while keeping
|
|
218
|
-
* the installation active for other repositories.
|
|
219
|
-
*
|
|
220
|
-
* Note: This requires the installation to have been granted access to the
|
|
221
|
-
* repository. If the app was installed at the account level with access
|
|
222
|
-
* to all repositories, this method can be used to selectively revoke
|
|
223
|
-
* access to specific repositories.
|
|
224
|
-
*
|
|
225
|
-
* @param repositoryId - GitHub repository ID (numeric ID, not full_name)
|
|
226
|
-
* @throws Error if repository not found or access cannot be revoked
|
|
227
|
-
*
|
|
228
|
-
* @example
|
|
229
|
-
* ```typescript
|
|
230
|
-
* const client = await ctx.plugins.githubApp.getClient(12345);
|
|
231
|
-
* await client.removeRepository(987654321);
|
|
232
|
-
* ```
|
|
233
|
-
*/
|
|
234
|
-
async removeRepository(repositoryId: number): Promise<void> {
|
|
235
|
-
await this.request<void>("DELETE", `/user/installations/${this.installationId}/repositories/${repositoryId}`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
214
|
/**
|
|
239
215
|
* Sleep utility for retry delays
|
|
240
216
|
*
|