@useairfoil/producer-github 0.0.1 → 0.0.3

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 (60) hide show
  1. package/LICENSE.txt +201 -0
  2. package/README.md +116 -0
  3. package/dist/index.d.mts +1939 -0
  4. package/dist/index.mjs +3 -0
  5. package/dist/main.d.mts +1 -0
  6. package/dist/main.mjs +700 -0
  7. package/dist/main.mjs.map +1 -0
  8. package/dist/manifest.d.mts +56 -0
  9. package/dist/manifest.mjs +137 -0
  10. package/dist/manifest.mjs.map +1 -0
  11. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  12. package/dist/src-DKDUc00e.mjs +1563 -0
  13. package/dist/src-DKDUc00e.mjs.map +1 -0
  14. package/package.json +46 -8
  15. package/src/client/auth.ts +113 -0
  16. package/src/client/client.ts +192 -0
  17. package/src/client/constants.ts +4 -0
  18. package/src/connector.ts +97 -0
  19. package/src/create-table.ts +10 -0
  20. package/src/demo/changes.ts +420 -0
  21. package/src/demo/index.ts +59 -0
  22. package/src/demo/setup.ts +34 -0
  23. package/src/demo/shared.ts +101 -0
  24. package/src/index.ts +13 -0
  25. package/src/main.ts +28 -0
  26. package/src/manifest.ts +45 -0
  27. package/src/resources/check-runs.ts +72 -0
  28. package/src/resources/commit-statuses.ts +84 -0
  29. package/src/resources/commits.ts +101 -0
  30. package/src/resources/issue-comments.ts +75 -0
  31. package/src/resources/issues.ts +78 -0
  32. package/src/resources/pull-request-commits.ts +91 -0
  33. package/src/resources/pull-request-review-comments.ts +78 -0
  34. package/src/resources/pull-request-reviews.ts +76 -0
  35. package/src/resources/pull-requests.ts +79 -0
  36. package/src/resources/releases.ts +74 -0
  37. package/src/resources/repositories.ts +101 -0
  38. package/src/resources/shared.ts +480 -0
  39. package/src/resources/workflow-jobs.ts +73 -0
  40. package/src/resources/workflow-runs.ts +70 -0
  41. package/src/sandbox.ts +35 -0
  42. package/src/schemas/check-runs.ts +72 -0
  43. package/src/schemas/commit-statuses.ts +51 -0
  44. package/src/schemas/commits.ts +80 -0
  45. package/src/schemas/events.ts +139 -0
  46. package/src/schemas/issue-comments.ts +48 -0
  47. package/src/schemas/issues.ts +76 -0
  48. package/src/schemas/pull-request-commits.ts +42 -0
  49. package/src/schemas/pull-request-review-comments.ts +85 -0
  50. package/src/schemas/pull-request-reviews.ts +53 -0
  51. package/src/schemas/pull-requests.ts +95 -0
  52. package/src/schemas/releases.ts +70 -0
  53. package/src/schemas/repositories.ts +73 -0
  54. package/src/schemas/shared.ts +76 -0
  55. package/src/schemas/workflow-jobs.ts +78 -0
  56. package/src/schemas/workflow-runs.ts +72 -0
  57. package/src/start.ts +47 -0
  58. package/src/tables.ts +31 -0
  59. package/src/webhook/route.ts +46 -0
  60. package/src/webhook/signature.ts +20 -0
