@shipfox/api-integration-github 11.0.0 → 12.2.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 (42) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -0
  3. package/dist/api/shared-installation-token-cache.d.ts.map +1 -1
  4. package/dist/core/agent-tools.d.ts.map +1 -1
  5. package/dist/core/errors.d.ts.map +1 -1
  6. package/dist/core/source-control.d.ts +2 -1
  7. package/dist/core/source-control.d.ts.map +1 -1
  8. package/dist/core/source-control.js +56 -1
  9. package/dist/core/source-control.js.map +1 -1
  10. package/dist/core/webhook-processor.d.ts +2 -1
  11. package/dist/core/webhook-processor.d.ts.map +1 -1
  12. package/dist/core/webhook-processor.js +1 -0
  13. package/dist/core/webhook-processor.js.map +1 -1
  14. package/dist/core/webhook.d.ts +3 -2
  15. package/dist/core/webhook.d.ts.map +1 -1
  16. package/dist/core/webhook.js +103 -1
  17. package/dist/core/webhook.js.map +1 -1
  18. package/dist/db/db.d.ts +2 -2
  19. package/dist/db/schema/installations.d.ts +1 -1
  20. package/dist/index.d.ts +4 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/presentation/routes/webhooks.d.ts +2 -1
  25. package/dist/presentation/routes/webhooks.d.ts.map +1 -1
  26. package/dist/presentation/routes/webhooks.js.map +1 -1
  27. package/dist/tsconfig.test.tsbuildinfo +1 -1
  28. package/package.json +8 -8
  29. package/src/connection-external-url.test.ts +1 -0
  30. package/src/core/agent-tools.test.ts +1 -0
  31. package/src/core/source-control.test.ts +80 -0
  32. package/src/core/source-control.ts +72 -0
  33. package/src/core/webhook-processor.test.ts +4 -0
  34. package/src/core/webhook-processor.ts +3 -0
  35. package/src/core/webhook.test.ts +197 -2
  36. package/src/core/webhook.ts +150 -0
  37. package/src/index.test.ts +1 -0
  38. package/src/index.ts +3 -0
  39. package/src/presentation/routes/install.test.ts +2 -1
  40. package/src/presentation/routes/webhooks.test.ts +49 -1
  41. package/src/presentation/routes/webhooks.ts +2 -0
  42. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 30 files with swc (480.06ms)
