@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/api/shared-installation-token-cache.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/source-control.d.ts +2 -1
- package/dist/core/source-control.d.ts.map +1 -1
- package/dist/core/source-control.js +56 -1
- package/dist/core/source-control.js.map +1 -1
- package/dist/core/webhook-processor.d.ts +2 -1
- package/dist/core/webhook-processor.d.ts.map +1 -1
- package/dist/core/webhook-processor.js +1 -0
- package/dist/core/webhook-processor.js.map +1 -1
- package/dist/core/webhook.d.ts +3 -2
- package/dist/core/webhook.d.ts.map +1 -1
- package/dist/core/webhook.js +103 -1
- package/dist/core/webhook.js.map +1 -1
- package/dist/db/db.d.ts +2 -2
- package/dist/db/schema/installations.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/presentation/routes/webhooks.d.ts +2 -1
- package/dist/presentation/routes/webhooks.d.ts.map +1 -1
- package/dist/presentation/routes/webhooks.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/connection-external-url.test.ts +1 -0
- package/src/core/agent-tools.test.ts +1 -0
- package/src/core/source-control.test.ts +80 -0
- package/src/core/source-control.ts +72 -0
- package/src/core/webhook-processor.test.ts +4 -0
- package/src/core/webhook-processor.ts +3 -0
- package/src/core/webhook.test.ts +197 -2
- package/src/core/webhook.ts +150 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +3 -0
- package/src/presentation/routes/install.test.ts +2 -1
- package/src/presentation/routes/webhooks.test.ts +49 -1
- package/src/presentation/routes/webhooks.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled: 30 files with swc (
|
|
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;
|
|
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;
|
|
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
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/core/webhook.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/core/webhook.js
CHANGED
|
@@ -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
|
}
|
package/dist/core/webhook.js.map
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
213
|
+
dialect: 'pg';
|
|
214
214
|
}>;
|
|
215
215
|
export type GithubInstallationDb = typeof githubInstallations.$inferSelect;
|
|
216
216
|
export type GithubInstallationCreateDb = typeof githubInstallations.$inferInsert;
|