@@ -0,0 +1,45 @@
1
+ import * as Manifest from "@useairfoil/connector-kit/manifest";
2
+
3
+ export const GitHubConfigDef = Manifest.defineConfig({
4
+ appClientId: Manifest.string({
5
+ runtimeKey: "GITHUB_APP_CLIENT_ID",
6
+ description: "Client ID of the GitHub App, for example Iv23li...",
7
+ }),
8
+ appPrivateKey: Manifest.secret({
9
+ runtimeKey: "GITHUB_APP_PRIVATE_KEY",
10
+ description: "PEM private key of the GitHub App. Newlines may be written as \\n.",
11
+ }),
12
+ installationId: Manifest.number({
13
+ runtimeKey: "GITHUB_INSTALLATION_ID",
14
+ description: "ID of the GitHub App installation to sync.",
15
+ integer: true,
16
+ minimum: 1,
17
+ }),
18
+ webhookSecret: Manifest.secret({
19
+ runtimeKey: "GITHUB_WEBHOOK_SECRET",
20
+ description: "Webhook secret of the GitHub App.",
21
+ }),
22
+ });
23
+
24
+ export type GitHubConfig = Manifest.ConfigValuesOf<typeof GitHubConfigDef>;
25
+
26
+ export const manifest = Manifest.define({
27
+ name: "producer-github",
28
+ title: "GitHub",
29
+ config: GitHubConfigDef.spec,
30
+ resources: [
31
+ { name: "repositories", capabilities: ["backfill", "changes", "webhook"] },
32
+ { name: "issues", capabilities: ["backfill", "changes", "webhook"] },
33
+ { name: "pull_requests", capabilities: ["backfill", "changes", "webhook"] },
34
+ { name: "issue_comments", capabilities: ["backfill", "changes", "webhook"] },
35
+ { name: "pull_request_reviews", capabilities: ["backfill", "changes", "webhook"] },
36
+ { name: "pull_request_review_comments", capabilities: ["backfill", "changes", "webhook"] },
37
+ { name: "pull_request_commits", capabilities: ["backfill", "changes", "webhook"] },
38
+ { name: "commits", capabilities: ["backfill", "changes", "webhook"] },
39
+ { name: "check_runs", capabilities: ["backfill", "changes", "webhook"] },
40
+ { name: "commit_statuses", capabilities: ["backfill", "changes", "webhook"] },
41
+ { name: "workflow_runs", capabilities: ["backfill", "changes", "webhook"] },
42
+ { name: "workflow_jobs", capabilities: ["backfill", "changes", "webhook"] },
43
+ { name: "releases", capabilities: ["backfill", "changes", "webhook"] },
44
+ ],
45
+ });
@@ -0,0 +1,72 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { DateTime, Effect } from "effect";
3
+
4
+ import type { GitHubClientService } from "../client/client";
5
+ import type { CommitObject } from "../schemas/commits";
6
+ import type { RepositoryRef } from "../schemas/repositories";
7
+
8
+ import {
9
+ type CheckRun,
10
+ type CheckRunObject,
11
+ CheckRunObjectSchema,
12
+ CheckRunSchema,
13
+ } from "../schemas/check-runs";
14
+ import { CheckRunEventSchema, WebhookPayloadSchema } from "../schemas/events";
15
+ import { commitSpec } from "./commits";
16
+ import {
17
+ type RepoResourceSpec,
18
+ backfillRepoResource,
19
+ changesRepoResource,
20
+ checkRepoResource,
21
+ childRows,
22
+ decodeEvent,
23
+ wrappedList,
24
+ } from "./shared";
25
+
26
+ const toRow = (checkRun: CheckRunObject, repository_id: bigint, version: Date): CheckRun => ({
27
+ ...checkRun,
28
+ version,
29
+ repository_id,
30
+ });
31
+
32
+ /** Check runs of default-branch commits. Webhooks also bring other branches. */
33
+ const checkRunSpec = (client: GitHubClientService): RepoResourceSpec<CommitObject, CheckRun> => ({
34
+ ...commitSpec,
35
+ rows: childRows(client, {
36
+ path: (commit) => `commits/${commit.sha}/check-runs`,
37
+ pageSchema: wrappedList("check_runs", CheckRunObjectSchema),
38
+ // Without it, a re-run hides the earlier runs.
39
+ params: { filter: "all" },
40
+ toRow: (checkRun, _commit, repositoryId, readAt) => toRow(checkRun, repositoryId, readAt),
41
+ }),
42
+ });
43
+
44
+ export const makeCheckRuns = (
45
+ client: GitHubClientService,
46
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
47
+ ) => {
48
+ const spec = checkRunSpec(client);
49
+ return Resource.entity({
50
+ name: "check_runs",
51
+ rowSchema: CheckRunSchema,
52
+ version: "version",
53
+
54
+ check: checkRepoResource(client, spec),
55
+ backfill: backfillRepoResource(client, repositories, spec),
56
+ changes: changesRepoResource(client, spec),
57
+ webhook: {
58
+ schema: WebhookPayloadSchema,
59
+ // No update time, so read the latest state instead of trusting the payload.
60
+ handler: ({ payload }) =>
61
+ Effect.gen(function* () {
62
+ const { repository, check_run } = yield* decodeEvent(CheckRunEventSchema, payload);
63
+ const readAt = yield* DateTime.nowAsDate;
64
+ const page = yield* client.get(
65
+ CheckRunObjectSchema,
66
+ `/repos/${repository.full_name}/check-runs/${check_run.id}`,
67
+ );
68
+ return [toRow(page.body, repository.id, readAt)];
69
+ }),
70
+ },
71
+ });
72
+ };
@@ -0,0 +1,84 @@
1
+ import { ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Array as Arr, Effect, Schema } from "effect";
3
+
4
+ import type { CommitObject } from "../schemas/commits";
5
+ import type { RepositoryRef } from "../schemas/repositories";
6
+
7
+ import { type GitHubClientService, withParams } from "../client/client";
8
+ import {
9
+ type CommitStatus,
10
+ type CommitStatusObject,
11
+ CommitStatusObjectSchema,
12
+ CommitStatusSchema,
13
+ } from "../schemas/commit-statuses";
14
+ import { StatusEventSchema, WebhookPayloadSchema } from "../schemas/events";
15
+ import { commitSpec } from "./commits";
16
+ import {
17
+ type RepoResourceSpec,
18
+ backfillRepoResource,
19
+ changesRepoResource,
20
+ checkRepoResource,
21
+ childRows,
22
+ decodeEvent,
23
+ pageSize,
24
+ readPages,
25
+ } from "./shared";
26
+
27
+ const toRow = (
28
+ { updated_at, ...rest }: CommitStatusObject,
29
+ sha: string,
30
+ repository_id: bigint,
31
+ ): CommitStatus => ({ ...rest, version: updated_at, repository_id, sha });
32
+
33
+ /** Statuses of default-branch commits. Webhooks also bring other branches. */
34
+ const commitStatusSpec = (
35
+ client: GitHubClientService,
36
+ ): RepoResourceSpec<CommitObject, CommitStatus> => ({
37
+ ...commitSpec,
38
+ rows: childRows(client, {
39
+ path: (commit) => `commits/${commit.sha}/statuses`,
40
+ pageSchema: Schema.Array(CommitStatusObjectSchema),
41
+ toRow: (status, commit, repositoryId) => toRow(status, commit.sha, repositoryId),
42
+ }),
43
+ });
44
+
45
+ export const makeCommitStatuses = (
46
+ client: GitHubClientService,
47
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
48
+ ) => {
49
+ const spec = commitStatusSpec(client);
50
+ return Resource.entity({
51
+ name: "commit_statuses",
52
+ rowSchema: CommitStatusSchema,
53
+ version: "version",
54
+
55
+ check: checkRepoResource(client, spec),
56
+ backfill: backfillRepoResource(client, repositories, spec),
57
+ changes: changesRepoResource(client, spec),
58
+ webhook: {
59
+ schema: WebhookPayloadSchema,
60
+ // The payload has no node ID or creator, so read the status from the list.
61
+ handler: ({ payload }) =>
62
+ Effect.gen(function* () {
63
+ const { id, sha, repository } = yield* decodeEvent(StatusEventSchema, payload);
64
+ const isStatus = (status: CommitStatusObject) => status.id === id;
65
+ const status = yield* readPages(
66
+ client,
67
+ Schema.Array(CommitStatusObjectSchema),
68
+ withParams(`/repos/${repository.full_name}/commits/${sha}/statuses`, {
69
+ per_page: pageSize,
70
+ }),
71
+ (page) => !page.some(isStatus),
72
+ ).pipe(
73
+ Effect.flatMap((statuses) =>
74
+ Effect.fromOption(
75
+ Arr.findFirst(statuses, isStatus),
76
+ () => new ConnectorError({ message: `Commit status ${id} is not in the list` }),
77
+ ),
78
+ ),
79
+ );
80
+ return [toRow(status, sha, repository.id)];
81
+ }),
82
+ },
83
+ });
84
+ };
@@ -0,0 +1,101 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ import type { RepositoryRef } from "../schemas/repositories";
5
+
6
+ import { type GitHubClientService, withParams } from "../client/client";
7
+ import {
8
+ type Commit,
9
+ type CommitObject,
10
+ CommitObjectSchema,
11
+ CommitSchema,
12
+ } from "../schemas/commits";
13
+ import { PushEventSchema, WebhookPayloadSchema } from "../schemas/events";
14
+ import {
15
+ type RepoResourceSpec,
16
+ backfillRepoResource,
17
+ changesRepoResource,
18
+ checkRepoResource,
19
+ decodeEvent,
20
+ pageSize,
21
+ readPages,
22
+ rowsOf,
23
+ wrappedList,
24
+ } from "./shared";
25
+
26
+ const toRow = (
27
+ { sha, node_id, commit, author, committer, parents, html_url }: CommitObject,
28
+ repository_id: bigint,
29
+ ): Commit => ({
30
+ key: `${repository_id}:${sha}`,
31
+ version: commit.committer.date,
32
+ repository_id,
33
+ sha,
34
+ node_id,
35
+ message: commit.message,
36
+ author: commit.author,
37
+ committer: commit.committer,
38
+ author_user: author,
39
+ committer_user: committer,
40
+ parents: parents.map((parent) => parent.sha),
41
+ verified: commit.verification?.verified ?? false,
42
+ html_url,
43
+ });
44
+
45
+ const committedAt = (item: CommitObject) => item.commit.committer.date;
46
+
47
+ /** The list only has the default branch. */
48
+ export const commitSpec: RepoResourceSpec<CommitObject, Commit> = {
49
+ path: "commits",
50
+ pageSchema: Schema.Array(CommitObjectSchema),
51
+ params: {},
52
+ // `since` filters on the commit time, not the push time. Pushes of older
53
+ // commits come by webhook.
54
+ since: true,
55
+ order: "newest",
56
+ createdAt: committedAt,
57
+ updatedAt: committedAt,
58
+ keep: () => true,
59
+ enabled: (repository) => repository.size > 0,
60
+ rows: rowsOf(toRow),
61
+ };
62
+
63
+ const noCommit = /^0+$/;
64
+
65
+ export const makeCommits = (
66
+ client: GitHubClientService,
67
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
68
+ ) =>
69
+ Resource.entity({
70
+ name: "commits",
71
+ rowSchema: CommitSchema,
72
+ version: "version",
73
+
74
+ check: checkRepoResource(client, commitSpec),
75
+ backfill: backfillRepoResource(client, repositories, commitSpec),
76
+ changes: changesRepoResource(client, commitSpec),
77
+ webhook: {
78
+ schema: WebhookPayloadSchema,
79
+ // The push payload is not the API's commit, so the commits are read from
80
+ // the compare API. A new or deleted branch has nothing to compare.
81
+ handler: ({ payload }) =>
82
+ Effect.gen(function* () {
83
+ const { ref, before, after, repository } = yield* decodeEvent(PushEventSchema, payload);
84
+ if (
85
+ ref !== `refs/heads/${repository.default_branch}` ||
86
+ noCommit.test(before) ||
87
+ noCommit.test(after)
88
+ ) {
89
+ return [];
90
+ }
91
+ const commits = yield* readPages(
92
+ client,
93
+ wrappedList("commits", CommitObjectSchema),
94
+ withParams(`/repos/${repository.full_name}/compare/${before}...${after}`, {
95
+ per_page: pageSize,
96
+ }),
97
+ );
98
+ return commits.map((commit) => toRow(commit, repository.id));
99
+ }),
100
+ },
101
+ });
@@ -0,0 +1,75 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ import type { GitHubClientService } from "../client/client";
5
+ import type { RepositoryRef } from "../schemas/repositories";
6
+
7
+ import {
8
+ IssueCommentDeletedEventSchema,
9
+ IssueCommentEventSchema,
10
+ WebhookPayloadSchema,
11
+ } from "../schemas/events";
12
+ import {
13
+ type IssueComment,
14
+ type IssueCommentObject,
15
+ IssueCommentObjectSchema,
16
+ IssueCommentSchema,
17
+ } from "../schemas/issue-comments";
18
+ import {
19
+ type RepoResourceSpec,
20
+ backfillRepoResource,
21
+ changesRepoResource,
22
+ checkRepoResource,
23
+ decodeEvent,
24
+ deleteRow,
25
+ numberAtEnd,
26
+ rowsOf,
27
+ timestamps,
28
+ } from "./shared";
29
+
30
+ const toRow = (
31
+ { updated_at, issue_url, ...rest }: IssueCommentObject,
32
+ repository_id: bigint,
33
+ ): IssueComment => ({
34
+ ...rest,
35
+ version: updated_at,
36
+ repository_id,
37
+ issue_number: numberAtEnd(issue_url),
38
+ });
39
+
40
+ export const issueCommentSpec: RepoResourceSpec<IssueCommentObject, IssueComment> = {
41
+ path: "issues/comments",
42
+ pageSchema: Schema.Array(IssueCommentObjectSchema),
43
+ params: {},
44
+ since: true,
45
+ order: "sorted",
46
+ ...timestamps,
47
+ keep: () => true,
48
+ enabled: (repository) => repository.has_issues || repository.has_pull_requests,
49
+ rows: rowsOf(toRow),
50
+ };
51
+
52
+ export const makeIssueComments = (
53
+ client: GitHubClientService,
54
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
55
+ ) =>
56
+ Resource.entity({
57
+ name: "issue_comments",
58
+ rowSchema: IssueCommentSchema,
59
+ version: "version",
60
+
61
+ check: checkRepoResource(client, issueCommentSpec),
62
+ backfill: backfillRepoResource(client, repositories, issueCommentSpec),
63
+ changes: changesRepoResource(client, issueCommentSpec),
64
+ webhook: {
65
+ schema: WebhookPayloadSchema,
66
+ handler: ({ payload }) =>
67
+ Effect.gen(function* () {
68
+ const event = yield* decodeEvent(IssueCommentEventSchema, payload);
69
+ if (Schema.is(IssueCommentDeletedEventSchema)(event)) {
70
+ return [yield* deleteRow(event.comment.id)];
71
+ }
72
+ return [toRow(event.comment, event.repository.id)];
73
+ }),
74
+ },
75
+ });
@@ -0,0 +1,78 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ import type { GitHubClientService } from "../client/client";
5
+ import type { RepositoryRef } from "../schemas/repositories";
6
+
7
+ import {
8
+ IssueRemovedEventSchema,
9
+ IssuesEventSchema,
10
+ WebhookPayloadSchema,
11
+ } from "../schemas/events";
12
+ import { type Issue, type IssueObject, IssueObjectSchema, IssueSchema } from "../schemas/issues";
13
+ import {
14
+ type RepoResourceSpec,
15
+ backfillRepoResource,
16
+ changesRepoResource,
17
+ checkRepoResource,
18
+ decodeEvent,
19
+ deleteRow,
20
+ rowsOf,
21
+ timestamps,
22
+ } from "./shared";
23
+
24
+ const toRow = (
25
+ { updated_at, labels, assignees, pull_request: _pullRequest, ...rest }: IssueObject,
26
+ repository_id: bigint,
27
+ ): Issue => ({
28
+ ...rest,
29
+ version: updated_at,
30
+ repository_id,
31
+ labels: labels ?? [],
32
+ assignees: assignees ?? [],
33
+ });
34
+
35
+ // A webhook can leave a list out, so read the issue again.
36
+ const hasLists = ({ labels }: IssueObject) => labels !== undefined;
37
+
38
+ export const issueSpec: RepoResourceSpec<IssueObject, Issue> = {
39
+ path: "issues",
40
+ pageSchema: Schema.Array(IssueObjectSchema),
41
+ params: { state: "all" },
42
+ since: true,
43
+ order: "sorted",
44
+ ...timestamps,
45
+ keep: (item) => item.pull_request === undefined,
46
+ enabled: (repository) => repository.has_issues,
47
+ rows: rowsOf(toRow),
48
+ };
49
+
50
+ export const makeIssues = (
51
+ client: GitHubClientService,
52
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
53
+ ) =>
54
+ Resource.entity({
55
+ name: "issues",
56
+ rowSchema: IssueSchema,
57
+ version: "version",
58
+
59
+ check: checkRepoResource(client, issueSpec),
60
+ backfill: backfillRepoResource(client, repositories, issueSpec),
61
+ changes: changesRepoResource(client, issueSpec),
62
+ webhook: {
63
+ schema: WebhookPayloadSchema,
64
+ handler: ({ payload }) =>
65
+ Effect.gen(function* () {
66
+ const event = yield* decodeEvent(IssuesEventSchema, payload);
67
+ if (Schema.is(IssueRemovedEventSchema)(event)) return [yield* deleteRow(event.issue.id)];
68
+ const { issue, repository } = event;
69
+ const full = hasLists(issue)
70
+ ? issue
71
+ : (yield* client.get(
72
+ IssueObjectSchema,
73
+ `/repos/${repository.full_name}/issues/${issue.number}`,
74
+ )).body;
75
+ return [toRow(full, repository.id)];
76
+ }),
77
+ },
78
+ });
@@ -0,0 +1,91 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ import type { PullRequestObject } from "../schemas/pull-requests";
5
+ import type { RepositoryRef } from "../schemas/repositories";
6
+
7
+ import { type GitHubClientService, withParams } from "../client/client";
8
+ import { type CommitObject, CommitObjectSchema } from "../schemas/commits";
9
+ import { PullRequestCommitsEventSchema, WebhookPayloadSchema } from "../schemas/events";
10
+ import { type PullRequestCommit, PullRequestCommitSchema } from "../schemas/pull-request-commits";
11
+ import { pullRequestSpec } from "./pull-requests";
12
+ import {
13
+ type RepoResourceSpec,
14
+ backfillRepoResource,
15
+ changesRepoResource,
16
+ checkRepoResource,
17
+ childRows,
18
+ decodeEvent,
19
+ pageSize,
20
+ readPages,
21
+ } from "./shared";
22
+
23
+ const toRow = (
24
+ { sha, commit, author, committer, html_url }: CommitObject,
25
+ pullRequest: { readonly number: bigint; readonly updated_at: Date },
26
+ repository_id: bigint,
27
+ ): PullRequestCommit => ({
28
+ key: `${repository_id}:${pullRequest.number}:${sha}`,
29
+ version: pullRequest.updated_at,
30
+ repository_id,
31
+ pull_request_number: pullRequest.number,
32
+ sha,
33
+ message: commit.message,
34
+ author: commit.author,
35
+ committer: commit.committer,
36
+ author_user: author,
37
+ committer_user: committer,
38
+ html_url,
39
+ });
40
+
41
+ const commitsPath = (pullRequest: { readonly number: bigint }) =>
42
+ `pulls/${pullRequest.number}/commits`;
43
+
44
+ const pullRequestCommitSpec = (
45
+ client: GitHubClientService,
46
+ ): RepoResourceSpec<PullRequestObject, PullRequestCommit> => ({
47
+ ...pullRequestSpec,
48
+ rows: childRows(client, {
49
+ path: (pullRequest) => commitsPath(pullRequest),
50
+ pageSchema: Schema.Array(CommitObjectSchema),
51
+ toRow,
52
+ }),
53
+ });
54
+
55
+ // Other actions don't change the commits. An edit does only when it changes the base.
56
+ const commitActions = new Set(["opened", "reopened", "synchronize"]);
57
+
58
+ export const makePullRequestCommits = (
59
+ client: GitHubClientService,
60
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
61
+ ) => {
62
+ const spec = pullRequestCommitSpec(client);
63
+ return Resource.entity({
64
+ name: "pull_request_commits",
65
+ rowSchema: PullRequestCommitSchema,
66
+ version: "version",
67
+
68
+ check: checkRepoResource(client, spec),
69
+ backfill: backfillRepoResource(client, repositories, spec),
70
+ changes: changesRepoResource(client, spec),
71
+ webhook: {
72
+ schema: WebhookPayloadSchema,
73
+ handler: ({ payload }) =>
74
+ Effect.gen(function* () {
75
+ const { action, changes, repository, pull_request } = yield* decodeEvent(
76
+ PullRequestCommitsEventSchema,
77
+ payload,
78
+ );
79
+ if (!commitActions.has(action) && changes?.base === undefined) return [];
80
+ const commits = yield* readPages(
81
+ client,
82
+ Schema.Array(CommitObjectSchema),
83
+ withParams(`/repos/${repository.full_name}/${commitsPath(pull_request)}`, {
84
+ per_page: pageSize,
85
+ }),
86
+ );
87
+ return commits.map((commit) => toRow(commit, pull_request, repository.id));
88
+ }),
89
+ },
90
+ });
91
+ };
@@ -0,0 +1,78 @@
1
+ import { type ConnectorError, Resource } from "@useairfoil/connector-kit";
2
+ import { Effect, Schema } from "effect";
3
+
4
+ import type { GitHubClientService } from "../client/client";
5
+ import type { RepositoryRef } from "../schemas/repositories";
6
+
7
+ import {
8
+ PullRequestReviewCommentDeletedEventSchema,
9
+ PullRequestReviewCommentEventSchema,
10
+ WebhookPayloadSchema,
11
+ } from "../schemas/events";
12
+ import {
13
+ type PullRequestReviewComment,
14
+ type PullRequestReviewCommentObject,
15
+ PullRequestReviewCommentObjectSchema,
16
+ PullRequestReviewCommentSchema,
17
+ } from "../schemas/pull-request-review-comments";
18
+ import {
19
+ type RepoResourceSpec,
20
+ backfillRepoResource,
21
+ changesRepoResource,
22
+ checkRepoResource,
23
+ decodeEvent,
24
+ deleteRow,
25
+ numberAtEnd,
26
+ rowsOf,
27
+ timestamps,
28
+ } from "./shared";
29
+
30
+ const toRow = (
31
+ { updated_at, pull_request_url, ...rest }: PullRequestReviewCommentObject,
32
+ repository_id: bigint,
33
+ ): PullRequestReviewComment => ({
34
+ ...rest,
35
+ version: updated_at,
36
+ repository_id,
37
+ pull_request_number: numberAtEnd(pull_request_url),
38
+ });
39
+
40
+ export const pullRequestReviewCommentSpec: RepoResourceSpec<
41
+ PullRequestReviewCommentObject,
42
+ PullRequestReviewComment
43
+ > = {
44
+ path: "pulls/comments",
45
+ pageSchema: Schema.Array(PullRequestReviewCommentObjectSchema),
46
+ params: {},
47
+ since: true,
48
+ order: "sorted",
49
+ ...timestamps,
50
+ keep: () => true,
51
+ enabled: (repository) => repository.has_pull_requests,
52
+ rows: rowsOf(toRow),
53
+ };
54
+
55
+ export const makePullRequestReviewComments = (
56
+ client: GitHubClientService,
57
+ repositories: Effect.Effect<ReadonlyArray<RepositoryRef>, ConnectorError>,
58
+ ) =>
59
+ Resource.entity({
60
+ name: "pull_request_review_comments",
61
+ rowSchema: PullRequestReviewCommentSchema,
62
+ version: "version",
63
+
64
+ check: checkRepoResource(client, pullRequestReviewCommentSpec),
65
+ backfill: backfillRepoResource(client, repositories, pullRequestReviewCommentSpec),
66
+ changes: changesRepoResource(client, pullRequestReviewCommentSpec),
67
+ webhook: {
68
+ schema: WebhookPayloadSchema,
69
+ handler: ({ payload }) =>
70
+ Effect.gen(function* () {
71
+ const event = yield* decodeEvent(PullRequestReviewCommentEventSchema, payload);
72
+ if (Schema.is(PullRequestReviewCommentDeletedEventSchema)(event)) {
73
+ return [yield* deleteRow(event.comment.id)];
74
+ }
75
+ return [toRow(event.comment, event.repository.id)];
76
+ }),
77
+ },
78
+ });