2
+ Successfully compiled: 30 files with swc (472.42ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @shipfox/api-integration-github
2
2
 
3
+ ## 12.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - @shipfox/api-integration-github-dto@12.2.0
8
+ - @shipfox/api-integration-spi@1.0.1
9
+ - @shipfox/node-opentelemetry@0.6.4
10
+ - @shipfox/node-fastify@0.4.2
11
+ - @shipfox/api-auth-context@12.2.0
12
+
13
+ ## 12.0.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 54c820e: Capture the actor that caused a source-control event on the normalized trigger reference. `TriggerReference` gains a required `actor`, resolved from the webhook sender by the GitHub and Gitea providers and null for payloads that name none.
18
+
19
+ ### Patch Changes
20
+
21
+ - f78740d: Remove Unicode dash punctuation from package prose and source comments.
22
+ - 9dffe58: Drop pull request webhook deliveries opened from a fork before they can trigger a run.
23
+ - 24ef475: Adds provider-normalized repository, ref, and commit extraction for source-control trigger payloads.
24
+ - 869a792: Refresh source-backed project repository identity from GitHub repository and installation-repository events.
25
+ - Updated dependencies [f78740d]
26
+ - Updated dependencies [24ef475]
27
+ - Updated dependencies [f13e8bb]
28
+ - Updated dependencies [869a792]
29
+ - Updated dependencies [54c820e]
30
+ - @shipfox/node-fastify@0.4.1
31
+ - @shipfox/api-integration-spi@1.0.0
32
+ - @shipfox/node-postgres@0.5.0
33
+ - @shipfox/api-integration-github-dto@12.0.0
34
+ - @shipfox/api-auth-context@12.0.0
35
+ - @shipfox/node-drizzle@0.3.5
36
+
3
37
  ## 11.0.0
4
38
 
5
39
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"shared-installation-token-cache.d.ts","sourceRoot":"","sources":["../../src/api/shared-installation-token-cache.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,6BAA6B,EAAC,MAAM,aAAa,CAAC;AAC/D,OAAO,EAIL,KAAK,yBAAyB,EAO/B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,sBAAsB;IACrC,SAAS,CACP,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,KAAK,CACH,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,mCAAmC;IAClD,WAAW,EAAE,4BAA4B,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,EACV,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KACjB,OAAO,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,qBAAa,4BAA6B,YAAW,sBAAsB;IAQ7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiE;IAC9F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAEV,OAAO,EAAE,mCAAmC;IAQnE,SAAS,CACb,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC;YAgB3B,aAAa;YA4Gb,gBAAgB;YA0ChB,UAAU;YAcV,YAAY;YAcZ,aAAa;YAQb,kBAAkB;CAYjC"}
1
+ {"version":3,"file":"shared-installation-token-cache.d.ts","sourceRoot":"","sources":["../../src/api/shared-installation-token-cache.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,6BAA6B,EAAC,MAAM,aAAa,CAAC;AAC/D,OAAO,EAIL,KAAK,yBAAyB,EAO/B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,sBAAsB;IACrC,SAAS,CACP,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,KAAK,CACH,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,mCAAmC;IAClD,WAAW,EAAE,4BAA4B,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,EACV,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KACjB,OAAO,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,qBAAa,4BAA6B,YAAW,sBAAsB;IAQ7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiE;IAC9F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAA6B,OAAO,EAAE,mCAAmC,EAMxE;IAEK,SAAS,CACb,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAcxC;YAEa,aAAa;YA4Gb,gBAAgB;YA0ChB,UAAU;YAcV,YAAY;YAcZ,aAAa;YAQb,kBAAkB;CAYjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EACL,KAAK,2BAA2B,EAEhC,KAAK,4BAA4B,EAGlC,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,+BAA+B,EAC/B,iCAAiC,EACjC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,oCAAoC,EACpC,0BAA0B,EAC1B,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AAExC,KAAK,2BAA2B,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;AAEnE,KAAK,oBAAoB,GAAG;IAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,qBAAa,wBACX,YACE,kBAAkB,CAChB,2BAA2B,EAC3B,4BAA4B,EAC5B,OAAO,EACP,oBAAoB,CACrB;IAIS,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;gBAEnC,OAAO,GAAE,+BAAoC;IAI1E,OAAO,IAAI,SAAS,2BAA2B,EAAE;IAIjD,gBAAgB,IAAI,yBAAyB;IAIvC,WAAW,CACf,KAAK,EAAE,0BAA0B,CAAC,2BAA2B,EAAE,4BAA4B,CAAC,GAC3F,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;CA+CnD;AAED,MAAM,WAAW,+BAA+B;IAC9C,6BAA6B,CAAC,EAC1B,CAAC,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC,GACnE,SAAS,CAAC;IACd,aAAa,CAAC,EAAE,+BAA+B,GAAG,SAAS,CAAC;IAC5D,YAAY,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CACvF;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC"}
1
+ {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EACL,KAAK,2BAA2B,EAEhC,KAAK,4BAA4B,EAGlC,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,+BAA+B,EAC/B,iCAAiC,EACjC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,oCAAoC,EACpC,0BAA0B,EAC1B,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AAExC,KAAK,2BAA2B,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;AAEnE,KAAK,oBAAoB,GAAG;IAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,qBAAa,wBACX,YACE,kBAAkB,CAChB,2BAA2B,EAC3B,4BAA4B,EAC5B,OAAO,EACP,oBAAoB,CACrB;IAIS,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,YAA6B,OAAO,GAAE,+BAAoC,EAEzE;IAED,OAAO,IAAI,SAAS,2BAA2B,EAAE,CAEhD;IAED,gBAAgB,IAAI,yBAAyB,CAE5C;IAEK,WAAW,CACf,KAAK,EAAE,0BAA0B,CAAC,2BAA2B,EAAE,4BAA4B,CAAC,GAC3F,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CA8CjD;CACF;AAED,MAAM,WAAW,+BAA+B;IAC9C,6BAA6B,CAAC,EAC1B,CAAC,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC,GACnE,SAAS,CAAC;IACd,aAAa,CAAC,EAAE,+BAA+B,GAAG,SAAS,CAAC;IAC5D,YAAY,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CACvF;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,8BAA8B,CAAC;AAEtE,qBAAa,8BAA+B,SAAQ,wBAAwB;CAAG;AAE/E,qBAAa,uBAAwB,SAAQ,KAAK;CAAG;AAErD,qBAAa,oCAAqC,SAAQ,KAAK;;CAI9D;AAED,qBAAa,oCAAqC,SAAQ,KAAK;gBACjD,cAAc,EAAE,MAAM;CAGnC;AAED,qBAAa,oCAAqC,SAAQ,KAAK;gBACjD,cAAc,EAAE,MAAM,GAAG,MAAM;CAG5C"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,8BAA8B,CAAC;AAEtE,qBAAa,8BAA+B,SAAQ,wBAAwB;CAAG;AAE/E,qBAAa,uBAAwB,SAAQ,KAAK;CAAG;AAErD,qBAAa,oCAAqC,SAAQ,KAAK;IAC7D,cAEC;CACF;AAED,qBAAa,oCAAqC,SAAQ,KAAK;IAC7D,YAAY,cAAc,EAAE,MAAM,EAEjC;CACF;AAED,qBAAa,oCAAqC,SAAQ,KAAK;IAC7D,YAAY,cAAc,EAAE,MAAM,GAAG,MAAM,EAE1C;CACF"}
@@ -1,4 +1,4 @@
1
- import { type CheckoutSpec, type CreateCheckoutSpecInput, type FetchFileInput, type FilePage, type FileSnapshot, type IntegrationConnection, type ListFilesInput, type ListRepositoriesInput, type RepositoryPage, type RepositorySnapshot, type ResolveRepositoryInput, type SourceControlProvider } from '@shipfox/api-integration-spi';
1
+ import { type CheckoutSpec, type CreateCheckoutSpecInput, type FetchFileInput, type FilePage, type FileSnapshot, type IntegrationConnection, type ListFilesInput, type ListRepositoriesInput, type RepositoryPage, type RepositorySnapshot, type ResolveRepositoryInput, type SourceControlProvider, type TriggerReference } from '@shipfox/api-integration-spi';
2
2
  import type { GithubApiClient } from '#api/client.js';
3
3
  type GithubIntegrationConnection = IntegrationConnection<'github'>;
4
4
  export declare class GithubSourceControlProvider implements SourceControlProvider<GithubIntegrationConnection> {
@@ -8,6 +8,7 @@ export declare class GithubSourceControlProvider implements SourceControlProvide
8
8
  resolveRepository(input: ResolveRepositoryInput<GithubIntegrationConnection>): Promise<RepositorySnapshot>;
9
9
  listFiles(input: ListFilesInput<GithubIntegrationConnection>): Promise<FilePage>;
10
10
  fetchFile(input: FetchFileInput<GithubIntegrationConnection>): Promise<FileSnapshot>;
11
+ resolveTriggerReference(payload: unknown): TriggerReference | null;
11
12
  createCheckoutSpec(input: CreateCheckoutSpecInput<GithubIntegrationConnection>): Promise<CheckoutSpec>;
12
13
  private installationId;
13
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"source-control.d.ts","sourceRoot":"","sources":["../../src/core/source-control.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAG1B,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,eAAe,EAAmB,MAAM,gBAAgB,CAAC;AAKtE,KAAK,2BAA2B,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;AAOnE,qBAAa,2BACX,YAAW,qBAAqB,CAAC,2BAA2B,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAE9C,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,CAAC,2BAA2B,CAAC,GACxD,OAAO,CAAC,cAAc,CAAC;IAiDpB,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,CAAC,2BAA2B,CAAC,GACzD,OAAO,CAAC,kBAAkB,CAAC;IAWxB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,2BAA2B,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkBhF,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,2BAA2B,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IA2BpF,kBAAkB,CACtB,KAAK,EAAE,uBAAuB,CAAC,2BAA2B,CAAC,GAC1D,OAAO,CAAC,YAAY,CAAC;YAoBV,cAAc;CAW7B"}
1
+ {"version":3,"file":"source-control.d.ts","sourceRoot":"","sources":["../../src/core/source-control.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAI1B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAK1B,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAC,eAAe,EAAmB,MAAM,gBAAgB,CAAC;AAKtE,KAAK,2BAA2B,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;AAOnE,qBAAa,2BACX,YAAW,qBAAqB,CAAC,2BAA2B,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,eAAe,EAAI;IAElD,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,CAAC,2BAA2B,CAAC,GACxD,OAAO,CAAC,cAAc,CAAC,CA+CzB;IAEK,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,CAAC,2BAA2B,CAAC,GACzD,OAAO,CAAC,kBAAkB,CAAC,CAS7B;IAEK,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,2BAA2B,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgBrF;IAEK,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,2BAA2B,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBzF;IAED,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAwCjE;IAEK,kBAAkB,CACtB,KAAK,EAAE,uBAAuB,CAAC,2BAA2B,CAAC,GAC1D,OAAO,CAAC,YAAY,CAAC,CAkBvB;YAEa,cAAc;CAW7B"}
@@ -1,5 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { buildProviderRepositoryId, MAX_REPOSITORY_FILE_BYTES, parseProviderRepositoryId } from '@shipfox/api-integration-spi';
2
+ import { asRecord, buildProviderRepositoryId, isRecord, isValidGitObjectId, isValidTriggerRef, MAX_REPOSITORY_FILE_BYTES, nonEmptyString, parseProviderRepositoryId, positiveInteger } from '@shipfox/api-integration-spi';
3
3
  import { config } from '#config.js';
4
4
  import { getGithubInstallationByConnectionId } from '#db/installations.js';
5
5
  import { GithubIntegrationProviderError } from './errors.js';
@@ -99,6 +99,45 @@ export class GithubSourceControlProvider {
99
99
  content: file.content
100
100
  };
101
101
  }
102
+ resolveTriggerReference(payload) {
103
+ if (!isRecord(payload)) return null;
104
+ const actor = githubEventActor(payload);
105
+ const pullRequest = asRecord(payload.pull_request);
106
+ if (pullRequest) {
107
+ const head = asRecord(pullRequest.head);
108
+ const repository = asRecord(head?.repo);
109
+ const base = asRecord(pullRequest.base);
110
+ const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);
111
+ if (!repository || !baseRepository || !sameGithubRepository(repository, baseRepository)) {
112
+ return null;
113
+ }
114
+ const repositoryId = githubRepositoryId(repository);
115
+ const number = positiveInteger(pullRequest.number);
116
+ const commit = nonEmptyString(head?.sha);
117
+ const ref = number === null ? null : `refs/pull/${number}/head`;
118
+ if (!repositoryId || !ref || !commit) return null;
119
+ const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
120
+ if (!hasValidReference) return null;
121
+ return {
122
+ externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),
123
+ ref,
124
+ commit,
125
+ actor
126
+ };
127
+ }
128
+ const repositoryId = githubRepositoryId(asRecord(payload.repository));
129
+ const ref = nonEmptyString(payload.ref);
130
+ const commit = nonEmptyString(payload.after);
131
+ if (!repositoryId || !ref || !commit) return null;
132
+ const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
133
+ if (!hasValidReference) return null;
134
+ return {
135
+ externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),
136
+ ref,
137
+ commit,
138
+ actor
139
+ };
140
+ }
102
141
  async createCheckoutSpec(input) {
103
142
  const installationId = await this.installationId(input.connection.id);
104
143
  const { repositoryId } = parseGithubRepositoryLocator(input.externalRepositoryId);
@@ -134,6 +173,22 @@ export class GithubSourceControlProvider {
134
173
  return Number.parseInt(installation.installationId, 10);
135
174
  }
136
175
  }
