@flink-app/github-app-plugin 0.12.1-alpha.38 → 0.12.1-alpha.40

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.
@@ -53,12 +53,6 @@ describe("Integration and Security Tests", () => {
53
53
  webhookSecret: "test-webhook-secret",
54
54
  clientId: "test-client-id",
55
55
  clientSecret: "test-client-secret",
56
- onInstallationSuccess: async ({ installationId, repositories, account }) => {
57
- return {
58
- userId: "test-user-id",
59
- redirectUrl: "/dashboard",
60
- };
61
- },
62
56
  onWebhookEvent: async ({ event, action, payload, installationId }, ctx) => {
63
57
  // Webhook event handler
64
58
  },
@@ -88,8 +82,8 @@ describe("Integration and Security Tests", () => {
88
82
  });
89
83
 
90
84
  describe("Integration Test: Full Installation Flow", () => {
91
- it("should complete full installation flow using context methods", async () => {
92
- // Step 1: Initiate installation using context method
85
+ it("should initiate installation and generate state", async () => {
86
+ // Test initiation only (callback must be implemented by host app)
93
87
  const { redirectUrl, state } = await app.ctx.plugins.githubApp.initiateInstallation({
94
88
  userId: "test-user",
95
89
  metadata: { source: "test" },
@@ -98,6 +92,14 @@ describe("Integration and Security Tests", () => {
98
92
  expect(redirectUrl).toContain("github.com/apps/test-app/installations/new");
99
93
  expect(redirectUrl).toContain("state=");
100
94
  expect(state).toBeTruthy();
95
+ });
96
+
97
+ it("should complete installation using completeInstallation method", async () => {
98
+ // Step 1: Initiate installation
99
+ const { state } = await app.ctx.plugins.githubApp.initiateInstallation({
100
+ userId: "test-user",
101
+ metadata: { source: "test" },
102
+ });
101
103
 
102
104
  // Step 2: Mock GitHub API responses
103
105
  global.fetch = jasmine.createSpy("fetch").and.returnValues(
@@ -140,17 +142,17 @@ describe("Integration and Security Tests", () => {
140
142
  } as any)
141
143
  );
142
144
 
143
- // Step 3: Complete callback
144
- const callbackResponse = await http.get("/github-app/callback", {
145
- qs: {
146
- installation_id: "12345",
147
- setup_action: "install",
148
- state: state!,
149
- },
145
+ // Step 3: Complete installation using plugin method
146
+ const result = await app.ctx.plugins.githubApp.completeInstallation({
147
+ installationId: 12345,
148
+ state: state!,
149
+ userId: "test-user-id",
150
150
  });
151
151
 
152
- expect(callbackResponse.status).toBe(302);
153
- expect(callbackResponse.headers?.Location).toBe("/dashboard");
152
+ expect(result.success).toBe(true);
153
+ expect(result.installation).toBeDefined();
154
+ expect(result.installation?.installationId).toBe(12345);
155
+ expect(result.installation?.accountLogin).toBe("testuser");
154
156
 
155
157
  // Step 4: Verify installation was stored
156
158
  const installation = await app.ctx.plugins.githubApp.getInstallation("test-user-id");
@@ -161,20 +163,19 @@ describe("Integration and Security Tests", () => {
161
163
  });
162
164
 
163
165
  describe("Security Test: CSRF State Validation", () => {
164
- it("should prevent replay attacks with invalid state", async () => {
166
+ it("should prevent replay attacks with invalid state via completeInstallation", async () => {
165
167
  const invalidState = generateState();
166
168
 
167
169
  global.fetch = jasmine.createSpy("fetch");
168
170
 
169
- const response = await http.get("/github-app/callback", {
170
- qs: {
171
- installation_id: "12345",
172
- setup_action: "install",
173
- state: invalidState,
174
- },
171
+ const result = await app.ctx.plugins.githubApp.completeInstallation({
172
+ installationId: 12345,
173
+ state: invalidState,
174
+ userId: "test-user",
175
175
  });
176
176
 
177
- expect(response.status).toBe(400);
177
+ expect(result.success).toBe(false);
178
+ expect(result.error?.code).toBe("session-expired");
178
179
  expect(global.fetch).not.toHaveBeenCalled();
179
180
  });
180
181
 
@@ -211,27 +212,24 @@ describe("Integration and Security Tests", () => {
211
212
  } as any)
212
213
  );
213
214
 
214
- // Step 2: Complete callback first time (should succeed)
215
- const firstCallback = await http.get("/github-app/callback", {
216
- qs: {
217
- installation_id: "12345",
218
- setup_action: "install",
219
- state: state!,
220
- },
215
+ // Step 2: Complete installation first time (should succeed)
216
+ const firstResult = await app.ctx.plugins.githubApp.completeInstallation({
217
+ installationId: 12345,
218
+ state: state!,
219
+ userId: "test-user-id",
221
220
  });
222
221
 
223
- expect(firstCallback.status).toBe(302);
222
+ expect(firstResult.success).toBe(true);
224
223
 
225
224
  // Step 3: Try to reuse same state (should fail)
226
- const secondCallback = await http.get("/github-app/callback", {
227
- qs: {
228
- installation_id: "12345",
229
- setup_action: "install",
230
- state: state!,
231
- },
225
+ const secondResult = await app.ctx.plugins.githubApp.completeInstallation({
226
+ installationId: 12345,
227
+ state: state!,
228
+ userId: "test-user-id",
232
229
  });
233
230
 
234
- expect(secondCallback.status).toBe(400);
231
+ expect(secondResult.success).toBe(false);
232
+ expect(secondResult.error?.code).toBe("session-expired");
235
233
  });
236
234
  });
237
235
 
@@ -399,16 +397,16 @@ describe("Integration and Security Tests", () => {
399
397
  } as any)
400
398
  );
401
399
 
402
- const callbackResponse = await http.get("/github-app/callback", {
403
- qs: {
404
- installation_id: "12345",
405
- setup_action: "install",
406
- state: state!,
407
- },
400
+ const result = await app.ctx.plugins.githubApp.completeInstallation({
401
+ installationId: 12345,
402
+ state: state!,
403
+ userId: "test-user-id",
408
404
  });
409
405
 
410
406
  // Should handle error gracefully
411
- expect(callbackResponse.status).toBe(500);
407
+ expect(result.success).toBe(false);
408
+ expect(result.error).toBeDefined();
409
+ expect(result.error?.code).toBe("server-error");
412
410
  });
413
411
 
414
412
  it("should handle network errors when getting installation token", async () => {
@@ -60,7 +60,6 @@ describe("Plugin Core", () => {
60
60
  webhookSecret: "test-secret",
61
61
  clientId: "test-client-id",
62
62
  clientSecret: "test-client-secret",
63
- onInstallationSuccess: async () => ({ userId: "test", redirectUrl: "/" }),
64
63
  } as any;
65
64
 
66
65
  expect(() => githubAppPlugin(invalidOptions)).toThrowError(/appId is required/);
@@ -72,14 +71,13 @@ describe("Plugin Core", () => {
72
71
  webhookSecret: "test-secret",
73
72
  clientId: "test-client-id",
74
73
  clientSecret: "test-client-secret",
75
- onInstallationSuccess: async () => ({ userId: "test", redirectUrl: "/" }),
76
74
  } as any;
77
75
 
78
76
  expect(() => githubAppPlugin(invalidOptions)).toThrowError(/privateKey is required/);
79
77
  });
80
78
 
81
- it("should throw error if onInstallationSuccess callback is missing", () => {
82
- const invalidOptions = {
79
+ it("should not throw error when onInstallationSuccess is omitted", () => {
80
+ const validOptions = {
83
81
  appId: "12345",
84
82
  privateKey: testPrivateKeyBase64,
85
83
  webhookSecret: "test-secret",
@@ -87,7 +85,7 @@ describe("Plugin Core", () => {
87
85
  clientSecret: "test-client-secret",
88
86
  } as any;
89
87
 
90
- expect(() => githubAppPlugin(invalidOptions)).toThrowError(/onInstallationSuccess callback is required/);
88
+ expect(() => githubAppPlugin(validOptions)).not.toThrow();
91
89
  });
92
90
  });
93
91
 
@@ -102,7 +100,6 @@ describe("Plugin Core", () => {
102
100
  webhookSecret: "test-webhook-secret",
103
101
  clientId: "test-client-id",
104
102
  clientSecret: "test-client-secret",
105
- onInstallationSuccess: async () => ({ userId: "test-user", redirectUrl: "/dashboard" }),
106
103
  };
107
104
  });
108
105
 
@@ -125,12 +122,12 @@ describe("Plugin Core", () => {
125
122
  expect(mockApp.addRepo).toHaveBeenCalledWith("githubInstallationRepo", jasmine.any(Object));
126
123
  });
127
124
 
128
- it("should register only callback and webhook handlers when registerRoutes is true (default)", async () => {
125
+ it("should register only webhook handler when registerRoutes is true (default)", async () => {
129
126
  const plugin = githubAppPlugin(validOptions);
130
127
  await plugin.init!(mockApp, mockDb);
131
128
 
132
- // Verify only 2 handlers were registered (callback and webhook, NOT initiate and uninstall)
133
- expect(mockApp.addHandler).toHaveBeenCalledTimes(2);
129
+ // Verify only 1 handler was registered (webhook only, NOT callback)
130
+ expect(mockApp.addHandler).toHaveBeenCalledTimes(1);
134
131
  });
135
132
 
136
133
  it("should NOT register handlers when registerRoutes is false", async () => {
@@ -187,7 +184,6 @@ describe("Plugin Core", () => {
187
184
  webhookSecret: "test-webhook-secret",
188
185
  clientId: "test-client-id",
189
186
  clientSecret: "test-client-secret",
190
- onInstallationSuccess: async () => ({ userId: "test-user", redirectUrl: "/dashboard" }),
191
187
  };
192
188
 
193
189
  plugin = githubAppPlugin(validOptions);
@@ -20,7 +20,6 @@ describe('GitHub App Plugin - Project Setup', () => {
20
20
  webhookSecret: 'test',
21
21
  clientId: 'test',
22
22
  clientSecret: 'test',
23
- onInstallationSuccess: async () => ({ userId: 'test', redirectUrl: '/' }),
24
23
  };
25
24
  expect(options).toBeDefined();
26
25
  });
@@ -43,7 +42,6 @@ describe('GitHub App Plugin - Project Setup', () => {
43
42
  webhookSecret: 'test-webhook-secret',
44
43
  clientId: 'test-client-id',
45
44
  clientSecret: 'test-client-secret',
46
- onInstallationSuccess: async () => ({ userId: 'test', redirectUrl: '/' }),
47
45
  });
48
46
 
49
47
  expect(plugin).toBeDefined();
@@ -0,0 +1,16 @@
1
+ import { FlinkContext } from "@flink-app/flink";
2
+ import { GitHubAppPluginContext } from "./GitHubAppPluginContext";
3
+ import GitHubAppSessionRepo from "./repos/GitHubAppSessionRepo";
4
+ import GitHubInstallationRepo from "./repos/GitHubInstallationRepo";
5
+ import GitHubWebhookEventRepo from "./repos/GitHubWebhookEventRepo";
6
+
7
+ /**
8
+ * Internal context used for plugin handlers.
9
+ */
10
+ export interface GitHubAppInternalContext extends FlinkContext<GitHubAppPluginContext> {
11
+ repos: {
12
+ githubAppSessionRepo: GitHubAppSessionRepo;
13
+ githubInstallationRepo: GitHubInstallationRepo;
14
+ githubWebhookEventRepo?: GitHubWebhookEventRepo;
15
+ };
16
+ }
@@ -8,10 +8,10 @@ import GitHubWebhookEventRepo from "./repos/GitHubWebhookEventRepo";
8
8
  import { GitHubAuthService } from "./services/GitHubAuthService";
9
9
  import { GitHubAPIClient } from "./services/GitHubAPIClient";
10
10
  import { WebhookValidator } from "./services/WebhookValidator";
11
+ import { InstallationService } from "./services/InstallationService";
11
12
  import GitHubInstallation from "./schemas/GitHubInstallation";
12
13
  import { createGitHubAppError, GitHubAppErrorCodes } from "./utils/error-utils";
13
14
  import { generateState, generateSessionId } from "./utils/state-utils";
14
- import * as InstallationCallback from "./handlers/InstallationCallback";
15
15
  import * as WebhookHandler from "./handlers/WebhookHandler";
16
16
 
17
17
  /**
@@ -73,9 +73,6 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
73
73
  if (!options.clientSecret) {
74
74
  throw new Error("GitHub App Plugin: clientSecret is required");
75
75
  }
76
- if (!options.onInstallationSuccess) {
77
- throw new Error("GitHub App Plugin: onInstallationSuccess callback is required");
78
- }
79
76
 
80
77
  // Determine configuration defaults
81
78
  const baseUrl = options.baseUrl || "https://api.github.com";
@@ -87,6 +84,7 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
87
84
  let flinkApp: FlinkApp<any>;
88
85
  let authService: GitHubAuthService;
89
86
  let webhookValidator: WebhookValidator;
87
+ let installationService: InstallationService;
90
88
  let sessionRepo: GitHubAppSessionRepo;
91
89
  let installationRepo: GitHubInstallationRepo;
92
90
  let webhookEventRepo: GitHubWebhookEventRepo | undefined;
@@ -129,6 +127,9 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
129
127
  flinkApp.addRepo("githubAppSessionRepo", sessionRepo);
130
128
  flinkApp.addRepo("githubInstallationRepo", installationRepo);
131
129
 
130
+ // Initialize InstallationService
131
+ installationService = new InstallationService(authService, sessionRepo, installationRepo, baseUrl);
132
+
132
133
  // Conditionally initialize webhook event repo if logging is enabled
133
134
  if (logWebhookEvents) {
134
135
  webhookEventRepo = new GitHubWebhookEventRepo(flinkApp.ctx, db, webhookEventsCollectionName);
@@ -149,11 +150,10 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
149
150
  log.info(`GitHub App Plugin: Created TTL index on ${webhookEventsCollectionName} with ${webhookEventTTL}s expiration`);
150
151
  }
151
152
 
152
- // Conditionally register handlers (only GitHub-required handlers)
153
+ // Conditionally register handlers (only webhook handler)
153
154
  if (registerRoutes) {
154
- flinkApp.addHandler(InstallationCallback);
155
155
  flinkApp.addHandler(WebhookHandler);
156
- log.info("GitHub App Plugin: Registered handlers (callback and webhook)");
156
+ log.info("GitHub App Plugin: Registered webhook handler");
157
157
  } else {
158
158
  log.info("GitHub App Plugin: Skipped handler registration (routes disabled)");
159
159
  }
@@ -217,6 +217,21 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
217
217
  };
218
218
  }
219
219
 
220
+ /**
221
+ * Complete GitHub App installation
222
+ */
223
+ async function completeInstallation(params: {
224
+ installationId: number;
225
+ state: string;
226
+ userId: string;
227
+ }) {
228
+ if (!installationService) {
229
+ throw new Error("GitHub App Plugin: Plugin not initialized");
230
+ }
231
+
232
+ return installationService.completeInstallation(params);
233
+ }
234
+
220
235
  /**
221
236
  * Uninstalls GitHub App for a user
222
237
  */
@@ -387,6 +402,7 @@ export function githubAppPlugin(options: GitHubAppPluginOptions): FlinkPlugin {
387
402
  */
388
403
  const pluginCtx: GitHubAppPluginContext["githubApp"] = {
389
404
  initiateInstallation,
405
+ completeInstallation,
390
406
  uninstall,
391
407
  getClient,
392
408
  getInstallation,
@@ -3,6 +3,7 @@ import GitHubInstallation from "./schemas/GitHubInstallation";
3
3
  import { GitHubAppPluginOptions } from "./GitHubAppPluginOptions";
4
4
  import { GitHubAuthService } from "./services/GitHubAuthService";
5
5
  import { WebhookValidator } from "./services/WebhookValidator";
6
+ import { CompleteInstallationResult } from "./services/InstallationService";
6
7
 
7
8
  /**
8
9
  * Public context interface exposed via ctx.plugins.githubApp
@@ -52,6 +53,63 @@ export interface GitHubAppPluginContext {
52
53
  sessionId: string;
53
54
  }>;
54
55
 
56
+ /**
57
+ * Complete GitHub App installation
58
+ *
59
+ * Handles all the boilerplate of completing a GitHub App installation:
60
+ * 1. Validates the state parameter (CSRF protection)
61
+ * 2. Fetches installation details from GitHub API
62
+ * 3. Stores the installation in database
63
+ *
64
+ * The host application should:
65
+ * - Check if user is authenticated
66
+ * - Parse query parameters from the callback URL
67
+ * - Call this method with userId
68
+ * - Handle the response and redirect
69
+ *
70
+ * @param params - Installation completion parameters
71
+ * @param params.installationId - GitHub installation ID from query params
72
+ * @param params.state - State parameter from query params (CSRF protection)
73
+ * @param params.userId - Application user ID (from auth system)
74
+ * @returns Result with installation details or error
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // In your custom callback handler
79
+ * export default async function GitHubCallback({ ctx, req }: GetHandlerParams) {
80
+ * // 1. Check authentication (your way)
81
+ * if (!ctx.auth?.tokenData?.userId) {
82
+ * return unauthorized('Please log in first');
83
+ * }
84
+ *
85
+ * // 2. Parse query params
86
+ * const { installation_id, state } = req.query;
87
+ * if (!installation_id || !state) {
88
+ * return badRequest('Missing required parameters');
89
+ * }
90
+ *
91
+ * // 3. Complete installation
92
+ * const result = await ctx.plugins.githubApp.completeInstallation({
93
+ * installationId: parseInt(installation_id),
94
+ * state,
95
+ * userId: ctx.auth.tokenData.userId
96
+ * });
97
+ *
98
+ * // 4. Handle response (your way)
99
+ * if (!result.success) {
100
+ * return redirect(`/error?code=${result.error.code}`);
101
+ * }
102
+ *
103
+ * return redirect('/dashboard/github');
104
+ * }
105
+ * ```
106
+ */
107
+ completeInstallation(params: {
108
+ installationId: number;
109
+ state: string;
110
+ userId: string;
111
+ }): Promise<CompleteInstallationResult>;
112
+
55
113
  /**
56
114
  * Uninstalls GitHub App for a user
57
115
  *
@@ -8,6 +8,9 @@ export interface InstallationSuccessParams {
8
8
  /** GitHub installation ID */
9
9
  installationId: number;
10
10
 
11
+ /** Setup action type: 'install' for new installations, 'update' for repository selection updates */
12
+ setupAction: string;
13
+
11
14
  /** GitHub account (user or organization) where app was installed */
12
15
  account: {
13
16
  /** Account ID on GitHub */
@@ -149,8 +152,11 @@ export interface WebhookEventParams {
149
152
  /**
150
153
  * Configuration options for GitHub App Plugin
151
154
  *
152
- * Configure the plugin with your GitHub App credentials, callbacks,
153
- * and optional settings for database, caching, and features.
155
+ * Configure the plugin with your GitHub App credentials and optional settings
156
+ * for database, caching, and features.
157
+ *
158
+ * Note: Installation callback handler must be implemented by the host app.
159
+ * Use ctx.plugins.githubApp.completeInstallation() in your own handler.
154
160
  *
155
161
  * @example
156
162
  * ```typescript
@@ -161,12 +167,7 @@ export interface WebhookEventParams {
161
167
  * webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
162
168
  * clientId: process.env.GITHUB_APP_CLIENT_ID!,
163
169
  * clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
164
- *
165
- * // Required: Installation success callback
166
- * onInstallationSuccess: async ({ installationId, repositories }, ctx) => {
167
- * const userId = getUserId(ctx); // Your auth system
168
- * return { userId, redirectUrl: '/dashboard' };
169
- * },
170
+ * appSlug: 'my-app',
170
171
  *
171
172
  * // Optional: Handle webhook events
172
173
  * onWebhookEvent: async ({ event, payload }, ctx) => {
@@ -263,52 +264,6 @@ export interface GitHubAppPluginOptions {
263
264
 
264
265
  // Callbacks
265
266
 
266
- /**
267
- * Callback invoked after successful installation
268
- *
269
- * REQUIRED. Called when user completes GitHub App installation.
270
- * Return userId to link installation and optional redirectUrl.
271
- *
272
- * @param params - Installation data from GitHub
273
- * @param ctx - Flink context with repos and plugins
274
- * @returns Object with userId and optional redirectUrl
275
- *
276
- * @throws Should not throw - errors are caught and passed to onInstallationError
277
- *
278
- * @example
279
- * ```typescript
280
- * async ({ installationId, repositories, account }, ctx) => {
281
- * const userId = ctx.auth?.tokenData?.userId || 'anonymous';
282
- * return {
283
- * userId,
284
- * redirectUrl: '/dashboard/github'
285
- * };
286
- * }
287
- * ```
288
- */
289
- onInstallationSuccess: (params: InstallationSuccessParams, ctx: any) => Promise<InstallationSuccessResponse>;
290
-
291
- /**
292
- * Callback invoked when installation fails (optional)
293
- *
294
- * Called when installation encounters an error (invalid state,
295
- * expired session, GitHub API failure, etc.)
296
- *
297
- * @param params - Error information
298
- * @returns Object with optional redirectUrl
299
- *
300
- * @example
301
- * ```typescript
302
- * async ({ error, installationId }) => {
303
- * console.error('Installation error:', error);
304
- * return {
305
- * redirectUrl: `/error?code=${error.code}`
306
- * };
307
- * }
308
- * ```
309
- */
310
- onInstallationError?: (params: InstallationErrorParams) => Promise<InstallationErrorResponse>;
311
-
312
267
  /**
313
268
  * Callback invoked when webhook event is received (optional)
314
269
  *
@@ -393,8 +348,9 @@ export interface GitHubAppPluginOptions {
393
348
  /**
394
349
  * Register HTTP handlers automatically (optional)
395
350
  *
396
- * If true, registers installation and webhook handlers.
351
+ * If true, registers the webhook handler.
397
352
  * Set to false if you want to implement custom handlers.
353
+ * Note: Installation callback handler must be implemented by the host app.
398
354
  *
399
355
  * @default true
400
356
  */
@@ -11,16 +11,8 @@
11
11
  * Route: POST /github-app/webhook
12
12
  */
13
13
 
14
- import { FlinkContext, HttpMethod, RouteProps, log } from "@flink-app/flink";
15
- import { GitHubAppPluginContext } from "../GitHubAppPluginContext";
16
-
17
- /**
18
- * Context with GitHub App Plugin
19
- *
20
- * Note: Using 'any' for simplicity. In a real-world scenario, you'd want to properly
21
- * type the context with both FlinkContext and GitHubAppPluginContext including the repos.
22
- */
23
- type WebhookHandlerContext = FlinkContext<GitHubAppPluginContext>;
14
+ import { HttpMethod, RouteProps, log } from "@flink-app/flink";
15
+ import { GitHubAppInternalContext } from "../GitHubAppInternalContext";
24
16
 
25
17
  /**
26
18
  * Route configuration
@@ -36,7 +28,7 @@ export const Route: RouteProps = {
36
28
  *
37
29
  * Validates webhook signatures and processes GitHub webhook events.
38
30
  */
39
- const WebhookHandler = async ({ ctx, req }: { ctx: WebhookHandlerContext; req: any }) => {
31
+ const WebhookHandler = async ({ ctx, req }: { ctx: GitHubAppInternalContext; req: any }) => {
40
32
  try {
41
33
  // Extract headers
42
34
  const signature = req.headers["x-hub-signature-256"] as string;
package/src/index.ts CHANGED
@@ -9,21 +9,25 @@
9
9
  */
10
10
 
11
11
  // Plugin factory
12
- export { githubAppPlugin } from './GitHubAppPlugin';
12
+ export { githubAppPlugin } from "./GitHubAppPlugin";
13
13
 
14
14
  // Type exports
15
- export type { GitHubAppPluginOptions, InstallationSuccessParams, InstallationSuccessResponse, InstallationErrorParams, InstallationErrorResponse, WebhookEventParams } from './GitHubAppPluginOptions';
16
- export type { GitHubAppPluginContext } from './GitHubAppPluginContext';
15
+ export type {
16
+ GitHubAppPluginOptions,
17
+ WebhookEventParams,
18
+ } from "./GitHubAppPluginOptions";
19
+ export type { GitHubAppPluginContext } from "./GitHubAppPluginContext";
17
20
 
18
21
  // Schema exports
19
- export type { default as GitHubInstallation } from './schemas/GitHubInstallation';
20
- export type { default as GitHubAppSession } from './schemas/GitHubAppSession';
21
- export type { default as WebhookEvent } from './schemas/WebhookEvent';
22
- export type { default as WebhookPayload } from './schemas/WebhookPayload';
22
+ export type { default as GitHubInstallation } from "./schemas/GitHubInstallation";
23
+ export type { default as GitHubAppSession } from "./schemas/GitHubAppSession";
24
+ export type { default as WebhookEvent } from "./schemas/WebhookEvent";
25
+ export type { default as WebhookPayload } from "./schemas/WebhookPayload";
23
26
 
24
27
  // Service exports (for advanced usage)
25
- export { GitHubAPIClient, type Repository, type Content, type Issue, type CreateIssueParams } from './services/GitHubAPIClient';
26
- export { GitHubAuthService } from './services/GitHubAuthService';
28
+ export { GitHubAPIClient, type Repository, type Content, type Issue, type CreateIssueParams } from "./services/GitHubAPIClient";
29
+ export { GitHubAuthService } from "./services/GitHubAuthService";
30
+ export { InstallationService, type CompleteInstallationResult } from "./services/InstallationService";
27
31
 
28
32
  // Error utilities
29
- export { GitHubAppErrorCodes } from './utils/error-utils';
33
+ export { GitHubAppErrorCodes } from "./utils/error-utils";