@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 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.42",
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": "e7a3e35c85ee9cd1eaa9629664460a1936f0698a"
40
+ "gitHead": "e5fc78243a97075ce0272f287f3f89fd44681715"
41
41
  }
@@ -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
  *