176
+ function githubRepositoryId(repository) {
177
+ return positiveInteger(repository?.id) === null ? null : String(repository?.id);
178
+ }
179
+ // `sender` is the one actor field every GitHub webhook event carries, so it stays correct
180
+ // for pushes, pull requests, and anything else that reaches a trigger reference.
181
+ function githubEventActor(payload) {
182
+ return nonEmptyString(asRecord(payload.sender)?.login);
183
+ }
184
+ function sameGithubRepository(first, second) {
185
+ const firstId = githubRepositoryId(first);
186
+ const secondId = githubRepositoryId(second);
187
+ if (firstId && secondId) return firstId === secondId;
188
+ const firstName = nonEmptyString(first.full_name);
189
+ const secondName = nonEmptyString(second.full_name);
190
+ return Boolean(firstName && secondName && firstName.toLowerCase() === secondName.toLowerCase());
191
+ }
137
192
  function githubAppGitAuthor() {
138
193
  const appUsername = config.GITHUB_APP_USERNAME?.trim();
139
194
  if (!appUsername) return undefined;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/source-control.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n buildProviderRepositoryId,\n type CheckoutSpec,\n type CreateCheckoutSpecInput,\n type FetchFileInput,\n type FilePage,\n type FileSnapshot,\n type IntegrationConnection,\n type ListFilesInput,\n type ListRepositoriesInput,\n MAX_REPOSITORY_FILE_BYTES,\n parseProviderRepositoryId,\n type RepositoryPage,\n type RepositorySnapshot,\n type RepositoryVisibility,\n type ResolveRepositoryInput,\n type SourceControlProvider,\n} from '@shipfox/api-integration-spi';\nimport type {GithubApiClient, GithubRepository} from '#api/client.js';\nimport {config} from '#config.js';\nimport {getGithubInstallationByConnectionId} from '#db/installations.js';\nimport {GithubIntegrationProviderError} from './errors.js';\n\ntype GithubIntegrationConnection = IntegrationConnection<'github'>;\n\nconst GITHUB_PROVIDER = 'github';\nconst GITHUB_APP_BOT_SUFFIX = '[bot]';\nconst SEARCH_PAGE_SIZE = 100;\nconst SEARCH_MAX_PAGES_PER_REQUEST = 5;\n\nexport class GithubSourceControlProvider\n implements SourceControlProvider<GithubIntegrationConnection>\n{\n constructor(private readonly github: GithubApiClient) {}\n\n async listRepositories(\n input: ListRepositoriesInput<GithubIntegrationConnection>,\n ): Promise<RepositoryPage> {\n const installation = await getGithubInstallationByConnectionId(input.connection.id);\n if (!installation) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub installation details were not found for the connection',\n );\n }\n\n const installationId = Number.parseInt(installation.installationId, 10);\n const needle = input.search?.trim().toLowerCase();\n\n if (!needle) {\n const page = await this.github.listInstallationRepositories({\n installationId,\n limit: input.limit,\n cursor: input.cursor,\n });\n return {\n repositories: page.repositories.map(toRepositorySnapshot),\n nextCursor: page.nextCursor,\n };\n }\n\n const matches: RepositorySnapshot[] = [];\n let cursor = input.cursor;\n let pagesScanned = 0;\n while (matches.length < input.limit && pagesScanned < SEARCH_MAX_PAGES_PER_REQUEST) {\n const page = await this.github.listInstallationRepositories({\n installationId,\n limit: SEARCH_PAGE_SIZE,\n cursor,\n });\n pagesScanned += 1;\n for (const repo of page.repositories) {\n if (repo.fullName.toLowerCase().includes(needle)) {\n matches.push(toRepositorySnapshot(repo));\n }\n }\n cursor = page.nextCursor ?? undefined;\n if (!cursor) break;\n }\n\n return {\n repositories: matches.slice(0, input.limit),\n nextCursor: cursor ?? null,\n };\n }\n\n async resolveRepository(\n input: ResolveRepositoryInput<GithubIntegrationConnection>,\n ): Promise<RepositorySnapshot> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const repository = await this.github.getRepository({\n installationId,\n repositoryId,\n });\n\n return toRepositorySnapshot(repository);\n }\n\n async listFiles(input: ListFilesInput<GithubIntegrationConnection>): Promise<FilePage> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const page = await this.github.listRepositoryFiles({\n installationId,\n repositoryId,\n ref: input.ref,\n prefix: input.prefix,\n limit: input.limit,\n cursor: input.cursor,\n });\n\n return {\n files: page.files.map((file) => ({path: file.path, type: 'file', size: file.size})),\n nextCursor: page.nextCursor,\n };\n }\n\n async fetchFile(input: FetchFileInput<GithubIntegrationConnection>): Promise<FileSnapshot> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const file = await this.github.fetchRepositoryFile({\n installationId,\n repositoryId,\n ref: input.ref,\n path: input.path,\n });\n\n if (\n file.size > MAX_REPOSITORY_FILE_BYTES ||\n Buffer.byteLength(file.content, 'utf8') > MAX_REPOSITORY_FILE_BYTES\n ) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n\n return {\n path: file.path,\n ref: input.ref,\n content: file.content,\n };\n }\n\n async createCheckoutSpec(\n input: CreateCheckoutSpecInput<GithubIntegrationConnection>,\n ): Promise<CheckoutSpec> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const repository = await this.github.getRepository({installationId, repositoryId});\n const ref = input.ref?.trim() || repository.defaultBranch;\n const {token, expiresAt} = await this.github.createInstallationAccessToken({\n installationId,\n repositoryId,\n permissions: input.permissions,\n });\n const gitAuthor = githubAppGitAuthor();\n\n return {\n repositoryUrl: repository.cloneUrl,\n ref,\n credentials: {username: 'x-access-token', token, expiresAt},\n ...(gitAuthor ? {gitAuthor} : {}),\n };\n }\n\n private async installationId(connectionId: string): Promise<number> {\n const installation = await getGithubInstallationByConnectionId(connectionId);\n if (!installation) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub installation details were not found for the connection',\n );\n }\n\n return Number.parseInt(installation.installationId, 10);\n }\n}\n\nfunction githubAppGitAuthor(): CheckoutSpec['gitAuthor'] {\n const appUsername = config.GITHUB_APP_USERNAME?.trim();\n if (!appUsername) return undefined;\n const name = appUsername.endsWith(GITHUB_APP_BOT_SUFFIX)\n ? appUsername\n : `${appUsername}${GITHUB_APP_BOT_SUFFIX}`;\n return {name, email: `${config.GITHUB_APP_ID}+${name}@users.noreply.github.com`};\n}\n\nfunction toRepositorySnapshot(repository: GithubRepository): RepositorySnapshot {\n return {\n externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, String(repository.id)),\n owner: repository.ownerLogin,\n name: repository.name,\n fullName: repository.fullName,\n defaultBranch: repository.defaultBranch,\n visibility: toRepositoryVisibility(repository),\n cloneUrl: repository.cloneUrl,\n htmlUrl: repository.htmlUrl,\n };\n}\n\nfunction parseGithubRepositoryLocator(externalRepositoryId: string): {\n repositoryId: number;\n} {\n const value = parseProviderRepositoryId(externalRepositoryId, GITHUB_PROVIDER);\n const repositoryId = Number.parseInt(value, 10);\n if (!Number.isInteger(repositoryId) || repositoryId <= 0 || String(repositoryId) !== value) {\n throw new GithubIntegrationProviderError(\n 'repository-not-found',\n `GitHub repository id ${externalRepositoryId} must follow the form ${GITHUB_PROVIDER}:<numeric-id>`,\n );\n }\n\n return {repositoryId};\n}\n\nfunction toRepositoryVisibility(repository: GithubRepository): RepositoryVisibility {\n if (repository.visibility === 'public') return 'public';\n if (repository.visibility === 'private') return 'private';\n if (repository.visibility === 'internal') return 'internal';\n return repository.private ? 'private' : 'public';\n}\n"],"names":["Buffer","buildProviderRepositoryId","MAX_REPOSITORY_FILE_BYTES","parseProviderRepositoryId","config","getGithubInstallationByConnectionId","GithubIntegrationProviderError","GITHUB_PROVIDER","GITHUB_APP_BOT_SUFFIX","SEARCH_PAGE_SIZE","SEARCH_MAX_PAGES_PER_REQUEST","GithubSourceControlProvider","github","listRepositories","input","installation","connection","id","installationId","Number","parseInt","needle","search","trim","toLowerCase","page","listInstallationRepositories","limit","cursor","repositories","map","toRepositorySnapshot","nextCursor","matches","pagesScanned","length","repo","fullName","includes","push","undefined","slice","resolveRepository","repositoryId","parseGithubRepositoryLocator","externalRepositoryId","repository","getRepository","listFiles","listRepositoryFiles","ref","prefix","files","file","path","type","size","fetchFile","fetchRepositoryFile","byteLength","content","createCheckoutSpec","defaultBranch","token","expiresAt","createInstallationAccessToken","permissions","gitAuthor","githubAppGitAuthor","repositoryUrl","cloneUrl","credentials","username","connectionId","appUsername","GITHUB_APP_USERNAME","name","endsWith","email","GITHUB_APP_ID","String","owner","ownerLogin","visibility","toRepositoryVisibility","htmlUrl","value","isInteger","private"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/source-control.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n asRecord,\n buildProviderRepositoryId,\n type CheckoutSpec,\n type CreateCheckoutSpecInput,\n type FetchFileInput,\n type FilePage,\n type FileSnapshot,\n type IntegrationConnection,\n isRecord,\n isValidGitObjectId,\n isValidTriggerRef,\n type ListFilesInput,\n type ListRepositoriesInput,\n MAX_REPOSITORY_FILE_BYTES,\n nonEmptyString,\n parseProviderRepositoryId,\n positiveInteger,\n type RepositoryPage,\n type RepositorySnapshot,\n type RepositoryVisibility,\n type ResolveRepositoryInput,\n type SourceControlProvider,\n type TriggerReference,\n} from '@shipfox/api-integration-spi';\nimport type {GithubApiClient, GithubRepository} from '#api/client.js';\nimport {config} from '#config.js';\nimport {getGithubInstallationByConnectionId} from '#db/installations.js';\nimport {GithubIntegrationProviderError} from './errors.js';\n\ntype GithubIntegrationConnection = IntegrationConnection<'github'>;\n\nconst GITHUB_PROVIDER = 'github';\nconst GITHUB_APP_BOT_SUFFIX = '[bot]';\nconst SEARCH_PAGE_SIZE = 100;\nconst SEARCH_MAX_PAGES_PER_REQUEST = 5;\n\nexport class GithubSourceControlProvider\n implements SourceControlProvider<GithubIntegrationConnection>\n{\n constructor(private readonly github: GithubApiClient) {}\n\n async listRepositories(\n input: ListRepositoriesInput<GithubIntegrationConnection>,\n ): Promise<RepositoryPage> {\n const installation = await getGithubInstallationByConnectionId(input.connection.id);\n if (!installation) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub installation details were not found for the connection',\n );\n }\n\n const installationId = Number.parseInt(installation.installationId, 10);\n const needle = input.search?.trim().toLowerCase();\n\n if (!needle) {\n const page = await this.github.listInstallationRepositories({\n installationId,\n limit: input.limit,\n cursor: input.cursor,\n });\n return {\n repositories: page.repositories.map(toRepositorySnapshot),\n nextCursor: page.nextCursor,\n };\n }\n\n const matches: RepositorySnapshot[] = [];\n let cursor = input.cursor;\n let pagesScanned = 0;\n while (matches.length < input.limit && pagesScanned < SEARCH_MAX_PAGES_PER_REQUEST) {\n const page = await this.github.listInstallationRepositories({\n installationId,\n limit: SEARCH_PAGE_SIZE,\n cursor,\n });\n pagesScanned += 1;\n for (const repo of page.repositories) {\n if (repo.fullName.toLowerCase().includes(needle)) {\n matches.push(toRepositorySnapshot(repo));\n }\n }\n cursor = page.nextCursor ?? undefined;\n if (!cursor) break;\n }\n\n return {\n repositories: matches.slice(0, input.limit),\n nextCursor: cursor ?? null,\n };\n }\n\n async resolveRepository(\n input: ResolveRepositoryInput<GithubIntegrationConnection>,\n ): Promise<RepositorySnapshot> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const repository = await this.github.getRepository({\n installationId,\n repositoryId,\n });\n\n return toRepositorySnapshot(repository);\n }\n\n async listFiles(input: ListFilesInput<GithubIntegrationConnection>): Promise<FilePage> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const page = await this.github.listRepositoryFiles({\n installationId,\n repositoryId,\n ref: input.ref,\n prefix: input.prefix,\n limit: input.limit,\n cursor: input.cursor,\n });\n\n return {\n files: page.files.map((file) => ({path: file.path, type: 'file', size: file.size})),\n nextCursor: page.nextCursor,\n };\n }\n\n async fetchFile(input: FetchFileInput<GithubIntegrationConnection>): Promise<FileSnapshot> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const file = await this.github.fetchRepositoryFile({\n installationId,\n repositoryId,\n ref: input.ref,\n path: input.path,\n });\n\n if (\n file.size > MAX_REPOSITORY_FILE_BYTES ||\n Buffer.byteLength(file.content, 'utf8') > MAX_REPOSITORY_FILE_BYTES\n ) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n\n return {\n path: file.path,\n ref: input.ref,\n content: file.content,\n };\n }\n\n resolveTriggerReference(payload: unknown): TriggerReference | null {\n if (!isRecord(payload)) return null;\n\n const actor = githubEventActor(payload);\n const pullRequest = asRecord(payload.pull_request);\n if (pullRequest) {\n const head = asRecord(pullRequest.head);\n const repository = asRecord(head?.repo);\n const base = asRecord(pullRequest.base);\n const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);\n if (!repository || !baseRepository || !sameGithubRepository(repository, baseRepository)) {\n return null;\n }\n const repositoryId = githubRepositoryId(repository);\n const number = positiveInteger(pullRequest.number);\n const commit = nonEmptyString(head?.sha);\n const ref = number === null ? null : `refs/pull/${number}/head`;\n if (!repositoryId || !ref || !commit) return null;\n const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);\n if (!hasValidReference) return null;\n return {\n externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),\n ref,\n commit,\n actor,\n };\n }\n\n const repositoryId = githubRepositoryId(asRecord(payload.repository));\n const ref = nonEmptyString(payload.ref);\n const commit = nonEmptyString(payload.after);\n if (!repositoryId || !ref || !commit) return null;\n const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);\n if (!hasValidReference) return null;\n return {\n externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),\n ref,\n commit,\n actor,\n };\n }\n\n async createCheckoutSpec(\n input: CreateCheckoutSpecInput<GithubIntegrationConnection>,\n ): Promise<CheckoutSpec> {\n const installationId = await this.installationId(input.connection.id);\n const {repositoryId} = parseGithubRepositoryLocator(input.externalRepositoryId);\n const repository = await this.github.getRepository({installationId, repositoryId});\n const ref = input.ref?.trim() || repository.defaultBranch;\n const {token, expiresAt} = await this.github.createInstallationAccessToken({\n installationId,\n repositoryId,\n permissions: input.permissions,\n });\n const gitAuthor = githubAppGitAuthor();\n\n return {\n repositoryUrl: repository.cloneUrl,\n ref,\n credentials: {username: 'x-access-token', token, expiresAt},\n ...(gitAuthor ? {gitAuthor} : {}),\n };\n }\n\n private async installationId(connectionId: string): Promise<number> {\n const installation = await getGithubInstallationByConnectionId(connectionId);\n if (!installation) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub installation details were not found for the connection',\n );\n }\n\n return Number.parseInt(installation.installationId, 10);\n }\n}\n\nfunction githubRepositoryId(repository: Record<string, unknown> | null): string | null {\n return positiveInteger(repository?.id) === null ? null : String(repository?.id);\n}\n\n// `sender` is the one actor field every GitHub webhook event carries, so it stays correct\n// for pushes, pull requests, and anything else that reaches a trigger reference.\nfunction githubEventActor(payload: Record<string, unknown>): string | null {\n return nonEmptyString(asRecord(payload.sender)?.login);\n}\n\nfunction sameGithubRepository(\n first: Record<string, unknown>,\n second: Record<string, unknown>,\n): boolean {\n const firstId = githubRepositoryId(first);\n const secondId = githubRepositoryId(second);\n if (firstId && secondId) return firstId === secondId;\n\n const firstName = nonEmptyString(first.full_name);\n const secondName = nonEmptyString(second.full_name);\n return Boolean(firstName && secondName && firstName.toLowerCase() === secondName.toLowerCase());\n}\n\nfunction githubAppGitAuthor(): CheckoutSpec['gitAuthor'] {\n const appUsername = config.GITHUB_APP_USERNAME?.trim();\n if (!appUsername) return undefined;\n const name = appUsername.endsWith(GITHUB_APP_BOT_SUFFIX)\n ? appUsername\n : `${appUsername}${GITHUB_APP_BOT_SUFFIX}`;\n return {name, email: `${config.GITHUB_APP_ID}+${name}@users.noreply.github.com`};\n}\n\nfunction toRepositorySnapshot(repository: GithubRepository): RepositorySnapshot {\n return {\n externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, String(repository.id)),\n owner: repository.ownerLogin,\n name: repository.name,\n fullName: repository.fullName,\n defaultBranch: repository.defaultBranch,\n visibility: toRepositoryVisibility(repository),\n cloneUrl: repository.cloneUrl,\n htmlUrl: repository.htmlUrl,\n };\n}\n\nfunction parseGithubRepositoryLocator(externalRepositoryId: string): {\n repositoryId: number;\n} {\n const value = parseProviderRepositoryId(externalRepositoryId, GITHUB_PROVIDER);\n const repositoryId = Number.parseInt(value, 10);\n if (!Number.isInteger(repositoryId) || repositoryId <= 0 || String(repositoryId) !== value) {\n throw new GithubIntegrationProviderError(\n 'repository-not-found',\n `GitHub repository id ${externalRepositoryId} must follow the form ${GITHUB_PROVIDER}:<numeric-id>`,\n );\n }\n\n return {repositoryId};\n}\n\nfunction toRepositoryVisibility(repository: GithubRepository): RepositoryVisibility {\n if (repository.visibility === 'public') return 'public';\n if (repository.visibility === 'private') return 'private';\n if (repository.visibility === 'internal') return 'internal';\n return repository.private ? 'private' : 'public';\n}\n"],"names":["Buffer","asRecord","buildProviderRepositoryId","isRecord","isValidGitObjectId","isValidTriggerRef","MAX_REPOSITORY_FILE_BYTES","nonEmptyString","parseProviderRepositoryId","positiveInteger","config","getGithubInstallationByConnectionId","GithubIntegrationProviderError","GITHUB_PROVIDER","GITHUB_APP_BOT_SUFFIX","SEARCH_PAGE_SIZE","SEARCH_MAX_PAGES_PER_REQUEST","GithubSourceControlProvider","github","listRepositories","input","installation","connection","id","installationId","Number","parseInt","needle","search","trim","toLowerCase","page","listInstallationRepositories","limit","cursor","repositories","map","toRepositorySnapshot","nextCursor","matches","pagesScanned","length","repo","fullName","includes","push","undefined","slice","resolveRepository","repositoryId","parseGithubRepositoryLocator","externalRepositoryId","repository","getRepository","listFiles","listRepositoryFiles","ref","prefix","files","file","path","type","size","fetchFile","fetchRepositoryFile","byteLength","content","resolveTriggerReference","payload","actor","githubEventActor","pullRequest","pull_request","head","base","baseRepository","sameGithubRepository","githubRepositoryId","number","commit","sha","hasValidReference","after","createCheckoutSpec","defaultBranch","token","expiresAt","createInstallationAccessToken","permissions","gitAuthor","githubAppGitAuthor","repositoryUrl","cloneUrl","credentials","username","connectionId","String","sender","login","first","second","firstId","secondId","firstName","full_name","secondName","Boolean","appUsername","GITHUB_APP_USERNAME","name","endsWith","email","GITHUB_APP_ID","owner","ownerLogin","visibility","toRepositoryVisibility","htmlUrl","value","isInteger","private"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import { type GetIntegrationConnectionByIdFn, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn, type StoredWebhookRequest, type WebhookProcessingResult } from '@shipfox/api-integration-spi';
1
+ import { type GetIntegrationConnectionByIdFn, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type PublishSourceRepositoryUpdatedFn, type RecordDeliveryOnlyFn, type StoredWebhookRequest, type WebhookProcessingResult } from '@shipfox/api-integration-spi';
2
2
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
3
  export interface CreateGithubWebhookProcessorOptions {
4
4
  coreDb: () => NodePgDatabase<Record<string, unknown>>;
5
5
  publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
6
+ publishSourceRepositoryUpdated: PublishSourceRepositoryUpdatedFn;
6
7
  publishSourcePush: PublishSourcePushFn;
7
8
  recordDeliveryOnly: RecordDeliveryOnlyFn;
8
9
  getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,mCAAmC;IAClD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,+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,WAAW,sBAAsB;IACrC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,sBAAsB,CAGxB"}
1
+ {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,mCAAmC;IAClD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,8BAA8B,EAAE,gCAAgC,CAAC;IACjE,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,WAAW,sBAAsB;IACrC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,sBAAsB,CAGxB"}
@@ -64,6 +64,7 @@ async function processGithubWebhookRequest(options, webhooks, request) {
64
64
  event,
65
65
  payload,
66
66
  publishIntegrationEventReceived: options.publishIntegrationEventReceived,
67
+ publishSourceRepositoryUpdated: options.publishSourceRepositoryUpdated,
67
68
  publishSourcePush: options.publishSourcePush,
68
69
  recordDeliveryOnly: options.recordDeliveryOnly,
69
70
  getIntegrationConnectionById: options.getIntegrationConnectionById
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {Webhooks} from '@octokit/webhooks';\nimport {\n decodeWebhookBody,\n type GetIntegrationConnectionByIdFn,\n type PublishIntegrationEventReceivedFn,\n type PublishSourcePushFn,\n type RecordDeliveryOnlyFn,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {handleGithubEvent} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'x-github-delivery';\nconst EVENT_HEADER = 'x-github-event';\nconst SIGNATURE_HEADER = 'x-hub-signature-256';\n\nexport interface CreateGithubWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, 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 interface GithubWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createGithubWebhookProcessor(\n options: CreateGithubWebhookProcessorOptions,\n): GithubWebhookProcessor {\n const webhooks = new Webhooks({secret: config.GITHUB_APP_WEBHOOK_SECRET});\n return {process: (request) => processGithubWebhookRequest(options, webhooks, request)};\n}\n\nasync function processGithubWebhookRequest(\n options: CreateGithubWebhookProcessorOptions,\n webhooks: Webhooks,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'github') {\n throw new Error(`GitHub processor cannot process ${request.route_id} requests`);\n }\n\n const deliveryId = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n if (!deliveryId || !event || !signature) {\n return {outcome: 'discarded', reason: 'missing_required_input'};\n }\n\n const rawBody = Buffer.from(decodeWebhookBody(request.body));\n let verified: boolean;\n try {\n verified = await webhooks.verify(rawBody.toString('utf8'), signature);\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook signature verification threw');\n verified = false;\n }\n if (!verified) return {outcome: 'discarded', reason: 'invalid_signature', deliveryId};\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody.toString('utf8'));\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload', deliveryId};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleGithubEvent({\n tx,\n deliveryId,\n event,\n payload,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n\n if (result.installationTokenCleanup && options.deleteInstallationTokenSecret) {\n await options.deleteInstallationTokenSecret(result.installationTokenCleanup);\n }\n\n return result.outcome.startsWith('duplicate')\n ? {outcome: 'duplicate', deliveryId}\n : {outcome: 'processed', deliveryId};\n}\n"],"names":["Buffer","Webhooks","decodeWebhookBody","logger","config","handleGithubEvent","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createGithubWebhookProcessor","options","webhooks","secret","GITHUB_APP_WEBHOOK_SECRET","process","request","processGithubWebhookRequest","route_id","Error","deliveryId","headers","event","signature","outcome","reason","rawBody","from","body","verified","verify","toString","error","warn","err","payload","JSON","parse","result","coreDb","transaction","tx","publishIntegrationEventReceived","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","installationTokenCleanup","deleteInstallationTokenSecret","startsWith"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SAAQC,QAAQ,QAAO,oBAAoB;AAC3C,SACEC,iBAAiB,QAOZ,+BAA+B;AACtC,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,QAAO,mBAAmB;AAEnD,MAAMC,kBAAkB;AACxB,MAAMC,eAAe;AACrB,MAAMC,mBAAmB;AAiBzB,OAAO,SAASC,6BACdC,OAA4C;IAE5C,MAAMC,WAAW,IAAIV,SAAS;QAACW,QAAQR,OAAOS,yBAAyB;IAAA;IACvE,OAAO;QAACC,SAAS,CAACC,UAAYC,4BAA4BN,SAASC,UAAUI;IAAQ;AACvF;AAEA,eAAeC,4BACbN,OAA4C,EAC5CC,QAAkB,EAClBI,OAA6B;IAE7B,IAAIA,QAAQE,QAAQ,KAAK,UAAU;QACjC,MAAM,IAAIC,MAAM,CAAC,gCAAgC,EAAEH,QAAQE,QAAQ,CAAC,SAAS,CAAC;IAChF;IAEA,MAAME,aAAaJ,QAAQK,OAAO,CAACd,gBAAgB;IACnD,MAAMe,QAAQN,QAAQK,OAAO,CAACb,aAAa;IAC3C,MAAMe,YAAYP,QAAQK,OAAO,CAACZ,iBAAiB;IACnD,IAAI,CAACW,cAAc,CAACE,SAAS,CAACC,WAAW;QACvC,OAAO;YAACC,SAAS;YAAaC,QAAQ;QAAwB;IAChE;IAEA,MAAMC,UAAUzB,OAAO0B,IAAI,CAACxB,kBAAkBa,QAAQY,IAAI;IAC1D,IAAIC;IACJ,IAAI;QACFA,WAAW,MAAMjB,SAASkB,MAAM,CAACJ,QAAQK,QAAQ,CAAC,SAASR;IAC7D,EAAE,OAAOS,OAAO;QACd5B,SAAS6B,IAAI,CAAC;YAACb;YAAYc,KAAKF;QAAK,GAAG;QACxCH,WAAW;IACb;IACA,IAAI,CAACA,UAAU,OAAO;QAACL,SAAS;QAAaC,QAAQ;QAAqBL;IAAU;IAEpF,IAAIe;IACJ,IAAI;QACFA,UAAUC,KAAKC,KAAK,CAACX,QAAQK,QAAQ,CAAC;IACxC,EAAE,OAAOC,OAAO;QACd5B,SAAS6B,IAAI,CAAC;YAACb;YAAYc,KAAKF;QAAK,GAAG;QACxC,OAAO;YAACR,SAAS;YAAaC,QAAQ;YAAqBL;QAAU;IACvE;IAEA,MAAMkB,SAAS,MAAM3B,QAAQ4B,MAAM,GAAGC,WAAW,CAAC,OAAOC,KACvDnC,kBAAkB;YAChBmC;YACArB;YACAE;YACAa;YACAO,iCAAiC/B,QAAQ+B,+BAA+B;YACxEC,mBAAmBhC,QAAQgC,iBAAiB;YAC5CC,oBAAoBjC,QAAQiC,kBAAkB;YAC9CC,8BAA8BlC,QAAQkC,4BAA4B;QACpE;IAGF,IAAIP,OAAOQ,wBAAwB,IAAInC,QAAQoC,6BAA6B,EAAE;QAC5E,MAAMpC,QAAQoC,6BAA6B,CAACT,OAAOQ,wBAAwB;IAC7E;IAEA,OAAOR,OAAOd,OAAO,CAACwB,UAAU,CAAC,eAC7B;QAACxB,SAAS;QAAaJ;IAAU,IACjC;QAACI,SAAS;QAAaJ;IAAU;AACvC"}
1
+ {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {Webhooks} from '@octokit/webhooks';\nimport {\n decodeWebhookBody,\n type GetIntegrationConnectionByIdFn,\n type PublishIntegrationEventReceivedFn,\n type PublishSourcePushFn,\n type PublishSourceRepositoryUpdatedFn,\n type RecordDeliveryOnlyFn,\n type StoredWebhookRequest,\n type WebhookProcessingResult,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {handleGithubEvent} from '#core/webhook.js';\n\nconst DELIVERY_HEADER = 'x-github-delivery';\nconst EVENT_HEADER = 'x-github-event';\nconst SIGNATURE_HEADER = 'x-hub-signature-256';\n\nexport interface CreateGithubWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n publishSourceRepositoryUpdated: PublishSourceRepositoryUpdatedFn;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n deleteInstallationTokenSecret?:\n | ((params: {workspaceId: string; installationId: number}) => Promise<unknown>)\n | undefined;\n}\n\nexport interface GithubWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createGithubWebhookProcessor(\n options: CreateGithubWebhookProcessorOptions,\n): GithubWebhookProcessor {\n const webhooks = new Webhooks({secret: config.GITHUB_APP_WEBHOOK_SECRET});\n return {process: (request) => processGithubWebhookRequest(options, webhooks, request)};\n}\n\nasync function processGithubWebhookRequest(\n options: CreateGithubWebhookProcessorOptions,\n webhooks: Webhooks,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'github') {\n throw new Error(`GitHub processor cannot process ${request.route_id} requests`);\n }\n\n const deliveryId = request.headers[DELIVERY_HEADER];\n const event = request.headers[EVENT_HEADER];\n const signature = request.headers[SIGNATURE_HEADER];\n if (!deliveryId || !event || !signature) {\n return {outcome: 'discarded', reason: 'missing_required_input'};\n }\n\n const rawBody = Buffer.from(decodeWebhookBody(request.body));\n let verified: boolean;\n try {\n verified = await webhooks.verify(rawBody.toString('utf8'), signature);\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook signature verification threw');\n verified = false;\n }\n if (!verified) return {outcome: 'discarded', reason: 'invalid_signature', deliveryId};\n\n let payload: unknown;\n try {\n payload = JSON.parse(rawBody.toString('utf8'));\n } catch (error) {\n logger().warn({deliveryId, err: error}, 'github webhook payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload', deliveryId};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleGithubEvent({\n tx,\n deliveryId,\n event,\n payload,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n publishSourceRepositoryUpdated: options.publishSourceRepositoryUpdated,\n publishSourcePush: options.publishSourcePush,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n\n if (result.installationTokenCleanup && options.deleteInstallationTokenSecret) {\n await options.deleteInstallationTokenSecret(result.installationTokenCleanup);\n }\n\n return result.outcome.startsWith('duplicate')\n ? {outcome: 'duplicate', deliveryId}\n : {outcome: 'processed', deliveryId};\n}\n"],"names":["Buffer","Webhooks","decodeWebhookBody","logger","config","handleGithubEvent","DELIVERY_HEADER","EVENT_HEADER","SIGNATURE_HEADER","createGithubWebhookProcessor","options","webhooks","secret","GITHUB_APP_WEBHOOK_SECRET","process","request","processGithubWebhookRequest","route_id","Error","deliveryId","headers","event","signature","outcome","reason","rawBody","from","body","verified","verify","toString","error","warn","err","payload","JSON","parse","result","coreDb","transaction","tx","publishIntegrationEventReceived","publishSourceRepositoryUpdated","publishSourcePush","recordDeliveryOnly","getIntegrationConnectionById","installationTokenCleanup","deleteInstallationTokenSecret","startsWith"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SAAQC,QAAQ,QAAO,oBAAoB;AAC3C,SACEC,iBAAiB,QAQZ,+BAA+B;AACtC,SAAQC,MAAM,QAAO,8BAA8B;AAEnD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,QAAO,mBAAmB;AAEnD,MAAMC,kBAAkB;AACxB,MAAMC,eAAe;AACrB,MAAMC,mBAAmB;AAkBzB,OAAO,SAASC,6BACdC,OAA4C;IAE5C,MAAMC,WAAW,IAAIV,SAAS;QAACW,QAAQR,OAAOS,yBAAyB;IAAA;IACvE,OAAO;QAACC,SAAS,CAACC,UAAYC,4BAA4BN,SAASC,UAAUI;IAAQ;AACvF;AAEA,eAAeC,4BACbN,OAA4C,EAC5CC,QAAkB,EAClBI,OAA6B;IAE7B,IAAIA,QAAQE,QAAQ,KAAK,UAAU;QACjC,MAAM,IAAIC,MAAM,CAAC,gCAAgC,EAAEH,QAAQE,QAAQ,CAAC,SAAS,CAAC;IAChF;IAEA,MAAME,aAAaJ,QAAQK,OAAO,CAACd,gBAAgB;IACnD,MAAMe,QAAQN,QAAQK,OAAO,CAACb,aAAa;IAC3C,MAAMe,YAAYP,QAAQK,OAAO,CAACZ,iBAAiB;IACnD,IAAI,CAACW,cAAc,CAACE,SAAS,CAACC,WAAW;QACvC,OAAO;YAACC,SAAS;YAAaC,QAAQ;QAAwB;IAChE;IAEA,MAAMC,UAAUzB,OAAO0B,IAAI,CAACxB,kBAAkBa,QAAQY,IAAI;IAC1D,IAAIC;IACJ,IAAI;QACFA,WAAW,MAAMjB,SAASkB,MAAM,CAACJ,QAAQK,QAAQ,CAAC,SAASR;IAC7D,EAAE,OAAOS,OAAO;QACd5B,SAAS6B,IAAI,CAAC;YAACb;YAAYc,KAAKF;QAAK,GAAG;QACxCH,WAAW;IACb;IACA,IAAI,CAACA,UAAU,OAAO;QAACL,SAAS;QAAaC,QAAQ;QAAqBL;IAAU;IAEpF,IAAIe;IACJ,IAAI;QACFA,UAAUC,KAAKC,KAAK,CAACX,QAAQK,QAAQ,CAAC;IACxC,EAAE,OAAOC,OAAO;QACd5B,SAAS6B,IAAI,CAAC;YAACb;YAAYc,KAAKF;QAAK,GAAG;QACxC,OAAO;YAACR,SAAS;YAAaC,QAAQ;YAAqBL;QAAU;IACvE;IAEA,MAAMkB,SAAS,MAAM3B,QAAQ4B,MAAM,GAAGC,WAAW,CAAC,OAAOC,KACvDnC,kBAAkB;YAChBmC;YACArB;YACAE;YACAa;YACAO,iCAAiC/B,QAAQ+B,+BAA+B;YACxEC,gCAAgChC,QAAQgC,8BAA8B;YACtEC,mBAAmBjC,QAAQiC,iBAAiB;YAC5CC,oBAAoBlC,QAAQkC,kBAAkB;YAC9CC,8BAA8BnC,QAAQmC,4BAA4B;QACpE;IAGF,IAAIR,OAAOS,wBAAwB,IAAIpC,QAAQqC,6BAA6B,EAAE;QAC5E,MAAMrC,QAAQqC,6BAA6B,CAACV,OAAOS,wBAAwB;IAC7E;IAEA,OAAOT,OAAOd,OAAO,CAACyB,UAAU,CAAC,eAC7B;QAACzB,SAAS;QAAaJ;IAAU,IACjC;QAACI,SAAS;QAAaJ;IAAU;AACvC"}
@@ -1,15 +1,16 @@
1
- import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
1
+ import { type GetIntegrationConnectionByIdFn, type IntegrationTx, type PublishIntegrationEventReceivedFn, type PublishSourcePushFn, type PublishSourceRepositoryUpdatedFn, type RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
2
2
  export interface HandleGithubEventParams {
3
3
  tx: IntegrationTx;
4
4
  deliveryId: string;
5
5
  event: string;
6
6
  payload: unknown;
7
7
  publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
8
+ publishSourceRepositoryUpdated: PublishSourceRepositoryUpdatedFn;
8
9
  publishSourcePush: PublishSourcePushFn;
9
10
  recordDeliveryOnly: RecordDeliveryOnlyFn;
10
11
  getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
11
12
  }
12
- 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';
13
+ export type HandleGithubEventOutcome = 'published' | 'duplicate' | 'published-envelope' | 'duplicate-envelope' | 'published-push-envelope-only' | 'duplicate-push-envelope-only' | 'fork-pull-request' | 'unknown-installation' | 'missing-connection' | 'inactive-connection' | 'no-installation-id';
13
14
  export interface HandleGithubEventResult {
14
15
  outcome: HandleGithubEventOutcome;
15
16
  installationTokenCleanup?: {
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAE1B,MAAM,8BAA8B,CAAC;AAStC,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;CAC9D;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;AAEzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,CAAC;IAClC,wBAAwB,CAAC,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAC,GAAG,SAAS,CAAC;CACtF;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC,CAqHlC"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,8BAA8B,EACnC,KAAK,aAAa,EAClB,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EAG1B,MAAM,8BAA8B,CAAC;AAStC,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,8BAA8B,EAAE,gCAAgC,CAAC;IACjE,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,wBAAwB,GAChC,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,oBAAoB,GACpB,8BAA8B,GAC9B,8BAA8B,GAC9B,mBAAmB,GACnB,sBAAsB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,CAAC;IAClC,wBAAwB,CAAC,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAC,GAAG,SAAS,CAAC;CACtF;AAsDD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC,CA0KlC"}
@@ -1,4 +1,4 @@
1
- import { githubPushPayloadSchema, githubWebhookActionSchema, githubWebhookInstallationSchema } from '@shipfox/api-integration-github-dto';
1
+ import { githubInstallationRepositoriesPayloadSchema, githubPushPayloadSchema, githubRepositoryRenamedPayloadSchema, githubWebhookActionSchema, githubWebhookInstallationSchema } from '@shipfox/api-integration-github-dto';
2
2
  import { buildProviderRepositoryId } from '@shipfox/api-integration-spi';
3
3
  import { logger } from '@shipfox/node-opentelemetry';
4
4
  import { getGithubInstallationByInstallationId } from '#db/installations.js';
@@ -9,9 +9,59 @@ const DELETED_BRANCH_SHA = '0'.repeat(40);
9
9
  function isBranchDeletion(after) {
10
10
  return after === DELETED_BRANCH_SHA;
11
11
  }
12
+ function isRecord(value) {
13
+ return typeof value === 'object' && value !== null;
14
+ }
15
+ function repositoryIdentity(value) {
16
+ if (!isRecord(value)) return undefined;
17
+ const id = (typeof value.id === 'number' && Number.isInteger(value.id) ? String(value.id) : undefined) ?? (typeof value.id === 'string' && value.id.trim() ? value.id.trim() : undefined);
18
+ const fullName = typeof value.full_name === 'string' && value.full_name.trim() ? value.full_name.trim().toLowerCase() : undefined;
19
+ if (!id && !fullName) return undefined;
20
+ return {
21
+ ...id ? {
22
+ id
23
+ } : {},
24
+ ...fullName ? {
25
+ fullName
26
+ } : {}
27
+ };
28
+ }
29
+ function areSameRepositories(head, base) {
30
+ if (head.id && base.id) return head.id === base.id;
31
+ if (head.fullName && base.fullName) return head.fullName === base.fullName;
32
+ return false;
33
+ }
34
+ function pullRequestRepositories(payload) {
35
+ if (!isRecord(payload) || !isRecord(payload.pull_request)) return undefined;
36
+ const pullRequest = payload.pull_request;
37
+ const head = isRecord(pullRequest.head) ? repositoryIdentity(pullRequest.head.repo) : undefined;
38
+ const base = (isRecord(pullRequest.base) ? repositoryIdentity(pullRequest.base.repo) : undefined) ?? repositoryIdentity(payload.repository);
39
+ return {
40
+ head,
41
+ base
42
+ };
43
+ }
12
44
  export async function handleGithubEvent(params) {
13
45
  const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);
14
46
  const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;
47
+ const pullRequestRepos = pullRequestRepositories(params.payload);
48
+ if (pullRequestRepos && (!pullRequestRepos.head || !pullRequestRepos.base || !areSameRepositories(pullRequestRepos.head, pullRequestRepos.base))) {
49
+ logger().info({
50
+ deliveryId: params.deliveryId,
51
+ event: params.event,
52
+ reason: !pullRequestRepos.head || !pullRequestRepos.base ? 'repository_unresolved' : 'repositories_differ',
53
+ headRepository: pullRequestRepos.head,
54
+ baseRepository: pullRequestRepos.base
55
+ }, 'github webhook: fork or indeterminate pull request, dropping');
56
+ await params.recordDeliveryOnly({
57
+ tx: params.tx,
58
+ provider: GITHUB_SOURCE,
59
+ deliveryId: params.deliveryId
60
+ });
61
+ return {
62
+ outcome: 'fork-pull-request'
63
+ };
64
+ }
15
65
  const installationEnvelope = githubWebhookInstallationSchema.safeParse(params.payload);
16
66
  const installationId = installationEnvelope.success ? installationEnvelope.data.installation?.id : undefined;
17
67
  if (installationId === undefined) {
@@ -106,6 +156,25 @@ export async function handleGithubEvent(params) {
106
156
  });
107
157
  }
108
158
  const eventName = action ? `${params.event}.${action}` : params.event;
159
+ const repositories = normalizeRepositoryUpdates(params.event, action, params.payload);
160
+ if (repositories) {
161
+ const result = await params.publishSourceRepositoryUpdated({
162
+ tx: params.tx,
163
+ provider: GITHUB_SOURCE,
164
+ source: connection.slug,
165
+ workspaceId: connection.workspaceId,
166
+ connectionId: connection.id,
167
+ connectionName: connection.displayName,
168
+ deliveryId: params.deliveryId,
169
+ receivedAt: new Date().toISOString(),
170
+ rawPayload: params.payload,
171
+ event: eventName,
172
+ repositories
173
+ });
174
+ return withInstallationTokenCleanup({
175
+ outcome: result.published ? 'published' : 'duplicate'
176
+ }, params.event, action, connection.workspaceId, installationId);
177
+ }
109
178
  const result = await publishGithubEnvelopeOnly({
110
179
  tx: params.tx,
111
180
  deliveryId: params.deliveryId,
@@ -116,6 +185,39 @@ export async function handleGithubEvent(params) {
116
185
  });
117
186
  return withInstallationTokenCleanup(result, params.event, action, connection.workspaceId, installationId);
118
187
  }
188
+ function normalizeRepositoryUpdates(event, action, payload) {
189
+ if (event === 'repository' && action === 'renamed') {
190
+ const parsed = githubRepositoryRenamedPayloadSchema.safeParse(payload);
191
+ if (!parsed.success) return undefined;
192
+ return [
193
+ toSourceRepositoryIdentity(parsed.data.repository)
194
+ ];
195
+ }
196
+ if (event !== 'installation_repositories' || action !== 'added' && action !== 'removed') {
197
+ return undefined;
198
+ }
199
+ const parsed = githubInstallationRepositoriesPayloadSchema.safeParse(payload);
200
+ if (!parsed.success) return undefined;
201
+ const repositories = new Map();
202
+ for (const repository of [
203
+ ...parsed.data.repositories_added,
204
+ ...parsed.data.repositories_removed
205
+ ]){
206
+ const normalized = toSourceRepositoryIdentity(repository);
207
+ repositories.set(repository.id, normalized);
208
+ }
209
+ return repositories.size > 0 ? [
210
+ ...repositories.values()
211
+ ] : undefined;
212
+ }
213
+ function toSourceRepositoryIdentity(repository) {
214
+ return {
215
+ externalRepositoryId: buildProviderRepositoryId(GITHUB_SOURCE, String(repository.id)),
216
+ owner: repository.owner.login,
217
+ name: repository.name,
218
+ defaultBranch: repository.default_branch
219
+ };
220
+ }
119
221
  function shouldDeleteInstallationTokenSecret(event, action) {
120
222
  return event === 'installation' && (action === 'deleted' || action === 'suspend');
121
223
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n type GithubPushPayloadDto,\n githubPushPayloadSchema,\n githubWebhookActionSchema,\n githubWebhookInstallationSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {\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-spi';\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}\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\nexport interface HandleGithubEventResult {\n outcome: HandleGithubEventOutcome;\n installationTokenCleanup?: {workspaceId: string; installationId: number} | undefined;\n}\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\nexport async function handleGithubEvent(\n params: HandleGithubEventParams,\n): Promise<HandleGithubEventResult> {\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 withInstallationTokenCleanup(\n {outcome: 'inactive-connection'},\n params.event,\n action,\n connection.workspaceId,\n installationId,\n );\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 return withInstallationTokenCleanup(\n result,\n params.event,\n action,\n connection.workspaceId,\n installationId,\n );\n}\n\nfunction shouldDeleteInstallationTokenSecret(event: string, action: string | undefined): boolean {\n return event === 'installation' && (action === 'deleted' || action === 'suspend');\n}\n\nfunction withInstallationTokenCleanup(\n result: HandleGithubEventResult,\n event: string,\n action: string | undefined,\n workspaceId: string,\n installationId: number,\n): HandleGithubEventResult {\n if (!shouldDeleteInstallationTokenSecret(event, action)) return result;\n return {...result, installationTokenCleanup: {workspaceId, installationId}};\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":["githubPushPayloadSchema","githubWebhookActionSchema","githubWebhookInstallationSchema","buildProviderRepositoryId","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","withInstallationTokenCleanup","event","validated","issues","error","publishGithubEnvelopeOnly","publishIntegrationEventReceived","publishGithubPush","eventPayload","rawPayload","eventName","result","shouldDeleteInstallationTokenSecret","installationTokenCleanup","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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"}
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n type GithubPushPayloadDto,\n githubInstallationRepositoriesPayloadSchema,\n githubPushPayloadSchema,\n githubRepositoryRenamedPayloadSchema,\n githubWebhookActionSchema,\n githubWebhookInstallationSchema,\n} from '@shipfox/api-integration-github-dto';\nimport {\n buildProviderRepositoryId,\n type GetIntegrationConnectionByIdFn,\n type IntegrationTx,\n type PublishIntegrationEventReceivedFn,\n type PublishSourcePushFn,\n type PublishSourceRepositoryUpdatedFn,\n type RecordDeliveryOnlyFn,\n type SourcePushPayload,\n type SourceRepositoryIdentity,\n} from '@shipfox/api-integration-spi';\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 publishSourceRepositoryUpdated: PublishSourceRepositoryUpdatedFn;\n publishSourcePush: PublishSourcePushFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\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 | 'fork-pull-request'\n | 'unknown-installation'\n | 'missing-connection'\n | 'inactive-connection'\n | 'no-installation-id';\n\nexport interface HandleGithubEventResult {\n outcome: HandleGithubEventOutcome;\n installationTokenCleanup?: {workspaceId: string; installationId: number} | undefined;\n}\n\nfunction isBranchDeletion(after: string): boolean {\n return after === DELETED_BRANCH_SHA;\n}\n\ninterface GithubRepositoryIdentity {\n id?: string;\n fullName?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction repositoryIdentity(value: unknown): GithubRepositoryIdentity | undefined {\n if (!isRecord(value)) return undefined;\n\n const id =\n (typeof value.id === 'number' && Number.isInteger(value.id) ? String(value.id) : undefined) ??\n (typeof value.id === 'string' && value.id.trim() ? value.id.trim() : undefined);\n const fullName =\n typeof value.full_name === 'string' && value.full_name.trim()\n ? value.full_name.trim().toLowerCase()\n : undefined;\n if (!id && !fullName) return undefined;\n\n return {...(id ? {id} : {}), ...(fullName ? {fullName} : {})};\n}\n\nfunction areSameRepositories(\n head: GithubRepositoryIdentity,\n base: GithubRepositoryIdentity,\n): boolean {\n if (head.id && base.id) return head.id === base.id;\n if (head.fullName && base.fullName) return head.fullName === base.fullName;\n return false;\n}\n\nfunction pullRequestRepositories(\n payload: unknown,\n):\n | {head: GithubRepositoryIdentity | undefined; base: GithubRepositoryIdentity | undefined}\n | undefined {\n if (!isRecord(payload) || !isRecord(payload.pull_request)) return undefined;\n\n const pullRequest = payload.pull_request;\n const head = isRecord(pullRequest.head) ? repositoryIdentity(pullRequest.head.repo) : undefined;\n const base =\n (isRecord(pullRequest.base) ? repositoryIdentity(pullRequest.base.repo) : undefined) ??\n repositoryIdentity(payload.repository);\n return {head, base};\n}\n\nexport async function handleGithubEvent(\n params: HandleGithubEventParams,\n): Promise<HandleGithubEventResult> {\n const actionEnvelope = githubWebhookActionSchema.safeParse(params.payload);\n const action = actionEnvelope.success ? actionEnvelope.data.action : undefined;\n\n const pullRequestRepos = pullRequestRepositories(params.payload);\n if (\n pullRequestRepos &&\n (!pullRequestRepos.head ||\n !pullRequestRepos.base ||\n !areSameRepositories(pullRequestRepos.head, pullRequestRepos.base))\n ) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n event: params.event,\n reason:\n !pullRequestRepos.head || !pullRequestRepos.base\n ? 'repository_unresolved'\n : 'repositories_differ',\n headRepository: pullRequestRepos.head,\n baseRepository: pullRequestRepos.base,\n },\n 'github webhook: fork or indeterminate pull request, dropping',\n );\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n deliveryId: params.deliveryId,\n });\n return {outcome: 'fork-pull-request'};\n }\n\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 withInstallationTokenCleanup(\n {outcome: 'inactive-connection'},\n params.event,\n action,\n connection.workspaceId,\n installationId,\n );\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 repositories = normalizeRepositoryUpdates(params.event, action, params.payload);\n if (repositories) {\n const result = await params.publishSourceRepositoryUpdated({\n tx: params.tx,\n provider: GITHUB_SOURCE,\n source: connection.slug,\n workspaceId: connection.workspaceId,\n connectionId: connection.id,\n connectionName: connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n rawPayload: params.payload,\n event: eventName,\n repositories,\n });\n return withInstallationTokenCleanup(\n {outcome: result.published ? 'published' : 'duplicate'},\n params.event,\n action,\n connection.workspaceId,\n installationId,\n );\n }\n\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 return withInstallationTokenCleanup(\n result,\n params.event,\n action,\n connection.workspaceId,\n installationId,\n );\n}\n\nfunction normalizeRepositoryUpdates(\n event: string,\n action: string | undefined,\n payload: unknown,\n): SourceRepositoryIdentity[] | undefined {\n if (event === 'repository' && action === 'renamed') {\n const parsed = githubRepositoryRenamedPayloadSchema.safeParse(payload);\n if (!parsed.success) return undefined;\n return [toSourceRepositoryIdentity(parsed.data.repository)];\n }\n\n if (event !== 'installation_repositories' || (action !== 'added' && action !== 'removed')) {\n return undefined;\n }\n\n const parsed = githubInstallationRepositoriesPayloadSchema.safeParse(payload);\n if (!parsed.success) return undefined;\n\n const repositories = new Map<number, SourceRepositoryIdentity>();\n for (const repository of [\n ...parsed.data.repositories_added,\n ...parsed.data.repositories_removed,\n ]) {\n const normalized = toSourceRepositoryIdentity(repository);\n repositories.set(repository.id, normalized);\n }\n return repositories.size > 0 ? [...repositories.values()] : undefined;\n}\n\nfunction toSourceRepositoryIdentity(repository: {\n id: number;\n name: string;\n owner: {login: string};\n default_branch: string;\n}): SourceRepositoryIdentity {\n return {\n externalRepositoryId: buildProviderRepositoryId(GITHUB_SOURCE, String(repository.id)),\n owner: repository.owner.login,\n name: repository.name,\n defaultBranch: repository.default_branch,\n };\n}\n\nfunction shouldDeleteInstallationTokenSecret(event: string, action: string | undefined): boolean {\n return event === 'installation' && (action === 'deleted' || action === 'suspend');\n}\n\nfunction withInstallationTokenCleanup(\n result: HandleGithubEventResult,\n event: string,\n action: string | undefined,\n workspaceId: string,\n installationId: number,\n): HandleGithubEventResult {\n if (!shouldDeleteInstallationTokenSecret(event, action)) return result;\n return {...result, installationTokenCleanup: {workspaceId, installationId}};\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":["githubInstallationRepositoriesPayloadSchema","githubPushPayloadSchema","githubRepositoryRenamedPayloadSchema","githubWebhookActionSchema","githubWebhookInstallationSchema","buildProviderRepositoryId","logger","getGithubInstallationByInstallationId","REFS_HEADS_PREFIX","GITHUB_SOURCE","DELETED_BRANCH_SHA","repeat","isBranchDeletion","after","isRecord","value","repositoryIdentity","undefined","id","Number","isInteger","String","trim","fullName","full_name","toLowerCase","areSameRepositories","head","base","pullRequestRepositories","payload","pull_request","pullRequest","repo","repository","handleGithubEvent","params","actionEnvelope","safeParse","action","success","data","pullRequestRepos","info","deliveryId","event","reason","headRepository","baseRepository","recordDeliveryOnly","tx","provider","outcome","installationEnvelope","installationId","installation","warn","connection","getIntegrationConnectionById","connectionId","lifecycleStatus","logContext","workspaceId","withInstallationTokenCleanup","validated","issues","error","publishGithubEnvelopeOnly","publishIntegrationEventReceived","publishGithubPush","eventPayload","rawPayload","eventName","repositories","normalizeRepositoryUpdates","result","publishSourceRepositoryUpdated","source","slug","connectionName","displayName","receivedAt","Date","toISOString","published","parsed","toSourceRepositoryIdentity","Map","repositories_added","repositories_removed","normalized","set","size","values","externalRepositoryId","owner","login","name","defaultBranch","default_branch","shouldDeleteInstallationTokenSecret","installationTokenCleanup","ref","stripRefsHeads","push","headCommitSha","isDefaultBranch","publishSourcePush","startsWith","slice","length"],"mappings":"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"}
package/dist/db/db.d.ts CHANGED
@@ -211,7 +211,7 @@ export declare const schema: {
211
211
  generated: undefined;
212
212
  }, {}, {}>;
213
213
  };
214
- dialect: "pg";
214
+ dialect: 'pg';
215
215
  }>;
216
216
  };
217
217
  export declare function db(): NodePgDatabase<{
@@ -426,7 +426,7 @@ export declare function db(): NodePgDatabase<{
426
426
  generated: undefined;
427
427
  }, {}, {}>;
428
428
  };
429
- dialect: "pg";
429
+ dialect: 'pg';
430
430
  }>;
431
431
  }>;
432
432
  export declare function closeDb(): void;
@@ -210,7 +210,7 @@ export declare const githubInstallations: import("drizzle-orm/pg-core").PgTableW
210
210
  generated: undefined;
211
211
  }, {}, {}>;
212
212
  };
213
- dialect: "pg";
213
+ dialect: 'pg';
214
214
  }>;
215
215
  export type GithubInstallationDb = typeof githubInstallations.$inferSelect;
216
216
  export type GithubInstallationCreateDb = typeof githubInstallations.$inferInsert;