@shipfox/api-integration-github 2.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.
Files changed (179) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +138 -0
  5. package/LICENSE +21 -0
  6. package/dist/api/client.d.ts +90 -0
  7. package/dist/api/client.d.ts.map +1 -0
  8. package/dist/api/client.js +306 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/api/installation-token-envelope.d.ts +36 -0
  11. package/dist/api/installation-token-envelope.d.ts.map +1 -0
  12. package/dist/api/installation-token-envelope.js +123 -0
  13. package/dist/api/installation-token-envelope.js.map +1 -0
  14. package/dist/api/installation-token-provider.d.ts +23 -0
  15. package/dist/api/installation-token-provider.d.ts.map +1 -0
  16. package/dist/api/installation-token-provider.js +130 -0
  17. package/dist/api/installation-token-provider.js.map +1 -0
  18. package/dist/api/shared-installation-token-cache.d.ts +43 -0
  19. package/dist/api/shared-installation-token-cache.d.ts.map +1 -0
  20. package/dist/api/shared-installation-token-cache.js +224 -0
  21. package/dist/api/shared-installation-token-cache.js.map +1 -0
  22. package/dist/config.d.ts +15 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +44 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/core/actions-logs.d.ts +47 -0
  27. package/dist/core/actions-logs.d.ts.map +1 -0
  28. package/dist/core/actions-logs.js +134 -0
  29. package/dist/core/actions-logs.js.map +1 -0
  30. package/dist/core/agent-tool-selection.d.ts +4 -0
  31. package/dist/core/agent-tool-selection.d.ts.map +1 -0
  32. package/dist/core/agent-tool-selection.js +38 -0
  33. package/dist/core/agent-tool-selection.js.map +1 -0
  34. package/dist/core/agent-tools.d.ts +50 -0
  35. package/dist/core/agent-tools.d.ts.map +1 -0
  36. package/dist/core/agent-tools.js +1122 -0
  37. package/dist/core/agent-tools.js.map +1 -0
  38. package/dist/core/errors.d.ts +15 -0
  39. package/dist/core/errors.d.ts.map +1 -0
  40. package/dist/core/errors.js +22 -0
  41. package/dist/core/errors.js.map +1 -0
  42. package/dist/core/install.d.ts +38 -0
  43. package/dist/core/install.d.ts.map +1 -0
  44. package/dist/core/install.js +64 -0
  45. package/dist/core/install.js.map +1 -0
  46. package/dist/core/source-control.d.ts +15 -0
  47. package/dist/core/source-control.d.ts.map +1 -0
  48. package/dist/core/source-control.js +175 -0
  49. package/dist/core/source-control.js.map +1 -0
  50. package/dist/core/state.d.ts +12 -0
  51. package/dist/core/state.d.ts.map +1 -0
  52. package/dist/core/state.js +54 -0
  53. package/dist/core/state.js.map +1 -0
  54. package/dist/core/webhook.d.ts +20 -0
  55. package/dist/core/webhook.d.ts.map +1 -0
  56. package/dist/core/webhook.js +216 -0
  57. package/dist/core/webhook.js.map +1 -0
  58. package/dist/db/db.d.ts +433 -0
  59. package/dist/db/db.d.ts.map +1 -0
  60. package/dist/db/db.js +18 -0
  61. package/dist/db/db.js.map +1 -0
  62. package/dist/db/installation-token-lock.d.ts +8 -0
  63. package/dist/db/installation-token-lock.d.ts.map +1 -0
  64. package/dist/db/installation-token-lock.js +37 -0
  65. package/dist/db/installation-token-lock.js.map +1 -0
  66. package/dist/db/installations.d.ts +33 -0
  67. package/dist/db/installations.d.ts.map +1 -0
  68. package/dist/db/installations.js +56 -0
  69. package/dist/db/installations.js.map +1 -0
  70. package/dist/db/migrations.d.ts +2 -0
  71. package/dist/db/migrations.d.ts.map +1 -0
  72. package/dist/db/migrations.js +5 -0
  73. package/dist/db/migrations.js.map +1 -0
  74. package/dist/db/schema/common.d.ts +2 -0
  75. package/dist/db/schema/common.d.ts.map +1 -0
  76. package/dist/db/schema/common.js +4 -0
  77. package/dist/db/schema/common.js.map +1 -0
  78. package/dist/db/schema/installations.d.ts +218 -0
  79. package/dist/db/schema/installations.d.ts.map +1 -0
  80. package/dist/db/schema/installations.js +46 -0
  81. package/dist/db/schema/installations.js.map +1 -0
  82. package/dist/index.d.ts +53 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +66 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/metrics/index.d.ts +2 -0
  87. package/dist/metrics/index.d.ts.map +1 -0
  88. package/dist/metrics/index.js +3 -0
  89. package/dist/metrics/index.js.map +1 -0
  90. package/dist/metrics/instance.d.ts +14 -0
  91. package/dist/metrics/instance.d.ts.map +1 -0
  92. package/dist/metrics/instance.js +75 -0
  93. package/dist/metrics/instance.js.map +1 -0
  94. package/dist/presentation/dto/integrations.d.ts +15 -0
  95. package/dist/presentation/dto/integrations.d.ts.map +1 -0
  96. package/dist/presentation/dto/integrations.js +10 -0
  97. package/dist/presentation/dto/integrations.js.map +1 -0
  98. package/dist/presentation/e2eRoutes/create-connection.d.ts +11 -0
  99. package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -0
  100. package/dist/presentation/e2eRoutes/create-connection.js +57 -0
  101. package/dist/presentation/e2eRoutes/create-connection.js.map +1 -0
  102. package/dist/presentation/e2eRoutes/index.d.ts +5 -0
  103. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  104. package/dist/presentation/e2eRoutes/index.js +11 -0
  105. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  106. package/dist/presentation/routes/errors.d.ts +2 -0
  107. package/dist/presentation/routes/errors.d.ts.map +1 -0
  108. package/dist/presentation/routes/errors.js +46 -0
  109. package/dist/presentation/routes/errors.js.map +1 -0
  110. package/dist/presentation/routes/install.d.ts +13 -0
  111. package/dist/presentation/routes/install.d.ts.map +1 -0
  112. package/dist/presentation/routes/install.js +77 -0
  113. package/dist/presentation/routes/install.js.map +1 -0
  114. package/dist/presentation/routes/webhooks.d.ts +16 -0
  115. package/dist/presentation/routes/webhooks.d.ts.map +1 -0
  116. package/dist/presentation/routes/webhooks.js +110 -0
  117. package/dist/presentation/routes/webhooks.js.map +1 -0
  118. package/dist/tsconfig.test.tsbuildinfo +1 -0
  119. package/drizzle/0000_initial.sql +17 -0
  120. package/drizzle/meta/_journal.json +13 -0
  121. package/drizzle.config.ts +7 -0
  122. package/package.json +71 -0
  123. package/src/api/client.test.ts +94 -0
  124. package/src/api/client.ts +525 -0
  125. package/src/api/installation-token-envelope.ts +171 -0
  126. package/src/api/installation-token-provider.test.ts +241 -0
  127. package/src/api/installation-token-provider.ts +224 -0
  128. package/src/api/shared-installation-token-cache.test.ts +294 -0
  129. package/src/api/shared-installation-token-cache.ts +346 -0
  130. package/src/config.test.ts +14 -0
  131. package/src/config.ts +47 -0
  132. package/src/connection-external-url.test.ts +78 -0
  133. package/src/core/actions-logs.test.ts +156 -0
  134. package/src/core/actions-logs.ts +186 -0
  135. package/src/core/agent-tool-selection.test.ts +41 -0
  136. package/src/core/agent-tool-selection.ts +42 -0
  137. package/src/core/agent-tools.test.ts +539 -0
  138. package/src/core/agent-tools.ts +1269 -0
  139. package/src/core/errors.ts +23 -0
  140. package/src/core/install.test.ts +238 -0
  141. package/src/core/install.ts +119 -0
  142. package/src/core/source-control.test.ts +327 -0
  143. package/src/core/source-control.ts +223 -0
  144. package/src/core/state.test.ts +43 -0
  145. package/src/core/state.ts +85 -0
  146. package/src/core/webhook.test.ts +522 -0
  147. package/src/core/webhook.ts +296 -0
  148. package/src/db/db.ts +18 -0
  149. package/src/db/installation-token-lock.test.ts +68 -0
  150. package/src/db/installation-token-lock.ts +39 -0
  151. package/src/db/installations.test.ts +67 -0
  152. package/src/db/installations.ts +108 -0
  153. package/src/db/migrations.ts +4 -0
  154. package/src/db/schema/common.ts +3 -0
  155. package/src/db/schema/installations.ts +46 -0
  156. package/src/index.ts +124 -0
  157. package/src/metrics/index.ts +1 -0
  158. package/src/metrics/instance.ts +82 -0
  159. package/src/presentation/dto/integrations.ts +8 -0
  160. package/src/presentation/e2eRoutes/create-connection.ts +70 -0
  161. package/src/presentation/e2eRoutes/index.test.ts +126 -0
  162. package/src/presentation/e2eRoutes/index.ts +14 -0
  163. package/src/presentation/routes/errors.ts +43 -0
  164. package/src/presentation/routes/install.test.ts +279 -0
  165. package/src/presentation/routes/install.ts +93 -0
  166. package/src/presentation/routes/webhooks.test.ts +560 -0
  167. package/src/presentation/routes/webhooks.ts +115 -0
  168. package/test/env.ts +16 -0
  169. package/test/factories/github-installation.ts +35 -0
  170. package/test/factories/index.ts +1 -0
  171. package/test/fixtures/github-webhook.ts +20 -0
  172. package/test/globalSetup.ts +14 -0
  173. package/test/index.ts +2 -0
  174. package/test/setup.ts +17 -0
  175. package/tsconfig.build.json +9 -0
  176. package/tsconfig.build.tsbuildinfo +1 -0
  177. package/tsconfig.json +3 -0
  178. package/tsconfig.test.json +9 -0
  179. package/vitest.config.ts +12 -0
@@ -0,0 +1,54 @@
1
+ import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { config } from '#config.js';
3
+ import { GithubInstallStateError } from './errors.js';
4
+ const STATE_TTL_SECONDS = 30 * 60;
5
+ export function signGithubInstallState(params) {
6
+ const now = params.now ?? new Date();
7
+ const payload = {
8
+ workspaceId: params.workspaceId,
9
+ userId: params.userId,
10
+ nonce: params.nonce ?? randomUUID(),
11
+ expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS
12
+ };
13
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
14
+ return `${encodedPayload}.${sign(encodedPayload)}`;
15
+ }
16
+ export function verifyGithubInstallState(state, now = new Date()) {
17
+ const [encodedPayload, signature, extra] = state.split('.');
18
+ if (!encodedPayload || !signature || extra !== undefined) {
19
+ throw new GithubInstallStateError('Invalid GitHub install state');
20
+ }
21
+ if (!constantTimeEqual(signature, sign(encodedPayload))) {
22
+ throw new GithubInstallStateError('Invalid GitHub install state signature');
23
+ }
24
+ const payload = parsePayload(encodedPayload);
25
+ if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {
26
+ throw new GithubInstallStateError('Expired GitHub install state');
27
+ }
28
+ return {
29
+ workspaceId: payload.workspaceId,
30
+ userId: payload.userId
31
+ };
32
+ }
33
+ function sign(encodedPayload) {
34
+ return createHmac('sha256', config.GITHUB_INSTALL_STATE_SECRET).update(encodedPayload).digest('base64url');
35
+ }
36
+ function constantTimeEqual(a, b) {
37
+ const left = Buffer.from(a);
38
+ const right = Buffer.from(b);
39
+ if (left.length !== right.length) return false;
40
+ return timingSafeEqual(left, right);
41
+ }
42
+ function parsePayload(encodedPayload) {
43
+ try {
44
+ const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
45
+ if (typeof parsed.workspaceId !== 'string' || typeof parsed.userId !== 'string' || typeof parsed.nonce !== 'string' || typeof parsed.expiresAt !== 'number') {
46
+ throw new Error('Invalid payload shape');
47
+ }
48
+ return parsed;
49
+ } catch (_error) {
50
+ throw new GithubInstallStateError('Invalid GitHub install state payload');
51
+ }
52
+ }
53
+
54
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/state.ts"],"sourcesContent":["import {createHmac, randomUUID, timingSafeEqual} from 'node:crypto';\nimport {config} from '#config.js';\nimport {GithubInstallStateError} from './errors.js';\n\nconst STATE_TTL_SECONDS = 30 * 60;\n\ninterface GithubInstallStatePayload {\n workspaceId: string;\n userId: string;\n nonce: string;\n expiresAt: number;\n}\n\nexport interface GithubInstallStateClaims {\n workspaceId: string;\n userId: string;\n}\n\nexport function signGithubInstallState(params: {\n workspaceId: string;\n userId: string;\n nonce?: string | undefined;\n now?: Date | undefined;\n}): string {\n const now = params.now ?? new Date();\n const payload: GithubInstallStatePayload = {\n workspaceId: params.workspaceId,\n userId: params.userId,\n nonce: params.nonce ?? randomUUID(),\n expiresAt: Math.floor(now.getTime() / 1000) + STATE_TTL_SECONDS,\n };\n const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');\n return `${encodedPayload}.${sign(encodedPayload)}`;\n}\n\nexport function verifyGithubInstallState(\n state: string,\n now: Date = new Date(),\n): GithubInstallStateClaims {\n const [encodedPayload, signature, extra] = state.split('.');\n if (!encodedPayload || !signature || extra !== undefined) {\n throw new GithubInstallStateError('Invalid GitHub install state');\n }\n\n if (!constantTimeEqual(signature, sign(encodedPayload))) {\n throw new GithubInstallStateError('Invalid GitHub install state signature');\n }\n\n const payload = parsePayload(encodedPayload);\n if (payload.expiresAt < Math.floor(now.getTime() / 1000)) {\n throw new GithubInstallStateError('Expired GitHub install state');\n }\n\n return {workspaceId: payload.workspaceId, userId: payload.userId};\n}\n\nfunction sign(encodedPayload: string): string {\n return createHmac('sha256', config.GITHUB_INSTALL_STATE_SECRET)\n .update(encodedPayload)\n .digest('base64url');\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const left = Buffer.from(a);\n const right = Buffer.from(b);\n if (left.length !== right.length) return false;\n return timingSafeEqual(left, right);\n}\n\nfunction parsePayload(encodedPayload: string): GithubInstallStatePayload {\n try {\n const parsed = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));\n if (\n typeof parsed.workspaceId !== 'string' ||\n typeof parsed.userId !== 'string' ||\n typeof parsed.nonce !== 'string' ||\n typeof parsed.expiresAt !== 'number'\n ) {\n throw new Error('Invalid payload shape');\n }\n return parsed;\n } catch (_error) {\n throw new GithubInstallStateError('Invalid GitHub install state payload');\n }\n}\n"],"names":["createHmac","randomUUID","timingSafeEqual","config","GithubInstallStateError","STATE_TTL_SECONDS","signGithubInstallState","params","now","Date","payload","workspaceId","userId","nonce","expiresAt","Math","floor","getTime","encodedPayload","Buffer","from","JSON","stringify","toString","sign","verifyGithubInstallState","state","signature","extra","split","undefined","constantTimeEqual","parsePayload","GITHUB_INSTALL_STATE_SECRET","update","digest","a","b","left","right","length","parsed","parse","Error","_error"],"mappings":"AAAA,SAAQA,UAAU,EAAEC,UAAU,EAAEC,eAAe,QAAO,cAAc;AACpE,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,uBAAuB,QAAO,cAAc;AAEpD,MAAMC,oBAAoB,KAAK;AAc/B,OAAO,SAASC,uBAAuBC,MAKtC;IACC,MAAMC,MAAMD,OAAOC,GAAG,IAAI,IAAIC;IAC9B,MAAMC,UAAqC;QACzCC,aAAaJ,OAAOI,WAAW;QAC/BC,QAAQL,OAAOK,MAAM;QACrBC,OAAON,OAAOM,KAAK,IAAIZ;QACvBa,WAAWC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,QAAQZ;IAChD;IACA,MAAMa,iBAAiBC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACZ,UAAUa,QAAQ,CAAC;IACrE,OAAO,GAAGL,eAAe,CAAC,EAAEM,KAAKN,iBAAiB;AACpD;AAEA,OAAO,SAASO,yBACdC,KAAa,EACblB,MAAY,IAAIC,MAAM;IAEtB,MAAM,CAACS,gBAAgBS,WAAWC,MAAM,GAAGF,MAAMG,KAAK,CAAC;IACvD,IAAI,CAACX,kBAAkB,CAACS,aAAaC,UAAUE,WAAW;QACxD,MAAM,IAAI1B,wBAAwB;IACpC;IAEA,IAAI,CAAC2B,kBAAkBJ,WAAWH,KAAKN,kBAAkB;QACvD,MAAM,IAAId,wBAAwB;IACpC;IAEA,MAAMM,UAAUsB,aAAad;IAC7B,IAAIR,QAAQI,SAAS,GAAGC,KAAKC,KAAK,CAACR,IAAIS,OAAO,KAAK,OAAO;QACxD,MAAM,IAAIb,wBAAwB;IACpC;IAEA,OAAO;QAACO,aAAaD,QAAQC,WAAW;QAAEC,QAAQF,QAAQE,MAAM;IAAA;AAClE;AAEA,SAASY,KAAKN,cAAsB;IAClC,OAAOlB,WAAW,UAAUG,OAAO8B,2BAA2B,EAC3DC,MAAM,CAAChB,gBACPiB,MAAM,CAAC;AACZ;AAEA,SAASJ,kBAAkBK,CAAS,EAAEC,CAAS;IAC7C,MAAMC,OAAOnB,OAAOC,IAAI,CAACgB;IACzB,MAAMG,QAAQpB,OAAOC,IAAI,CAACiB;IAC1B,IAAIC,KAAKE,MAAM,KAAKD,MAAMC,MAAM,EAAE,OAAO;IACzC,OAAOtC,gBAAgBoC,MAAMC;AAC/B;AAEA,SAASP,aAAad,cAAsB;IAC1C,IAAI;QACF,MAAMuB,SAASpB,KAAKqB,KAAK,CAACvB,OAAOC,IAAI,CAACF,gBAAgB,aAAaK,QAAQ,CAAC;QAC5E,IACE,OAAOkB,OAAO9B,WAAW,KAAK,YAC9B,OAAO8B,OAAO7B,MAAM,KAAK,YACzB,OAAO6B,OAAO5B,KAAK,KAAK,YACxB,OAAO4B,OAAO3B,SAAS,KAAK,UAC5B;YACA,MAAM,IAAI6B,MAAM;QAClB;QACA,OAAOF;IACT,EAAE,OAAOG,QAAQ;QACf,MAAM,IAAIxC,wBAAwB;IACpC;AACF"}
@@ -0,0 +1,20 @@
1
+ import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
2
+ export interface HandleGithubEventParams {
3
+ tx: IntegrationTx;
4
+ deliveryId: string;
5
+ event: string;
6
+ payload: unknown;
7
+ publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
8
+ publishSourcePush: PublishSourcePushFn;
9
+ recordDeliveryOnly: RecordDeliveryOnlyFn;
10
+ getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
11
+ deleteInstallationTokenSecret?: ((params: {
12
+ workspaceId: string;
13
+ installationId: number;
14
+ }) => Promise<unknown>) | undefined;
15
+ }
16
+ export type HandleGithubEventOutcome = 'published' | 'duplicate' | 'published-envelope' | 'duplicate-envelope' | 'published-push-envelope-only' | 'duplicate-push-envelope-only' | 'unknown-installation' | 'missing-connection' | 'inactive-connection' | 'no-installation-id';
17
+ export declare function handleGithubEvent(params: HandleGithubEventParams): Promise<{
18
+ outcome: HandleGithubEventOutcome;
19
+ }>;
20
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAe3C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;IAC7D,6BAA6B,CAAC,EAC1B,CAAC,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAC7E,SAAS,CAAC;CACf;AAED,MAAM,MAAM,wBAAwB,GAChC,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,oBAAoB,GACpB,8BAA8B,GAC9B,8BAA8B,GAC9B,sBAAsB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,oBAAoB,CAAC;AAMzB,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC;IAAC,OAAO,EAAE,wBAAwB,CAAA;CAAC,CAAC,CAmH9C"}
@@ -0,0 +1,216 @@
1
+ import { buildProviderRepositoryId } from '@shipfox/api-integration-core-dto';
2
+ import { githubPushPayloadSchema, githubWebhookActionSchema, githubWebhookInstallationSchema } from '@shipfox/api-integration-github-dto';
3
+ import { logger } from '@shipfox/node-opentelemetry';
4
+ import { getGithubInstallationByInstallationId } from '#db/installations.js';
5
+ const REFS_HEADS_PREFIX = 'refs/heads/';
6
+ const GITHUB_SOURCE = 'github';
7
+ // GitHub sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.
8
+ const DELETED_BRANCH_SHA = '0'.repeat(40);
9
+ function isBranchDeletion(after) {
10
+ return after === DELETED_BRANCH_SHA;
11
+ }
12
+ export async function handleGithubEvent(params) {
13
+ const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);
14
+ const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;
15
+ const installationEnvelope = githubWebhookInstallationSchema.safeParse(params.payload);
16
+ const installationId = installationEnvelope.success ? installationEnvelope.data.installation?.id : undefined;
17
+ if (installationId === undefined) {
18
+ await params.recordDeliveryOnly({
19
+ tx: params.tx,
20
+ provider: GITHUB_SOURCE,
21
+ deliveryId: params.deliveryId
22
+ });
23
+ return {
24
+ outcome: 'no-installation-id'
25
+ };
26
+ }
27
+ const installation = await getGithubInstallationByInstallationId(String(installationId), {
28
+ tx: params.tx
29
+ });
30
+ if (!installation) {
31
+ logger().warn({
32
+ deliveryId: params.deliveryId,
33
+ installationId
34
+ }, 'github webhook: unknown installation, dropping');
35
+ await params.recordDeliveryOnly({
36
+ tx: params.tx,
37
+ provider: GITHUB_SOURCE,
38
+ deliveryId: params.deliveryId
39
+ });
40
+ return {
41
+ outcome: 'unknown-installation'
42
+ };
43
+ }
44
+ const connection = await params.getIntegrationConnectionById(installation.connectionId, {
45
+ tx: params.tx
46
+ });
47
+ if (!connection) {
48
+ logger().warn({
49
+ deliveryId: params.deliveryId,
50
+ installationId,
51
+ connectionId: installation.connectionId
52
+ }, 'github webhook: installation has no connection, dropping');
53
+ await params.recordDeliveryOnly({
54
+ tx: params.tx,
55
+ provider: GITHUB_SOURCE,
56
+ deliveryId: params.deliveryId
57
+ });
58
+ return {
59
+ outcome: 'missing-connection'
60
+ };
61
+ }
62
+ if (connection.lifecycleStatus !== 'active') {
63
+ const logContext = {
64
+ deliveryId: params.deliveryId,
65
+ installationId,
66
+ connectionId: connection.id,
67
+ workspaceId: connection.workspaceId,
68
+ lifecycleStatus: connection.lifecycleStatus
69
+ };
70
+ // `disabled` is an expected steady state; only `error` is anomalous.
71
+ if (connection.lifecycleStatus === 'error') {
72
+ logger().warn(logContext, 'github webhook: connection in error state, dropping');
73
+ } else {
74
+ logger().info(logContext, 'github webhook: connection disabled, dropping');
75
+ }
76
+ await params.recordDeliveryOnly({
77
+ tx: params.tx,
78
+ provider: GITHUB_SOURCE,
79
+ deliveryId: params.deliveryId
80
+ });
81
+ return {
82
+ outcome: 'inactive-connection'
83
+ };
84
+ }
85
+ if (params.event === 'push') {
86
+ const validated = githubPushPayloadSchema.safeParse(params.payload);
87
+ if (!validated.success) {
88
+ logger().warn({
89
+ deliveryId: params.deliveryId,
90
+ issues: validated.error.issues
91
+ }, 'github webhook push payload failed schema validation; publishing generic envelope only');
92
+ return publishGithubEnvelopeOnly({
93
+ tx: params.tx,
94
+ deliveryId: params.deliveryId,
95
+ payload: params.payload,
96
+ publishIntegrationEventReceived: params.publishIntegrationEventReceived,
97
+ connection,
98
+ event: 'push'
99
+ });
100
+ }
101
+ return publishGithubPush({
102
+ ...params,
103
+ eventPayload: validated.data,
104
+ rawPayload: params.payload,
105
+ connection
106
+ });
107
+ }
108
+ const eventName = action ? `${params.event}.${action}` : params.event;
109
+ const result = await publishGithubEnvelopeOnly({
110
+ tx: params.tx,
111
+ deliveryId: params.deliveryId,
112
+ payload: params.payload,
113
+ publishIntegrationEventReceived: params.publishIntegrationEventReceived,
114
+ connection,
115
+ event: eventName
116
+ });
117
+ if (result.outcome === 'published-envelope') {
118
+ await deleteInstallationTokenSecretBestEffort({
119
+ deleteInstallationTokenSecret: params.deleteInstallationTokenSecret,
120
+ event: params.event,
121
+ action,
122
+ deliveryId: params.deliveryId,
123
+ workspaceId: connection.workspaceId,
124
+ installationId
125
+ });
126
+ }
127
+ return result;
128
+ }
129
+ function shouldDeleteInstallationTokenSecret(event, action) {
130
+ return event === 'installation' && (action === 'deleted' || action === 'suspend');
131
+ }
132
+ async function deleteInstallationTokenSecretBestEffort(params) {
133
+ if (!shouldDeleteInstallationTokenSecret(params.event, params.action)) return;
134
+ try {
135
+ await params.deleteInstallationTokenSecret?.({
136
+ workspaceId: params.workspaceId,
137
+ installationId: params.installationId
138
+ });
139
+ } catch (error) {
140
+ logger().warn({
141
+ deliveryId: params.deliveryId,
142
+ installationId: params.installationId,
143
+ workspaceId: params.workspaceId,
144
+ error
145
+ }, 'github webhook installation token cleanup failed');
146
+ }
147
+ }
148
+ async function publishGithubPush(params) {
149
+ if (isBranchDeletion(params.eventPayload.after)) {
150
+ const result = await params.publishIntegrationEventReceived({
151
+ tx: params.tx,
152
+ event: {
153
+ provider: GITHUB_SOURCE,
154
+ source: params.connection.slug,
155
+ event: 'push',
156
+ workspaceId: params.connection.workspaceId,
157
+ connectionId: params.connection.id,
158
+ connectionName: params.connection.displayName,
159
+ deliveryId: params.deliveryId,
160
+ receivedAt: new Date().toISOString(),
161
+ payload: params.rawPayload
162
+ }
163
+ });
164
+ return {
165
+ outcome: result.published ? 'published-push-envelope-only' : 'duplicate-push-envelope-only'
166
+ };
167
+ }
168
+ const ref = stripRefsHeads(params.eventPayload.ref);
169
+ const defaultBranch = params.eventPayload.repository.default_branch;
170
+ const push = {
171
+ externalRepositoryId: buildProviderRepositoryId(GITHUB_SOURCE, String(params.eventPayload.repository.id)),
172
+ ref,
173
+ headCommitSha: params.eventPayload.after,
174
+ defaultBranch,
175
+ isDefaultBranch: ref === defaultBranch
176
+ };
177
+ const result = await params.publishSourcePush({
178
+ tx: params.tx,
179
+ provider: GITHUB_SOURCE,
180
+ source: params.connection.slug,
181
+ workspaceId: params.connection.workspaceId,
182
+ connectionId: params.connection.id,
183
+ connectionName: params.connection.displayName,
184
+ deliveryId: params.deliveryId,
185
+ receivedAt: new Date().toISOString(),
186
+ rawPayload: params.rawPayload,
187
+ push
188
+ });
189
+ return {
190
+ outcome: result.published ? 'published' : 'duplicate'
191
+ };
192
+ }
193
+ async function publishGithubEnvelopeOnly(params) {
194
+ const result = await params.publishIntegrationEventReceived({
195
+ tx: params.tx,
196
+ event: {
197
+ provider: GITHUB_SOURCE,
198
+ source: params.connection.slug,
199
+ event: params.event,
200
+ workspaceId: params.connection.workspaceId,
201
+ connectionId: params.connection.id,
202
+ connectionName: params.connection.displayName,
203
+ deliveryId: params.deliveryId,
204
+ receivedAt: new Date().toISOString(),
205
+ payload: params.payload
206
+ }
207
+ });
208
+ return {
209
+ outcome: result.published ? 'published-envelope' : 'duplicate-envelope'
210
+ };
211
+ }
212
+ function stripRefsHeads(ref) {
213
+ return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;
214
+ }
215
+
216
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n buildProviderRepositoryId,\n type GetIntegrationConnectionByIdFn,\n type IntegrationTx,\n type PublishIntegrationEventReceivedFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type SourcePushPayload,\n} from '@shipfox/api-integration-core-dto';\nimport {\n type GithubPushPayloadDto,\n githubPushPayloadSchema,\n githubWebhookActionSchema,\n githubWebhookInstallationSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {getGithubInstallationByInstallationId} from '#db/installations.js';\n\nconst REFS_HEADS_PREFIX = 'refs/heads/';\nconst GITHUB_SOURCE = 'github';\n// GitHub sends a `push` webhook for a branch deletion with `after` set to this all-zero SHA.\nconst DELETED_BRANCH_SHA = '0'.repeat(40);\n\nexport interface HandleGithubEventParams {\n tx: IntegrationTx;\n deliveryId: string;\n event: string;\n payload: unknown;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n deleteInstallationTokenSecret?:\n | ((params: {workspaceId: string; installationId: number}) => Promise<unknown>)\n | undefined;\n}\n\nexport type HandleGithubEventOutcome =\n | 'published'\n | 'duplicate'\n | 'published-envelope'\n | 'duplicate-envelope'\n | 'published-push-envelope-only'\n | 'duplicate-push-envelope-only'\n | 'unknown-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'no-installation-id';\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\nexport async function handleGithubEvent(\n params: HandleGithubEventParams,\n): Promise<{outcome: HandleGithubEventOutcome}> {\n const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);\n const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;\n const installationEnvelope = githubWebhookInstallationSchema.safeParse(params.payload);\n const installationId = installationEnvelope.success\n ? installationEnvelope.data.installation?.id\n : undefined;\n if (installationId === undefined) {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'no-installation-id'};\n }\n\n const installation = await getGithubInstallationByInstallationId(String(installationId), {\n tx: params.tx,\n });\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, installationId},\n 'github webhook: unknown installation, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'unknown-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {deliveryId: params.deliveryId, installationId, connectionId: installation.connectionId},\n 'github webhook: installation has no connection, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n const logContext = {\n deliveryId: params.deliveryId,\n installationId,\n connectionId: connection.id,\n workspaceId: connection.workspaceId,\n lifecycleStatus: connection.lifecycleStatus,\n };\n // `disabled` is an expected steady state; only `error` is anomalous.\n if (connection.lifecycleStatus === 'error') {\n logger().warn(logContext, 'github webhook: connection in error state, dropping');\n } else {\n logger().info(logContext, 'github webhook: connection disabled, dropping');\n }\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'inactive-connection'};\n }\n\n if (params.event === 'push') {\n const validated = githubPushPayloadSchema.safeParse(params.payload);\n if (!validated.success) {\n logger().warn(\n {deliveryId: params.deliveryId, issues: validated.error.issues},\n 'github webhook push payload failed schema validation; publishing generic envelope only',\n );\n return publishGithubEnvelopeOnly({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload: params.payload,\n publishIntegrationEventReceived: params.publishIntegrationEventReceived,\n connection,\n event: 'push',\n });\n }\n\n return publishGithubPush({\n ...params,\n eventPayload: validated.data,\n rawPayload: params.payload,\n connection,\n });\n }\n\n const eventName = action ? `${params.event}.${action}` : params.event;\n const result = await publishGithubEnvelopeOnly({\n tx: params.tx,\n deliveryId: params.deliveryId,\n payload: params.payload,\n publishIntegrationEventReceived: params.publishIntegrationEventReceived,\n connection,\n event: eventName,\n });\n if (result.outcome === 'published-envelope') {\n await deleteInstallationTokenSecretBestEffort({\n deleteInstallationTokenSecret: params.deleteInstallationTokenSecret,\n event: params.event,\n action,\n deliveryId: params.deliveryId,\n workspaceId: connection.workspaceId,\n installationId,\n });\n }\n return result;\n}\n\nfunction shouldDeleteInstallationTokenSecret(event: string, action: string | undefined): boolean {\n return event === 'installation' && (action === 'deleted' || action === 'suspend');\n}\n\nasync function deleteInstallationTokenSecretBestEffort(params: {\n deleteInstallationTokenSecret:\n | ((params: {workspaceId: string; installationId: number}) => Promise<unknown>)\n | undefined;\n event: string;\n action: string | undefined;\n deliveryId: string;\n workspaceId: string;\n installationId: number;\n}): Promise<void> {\n if (!shouldDeleteInstallationTokenSecret(params.event, params.action)) return;\n\n try {\n await params.deleteInstallationTokenSecret?.({\n workspaceId: params.workspaceId,\n installationId: params.installationId,\n });\n } catch (error) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n installationId: params.installationId,\n workspaceId: params.workspaceId,\n error,\n },\n 'github webhook installation token cleanup failed',\n );\n }\n}\n\nasync function publishGithubPush(params: {\n tx: IntegrationTx;\n deliveryId: string;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n publishSourcePush: PublishSourcePushFn;\n eventPayload: GithubPushPayloadDto;\n rawPayload: unknown;\n connection: {\n id: string;\n workspaceId: string;\n slug: string;\n displayName: string;\n };\n}): Promise<{outcome: HandleGithubEventOutcome}> {\n if (isBranchDeletion(params.eventPayload.after)) {\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n event: 'push',\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.rawPayload,\n },\n });\n return {\n outcome: result.published ? 'published-push-envelope-only' : 'duplicate-push-envelope-only',\n };\n }\n\n const ref = stripRefsHeads(params.eventPayload.ref);\n const defaultBranch = params.eventPayload.repository.default_branch;\n const push: SourcePushPayload = {\n externalRepositoryId: buildProviderRepositoryId(\n GITHUB_SOURCE,\n String(params.eventPayload.repository.id),\n ),\n ref,\n headCommitSha: params.eventPayload.after,\n defaultBranch,\n isDefaultBranch: ref === defaultBranch,\n };\n const result = await params.publishSourcePush({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n rawPayload: params.rawPayload,\n push,\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nasync function publishGithubEnvelopeOnly(params: {\n tx: IntegrationTx;\n deliveryId: string;\n payload: unknown;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n connection: {id: string; workspaceId: string; slug: string; displayName: string};\n event: string;\n}): Promise<{outcome: HandleGithubEventOutcome}> {\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: GITHUB_SOURCE,\n source: params.connection.slug,\n event: params.event,\n workspaceId: params.connection.workspaceId,\n connectionId: params.connection.id,\n connectionName: params.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: params.payload,\n },\n });\n return {outcome: result.published ? 'published-envelope' : 'duplicate-envelope'};\n}\n\nfunction stripRefsHeads(ref: string): string {\n return ref.startsWith(REFS_HEADS_PREFIX) ? ref.slice(REFS_HEADS_PREFIX.length) : ref;\n}\n"],"names":["buildProviderRepositoryId","githubPushPayloadSchema","githubWebhookActionSchema","githubWebhookInstallationSchema","logger","getGithubInstallationByInstallationId","REFS_HEADS_PREFIX","GITHUB_SOURCE","DELETED_BRANCH_SHA","repeat","isBranchDeletion","after","handleGithubEvent","params","actionEnvelope","safeParse","payload","action","success","data","undefined","installationEnvelope","installationId","installation","id","recordDeliveryOnly","tx","provider","deliveryId","outcome","String","warn","connection","getIntegrationConnectionById","connectionId","lifecycleStatus","logContext","workspaceId","info","event","validated","issues","error","publishGithubEnvelopeOnly","publishIntegrationEventReceived","publishGithubPush","eventPayload","rawPayload","eventName","result","deleteInstallationTokenSecretBestEffort","deleteInstallationTokenSecret","shouldDeleteInstallationTokenSecret","source","slug","connectionName","displayName","receivedAt","Date","toISOString","published","ref","stripRefsHeads","defaultBranch","repository","default_branch","push","externalRepositoryId","headCommitSha","isDefaultBranch","publishSourcePush","startsWith","slice","length"],"mappings":"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"}