@rawdash/connector-github 0.15.0 → 0.16.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/dist/index.js CHANGED
@@ -1,21 +1,62 @@
1
1
  // ../../connector-shared/dist/index.js
2
2
  var HTTP_CLIENT_VERSION = "0.0.0";
3
3
  var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
4
- var githubRateLimit = {
5
- parse(h) {
6
- const remainingRaw = h.get("x-ratelimit-remaining");
7
- const resetRaw = h.get("x-ratelimit-reset");
8
- if (remainingRaw === null || resetRaw === null) {
9
- return null;
4
+ function connectorUserAgent(connectorId) {
5
+ return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
6
+ }
7
+ function standardRateLimitPolicy(config) {
8
+ const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;
9
+ const multiplier = resetUnit === "s" ? 1e3 : 1;
10
+ return {
11
+ parse(h) {
12
+ const remainingRaw = h.get(remainingHeader);
13
+ if (remainingRaw === null || remainingRaw.trim() === "") {
14
+ return null;
15
+ }
16
+ const remaining = Number(remainingRaw);
17
+ if (!Number.isFinite(remaining)) {
18
+ return null;
19
+ }
20
+ const resetRaw = h.get(resetHeader);
21
+ if (resetRaw === null) {
22
+ if (resetFallbackMs === void 0) {
23
+ return null;
24
+ }
25
+ return {
26
+ remaining,
27
+ resetAt: new Date(Date.now() + resetFallbackMs)
28
+ };
29
+ }
30
+ if (resetRaw.trim() === "") {
31
+ return null;
32
+ }
33
+ const reset = Number(resetRaw);
34
+ if (!Number.isFinite(reset) || reset < 0) {
35
+ return null;
36
+ }
37
+ const resetMs = reset * multiplier;
38
+ if (!Number.isFinite(resetMs)) {
39
+ return null;
40
+ }
41
+ return { remaining, resetAt: new Date(resetMs) };
10
42
  }
11
- const remaining = Number(remainingRaw);
12
- const reset = Number(resetRaw);
13
- if (!Number.isFinite(remaining) || !Number.isFinite(reset) || reset < 0) {
43
+ };
44
+ }
45
+ function sanitizeAllowedUrl(options) {
46
+ const { url, host, pathname, protocol = "https:" } = options;
47
+ if (url === null) {
48
+ return null;
49
+ }
50
+ try {
51
+ const u = new URL(url);
52
+ if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {
14
53
  return null;
15
54
  }
16
- return { remaining, resetAt: new Date(reset * 1e3) };
55
+ return u.toString();
56
+ } catch {
57
+ return null;
17
58
  }
18
- };
59
+ }
19
60
  function parseLinkHeader(header) {
20
61
  if (!header) {
21
62
  return {};
@@ -34,7 +75,11 @@ function parseLinkHeader(header) {
34
75
  import {
35
76
  BaseConnector,
36
77
  defineConfigFields,
37
- paginateChunked
78
+ defineConnectorDoc,
79
+ defineResources,
80
+ makeChunkedCursorGuard,
81
+ paginateChunked,
82
+ schemasFromResources
38
83
  } from "@rawdash/core";
39
84
  import { z } from "zod";
40
85
  var configFields = defineConfigFields(
@@ -56,12 +101,41 @@ var configFields = defineConfigFields(
56
101
  })
57
102
  })
58
103
  );
104
+ var doc = defineConnectorDoc({
105
+ displayName: "GitHub",
106
+ category: "engineering",
107
+ brandColor: "#181717",
108
+ tagline: "Sync pull requests, issues, deployments, releases, CI runs, and contributor activity from a GitHub repository.",
109
+ vendor: {
110
+ name: "GitHub",
111
+ apiDocs: "https://docs.github.com/rest",
112
+ website: "https://github.com"
113
+ },
114
+ auth: {
115
+ summary: "A personal access token is optional for public repositories but required for private repos and to avoid the low unauthenticated rate limit.",
116
+ setup: [
117
+ "Open GitHub \u2192 Settings \u2192 Developer settings \u2192 Personal access tokens.",
118
+ "Generate a token with the `repo` scope (read access is sufficient).",
119
+ 'Store it as a secret and reference it from the connector config as `token: secret("GITHUB_TOKEN")`.'
120
+ ]
121
+ },
122
+ rateLimit: "Unauthenticated requests share GitHub\u2019s low 60 requests/hour limit; an authenticated token raises it to 5,000 requests/hour.",
123
+ limitations: [
124
+ "The GitHub REST API can return the same item more than once within a sync (cursor pagination overlapping a mutating collection, retried requests, or an item surfaced via multiple endpoints). Each resource dedupes by stable id before writing, keeping the last copy seen.",
125
+ "Public repositories without a token are subject to GitHub\u2019s low unauthenticated rate limit."
126
+ ]
127
+ });
59
128
  var githubCredentials = {
60
129
  token: {
61
130
  description: "GitHub personal access token",
62
131
  auth: "optional"
63
132
  }
64
133
  };
134
+ var githubRateLimit = standardRateLimitPolicy({
135
+ remainingHeader: "x-ratelimit-remaining",
136
+ resetHeader: "x-ratelimit-reset",
137
+ resetUnit: "s"
138
+ });
65
139
  var PHASE_ORDER = [
66
140
  "repo_stats",
67
141
  "workflow_runs",
@@ -71,6 +145,23 @@ var PHASE_ORDER = [
71
145
  "releases",
72
146
  "contributors"
73
147
  ];
148
+ var PHASE_RESOURCES = {
149
+ repo_stats: ["repo"],
150
+ workflow_runs: ["workflow_run"],
151
+ pull_requests: ["pull_request"],
152
+ issues: ["issue"],
153
+ deployments: ["deployment"],
154
+ releases: ["release"],
155
+ contributors: ["contributor"]
156
+ };
157
+ function selectPhases(allowlist) {
158
+ if (allowlist === void 0) {
159
+ return PHASE_ORDER;
160
+ }
161
+ return PHASE_ORDER.filter(
162
+ (phase) => PHASE_RESOURCES[phase].some((r) => allowlist.has(r))
163
+ );
164
+ }
74
165
  var CONTRIBUTORS_SKIPPED = /* @__PURE__ */ Symbol("contributors-skipped");
75
166
  function dedupeByKey(items, keyFn, resource) {
76
167
  if (items.length < 2) {
@@ -92,23 +183,9 @@ function dedupeByKey(items, keyFn, resource) {
92
183
  }
93
184
  return Array.from(seen.values());
94
185
  }
95
- function isGitHubSyncCursor(value) {
96
- if (typeof value !== "object" || value === null) {
97
- return false;
98
- }
99
- const v = value;
100
- if (typeof v.phase !== "string") {
101
- return false;
102
- }
103
- if (!PHASE_ORDER.includes(v.phase)) {
104
- return false;
105
- }
106
- if (v.page !== null && typeof v.page !== "string") {
107
- return false;
108
- }
109
- return true;
110
- }
186
+ var isGitHubSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
111
187
  var workflowRunsResponseSchema = z.object({
188
+ total_count: z.number().int().optional(),
112
189
  workflow_runs: z.array(
113
190
  z.object({
114
191
  id: z.number().int(),
@@ -119,7 +196,33 @@ var workflowRunsResponseSchema = z.object({
119
196
  actor: z.object({ login: z.string().min(1) }).nullable(),
120
197
  created_at: z.iso.datetime(),
121
198
  updated_at: z.iso.datetime(),
122
- run_attempt: z.number().int()
199
+ run_attempt: z.number().int(),
200
+ artifacts_url: z.string().optional(),
201
+ cancel_url: z.string().optional(),
202
+ check_suite_id: z.number().int().optional(),
203
+ check_suite_node_id: z.string().optional(),
204
+ check_suite_url: z.string().optional(),
205
+ display_title: z.string().optional(),
206
+ event: z.string().optional(),
207
+ head_commit: z.unknown().optional(),
208
+ head_repository: z.unknown().optional(),
209
+ head_sha: z.string().optional(),
210
+ html_url: z.string().optional(),
211
+ jobs_url: z.string().optional(),
212
+ logs_url: z.string().optional(),
213
+ node_id: z.string().optional(),
214
+ path: z.string().optional(),
215
+ previous_attempt_url: z.string().nullable().optional(),
216
+ pull_requests: z.array(z.unknown()).optional(),
217
+ referenced_workflows: z.array(z.unknown()).optional(),
218
+ repository: z.unknown().optional(),
219
+ rerun_url: z.string().optional(),
220
+ run_number: z.number().int().optional(),
221
+ run_started_at: z.iso.datetime().optional(),
222
+ triggering_actor: z.object({ login: z.string().min(1) }).optional(),
223
+ url: z.string().optional(),
224
+ workflow_id: z.number().int().optional(),
225
+ workflow_url: z.string().optional()
123
226
  })
124
227
  )
125
228
  });
@@ -129,9 +232,58 @@ var pullRequestsSchema = z.array(
129
232
  title: z.string(),
130
233
  state: z.string(),
131
234
  draft: z.boolean(),
132
- user: z.object({ login: z.string().min(1) }),
235
+ user: z.object({
236
+ login: z.string().min(1),
237
+ avatar_url: z.string().optional(),
238
+ events_url: z.string().optional(),
239
+ followers_url: z.string().optional(),
240
+ following_url: z.string().optional(),
241
+ gists_url: z.string().optional(),
242
+ gravatar_id: z.string().nullable().optional(),
243
+ html_url: z.string().optional(),
244
+ id: z.number().int().optional(),
245
+ node_id: z.string().optional(),
246
+ organizations_url: z.string().optional(),
247
+ received_events_url: z.string().optional(),
248
+ repos_url: z.string().optional(),
249
+ site_admin: z.boolean().optional(),
250
+ starred_url: z.string().optional(),
251
+ subscriptions_url: z.string().optional(),
252
+ type: z.string().optional(),
253
+ url: z.string().optional(),
254
+ user_view_type: z.string().optional()
255
+ }),
133
256
  created_at: z.iso.datetime(),
134
- updated_at: z.iso.datetime()
257
+ updated_at: z.iso.datetime(),
258
+ _links: z.unknown().optional(),
259
+ active_lock_reason: z.string().nullable().optional(),
260
+ assignee: z.unknown().optional(),
261
+ assignees: z.unknown().optional(),
262
+ author_association: z.string().optional(),
263
+ auto_merge: z.unknown().optional(),
264
+ base: z.unknown().optional(),
265
+ body: z.string().nullable().optional(),
266
+ closed_at: z.string().nullable().optional(),
267
+ comments_url: z.string().optional(),
268
+ commits_url: z.string().optional(),
269
+ diff_url: z.string().optional(),
270
+ head: z.unknown().optional(),
271
+ html_url: z.string().optional(),
272
+ id: z.number().int().optional(),
273
+ issue_url: z.string().optional(),
274
+ labels: z.unknown().optional(),
275
+ locked: z.boolean().optional(),
276
+ merge_commit_sha: z.string().nullable().optional(),
277
+ merged_at: z.string().nullable().optional(),
278
+ milestone: z.unknown().optional(),
279
+ node_id: z.string().optional(),
280
+ patch_url: z.string().optional(),
281
+ requested_reviewers: z.unknown().optional(),
282
+ requested_teams: z.unknown().optional(),
283
+ review_comment_url: z.string().optional(),
284
+ review_comments_url: z.string().optional(),
285
+ statuses_url: z.string().optional(),
286
+ url: z.string().optional()
135
287
  })
136
288
  );
137
289
  var reviewsSchema = z.array(
@@ -148,10 +300,34 @@ var issuesSchema = z.array(
148
300
  state: z.string(),
149
301
  labels: z.array(z.object({ name: z.string() })),
150
302
  assignees: z.array(z.object({ login: z.string().min(1) })),
151
- user: z.object({ login: z.string().min(1) }),
303
+ user: z.object({ login: z.string().min(1) }).catchall(z.unknown()),
152
304
  created_at: z.iso.datetime(),
153
305
  updated_at: z.iso.datetime(),
154
- closed_at: z.iso.datetime().nullable()
306
+ closed_at: z.iso.datetime().nullable(),
307
+ pull_request: z.unknown().optional(),
308
+ active_lock_reason: z.unknown().optional(),
309
+ assignee: z.unknown().optional(),
310
+ author_association: z.string().optional(),
311
+ body: z.string().nullable().optional(),
312
+ closed_by: z.unknown().optional(),
313
+ comments: z.number().int().optional(),
314
+ comments_url: z.string().optional(),
315
+ draft: z.boolean().optional(),
316
+ events_url: z.string().optional(),
317
+ html_url: z.string().optional(),
318
+ id: z.number().int().optional(),
319
+ issue_field_values: z.unknown().optional(),
320
+ labels_url: z.string().optional(),
321
+ locked: z.boolean().optional(),
322
+ milestone: z.unknown().optional(),
323
+ node_id: z.string().optional(),
324
+ performed_via_github_app: z.unknown().optional(),
325
+ reactions: z.unknown().optional(),
326
+ repository_url: z.string().optional(),
327
+ state_reason: z.unknown().optional(),
328
+ timeline_url: z.string().optional(),
329
+ type: z.unknown().optional(),
330
+ url: z.string().optional()
155
331
  })
156
332
  );
157
333
  var deploymentsSchema = z.array(
@@ -201,19 +377,62 @@ var repoStatsSchema = z.object({
201
377
  forks_count: z.number().int(),
202
378
  subscribers_count: z.number().int()
203
379
  });
380
+ var githubResources = defineResources({
381
+ repo: {
382
+ shape: "entity",
383
+ description: "Top-level repository stats (stars, forks, and watchers) as a single entity.",
384
+ endpoint: "GET /repos/{owner}/{repo}",
385
+ responses: { repo: repoStatsSchema }
386
+ },
387
+ workflow_run: {
388
+ shape: "event",
389
+ description: "GitHub Actions CI pipeline executions.",
390
+ endpoint: "GET /repos/{owner}/{repo}/actions/runs",
391
+ responses: { workflow_runs: workflowRunsResponseSchema }
392
+ },
393
+ pull_request: {
394
+ shape: "entity",
395
+ description: "Open and closed pull requests, including draft state, author, and review state.",
396
+ endpoint: "GET /repos/{owner}/{repo}/pulls",
397
+ notes: "Review state is folded in from GET /repos/{owner}/{repo}/pulls/{number}/reviews per PR.",
398
+ responses: {
399
+ pull_requests: pullRequestsSchema,
400
+ pull_request_reviews: reviewsSchema
401
+ }
402
+ },
403
+ issue: {
404
+ shape: "entity",
405
+ description: "Open and closed issues with labels, assignees, and author (pull requests excluded).",
406
+ endpoint: "GET /repos/{owner}/{repo}/issues",
407
+ responses: { issues: issuesSchema }
408
+ },
409
+ deployment: {
410
+ shape: "entity",
411
+ description: "Deployments with their latest status, keyed by environment and ref.",
412
+ endpoint: "GET /repos/{owner}/{repo}/deployments",
413
+ notes: "The latest status is folded in from GET /repos/{owner}/{repo}/deployments/{id}/statuses.",
414
+ responses: {
415
+ deployments: deploymentsSchema,
416
+ deployment_statuses: deploymentStatusesSchema
417
+ }
418
+ },
419
+ release: {
420
+ shape: "entity",
421
+ description: "Published, draft, and prerelease GitHub releases.",
422
+ endpoint: "GET /repos/{owner}/{repo}/releases",
423
+ responses: { releases: releasesSchema }
424
+ },
425
+ contributor: {
426
+ shape: "entity",
427
+ description: "Per-author commit activity (commits, additions, deletions) for the repository.",
428
+ endpoint: "GET /repos/{owner}/{repo}/stats/contributors",
429
+ responses: { contributors: contributorsSchema }
430
+ }
431
+ });
204
432
  var GitHubConnector = class _GitHubConnector extends BaseConnector {
205
433
  static id = "github-actions";
206
- static schemas = {
207
- repo: repoStatsSchema,
208
- workflow_runs: workflowRunsResponseSchema,
209
- pull_requests: pullRequestsSchema,
210
- pull_request_reviews: reviewsSchema,
211
- issues: issuesSchema,
212
- deployments: deploymentsSchema,
213
- deployment_statuses: deploymentStatusesSchema,
214
- releases: releasesSchema,
215
- contributors: contributorsSchema
216
- };
434
+ static resources = githubResources;
435
+ static schemas = schemasFromResources(githubResources);
217
436
  static create(input, ctx) {
218
437
  const parsed = configFields.parse(input);
219
438
  return new _GitHubConnector(
@@ -225,11 +444,12 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
225
444
  id = "github-actions";
226
445
  credentials = githubCredentials;
227
446
  seenWorkflowRunIds = /* @__PURE__ */ new Set();
447
+ preservedDeploymentStatus = /* @__PURE__ */ new Map();
228
448
  buildHeaders() {
229
449
  const headers = {
230
450
  Accept: "application/vnd.github+json",
231
451
  "X-GitHub-Api-Version": "2022-11-28",
232
- "User-Agent": "rawdash/connector-github (+https://rawdash.dev)"
452
+ "User-Agent": connectorUserAgent("github")
233
453
  };
234
454
  if (this.creds.token) {
235
455
  headers["Authorization"] = `Bearer ${this.creds.token}`;
@@ -263,22 +483,21 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
263
483
  }
264
484
  }
265
485
  sanitizePageUrl(phase, pageUrl) {
266
- if (pageUrl === null) {
267
- return null;
268
- }
269
486
  const allowedPath = this.allowedPageBasePath(phase);
270
487
  if (allowedPath === null) {
271
488
  return null;
272
489
  }
273
- try {
274
- const u = new URL(pageUrl);
275
- if (u.protocol !== "https:" || u.host !== "api.github.com" || u.pathname !== allowedPath) {
276
- return null;
277
- }
278
- return u.toString();
279
- } catch {
280
- return null;
490
+ return sanitizeAllowedUrl({
491
+ url: pageUrl,
492
+ host: "api.github.com",
493
+ pathname: allowedPath
494
+ });
495
+ }
496
+ isResourceAllowed(options, resource) {
497
+ if (!options.resources) {
498
+ return true;
281
499
  }
500
+ return options.resources.has(resource);
282
501
  }
283
502
  resolveCursor(cursor) {
284
503
  if (!isGitHubSyncCursor(cursor)) {
@@ -334,24 +553,30 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
334
553
  next: cutoffReached ? null : nextLink
335
554
  };
336
555
  }
337
- async fetchPullRequests(page, signal) {
556
+ async fetchPullRequests(options, page, signal) {
338
557
  const { owner, repo } = this.settings;
339
- const url = page ?? `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&per_page=100`;
558
+ const url = page ?? `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&sort=updated&direction=desc&per_page=100`;
340
559
  const res = await this.fetch(url, "pull_requests", signal);
341
560
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
342
561
  const prs = res.body;
562
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
563
+ const filteredPrs = cutoff !== null ? prs.filter((pr) => new Date(pr.updated_at).getTime() >= cutoff) : prs;
564
+ const lastPr = prs.at(-1);
565
+ const cutoffReached = cutoff !== null && lastPr !== void 0 && new Date(lastPr.updated_at).getTime() < cutoff;
343
566
  const reviewsByPR = /* @__PURE__ */ new Map();
344
- for (const pr of prs) {
345
- signal?.throwIfAborted();
346
- const reviews = await this.fetch(
347
- `https://api.github.com/repos/${owner}/${repo}/pulls/${pr.number}/reviews`,
348
- "pull_request_reviews",
349
- signal
350
- );
351
- reviewsByPR.set(pr.number, reviews.body);
567
+ if (this.isResourceAllowed(options, "pull_request_reviews")) {
568
+ for (const pr of filteredPrs) {
569
+ signal?.throwIfAborted();
570
+ const reviews = await this.fetch(
571
+ `https://api.github.com/repos/${owner}/${repo}/pulls/${pr.number}/reviews`,
572
+ "pull_request_reviews",
573
+ signal
574
+ );
575
+ reviewsByPR.set(pr.number, reviews.body);
576
+ }
352
577
  }
353
- const items = [{ prs, reviewsByPR }];
354
- return { items, next: nextLink };
578
+ const items = [{ prs: filteredPrs, reviewsByPR }];
579
+ return { items, next: cutoffReached ? null : nextLink };
355
580
  }
356
581
  async fetchIssues(options, page, signal) {
357
582
  const { owner, repo } = this.settings;
@@ -371,7 +596,7 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
371
596
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
372
597
  return { items: res.body, next: nextLink };
373
598
  }
374
- async fetchDeployments(page, signal) {
599
+ async fetchDeployments(options, page, signal) {
375
600
  const { owner, repo } = this.settings;
376
601
  const url = page ?? `https://api.github.com/repos/${owner}/${repo}/deployments?per_page=100`;
377
602
  const res = await this.fetch(
@@ -381,25 +606,41 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
381
606
  );
382
607
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
383
608
  const deployments = res.body;
609
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
610
+ const filteredDeployments = cutoff !== null ? deployments.filter((d) => new Date(d.created_at).getTime() >= cutoff) : deployments;
611
+ const lastDeployment = deployments.at(-1);
612
+ const cutoffReached = cutoff !== null && lastDeployment !== void 0 && new Date(lastDeployment.created_at).getTime() < cutoff;
384
613
  const latestStatusById = /* @__PURE__ */ new Map();
385
- for (const deployment of deployments) {
386
- signal?.throwIfAborted();
387
- const statusRes = await this.fetch(
388
- `https://api.github.com/repos/${owner}/${repo}/deployments/${deployment.id}/statuses?per_page=1`,
389
- "deployment_statuses",
390
- signal
391
- );
392
- latestStatusById.set(deployment.id, statusRes.body[0] ?? null);
614
+ if (this.isResourceAllowed(options, "deployment_statuses")) {
615
+ for (const deployment of filteredDeployments) {
616
+ signal?.throwIfAborted();
617
+ const statusRes = await this.fetch(
618
+ `https://api.github.com/repos/${owner}/${repo}/deployments/${deployment.id}/statuses?per_page=1`,
619
+ "deployment_statuses",
620
+ signal
621
+ );
622
+ latestStatusById.set(deployment.id, statusRes.body[0] ?? null);
623
+ }
393
624
  }
394
- const items = [{ deployments, latestStatusById }];
395
- return { items, next: nextLink };
625
+ const items = [
626
+ { deployments: filteredDeployments, latestStatusById }
627
+ ];
628
+ return { items, next: cutoffReached ? null : nextLink };
396
629
  }
397
- async fetchReleases(page, signal) {
630
+ async fetchReleases(options, page, signal) {
398
631
  const { owner, repo } = this.settings;
399
632
  const url = page ?? `https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`;
400
633
  const res = await this.fetch(url, "releases", signal);
401
634
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
402
- return { items: res.body, next: nextLink };
635
+ const releases = res.body;
636
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
637
+ const filtered = cutoff !== null ? releases.filter((r) => {
638
+ const ts = new Date(r.published_at ?? r.created_at).getTime();
639
+ return ts >= cutoff;
640
+ }) : releases;
641
+ const lastRelease = releases.at(-1);
642
+ const cutoffReached = cutoff !== null && lastRelease !== void 0 && new Date(lastRelease.published_at ?? lastRelease.created_at).getTime() < cutoff;
643
+ return { items: filtered, next: cutoffReached ? null : nextLink };
403
644
  }
404
645
  async fetchContributors(signal) {
405
646
  const { owner, repo } = this.settings;
@@ -513,10 +754,16 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
513
754
  });
514
755
  }
515
756
  }
516
- async writePullRequests(storage, items, page) {
757
+ async writePullRequests(storage, items, page, options) {
758
+ const reviewsAllowed = this.isResourceAllowed(
759
+ options,
760
+ "pull_request_reviews"
761
+ );
517
762
  if (page === null) {
518
763
  await storage.entities([], { types: ["pull_request"] });
519
- await storage.edges([], { kinds: ["reviewed_by"] });
764
+ if (reviewsAllowed) {
765
+ await storage.edges([], { kinds: ["reviewed_by"] });
766
+ }
520
767
  }
521
768
  const pageItems = items;
522
769
  for (const { prs: rawPrs, reviewsByPR } of pageItems) {
@@ -539,6 +786,9 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
539
786
  updated_at: new Date(pr.updated_at).getTime()
540
787
  });
541
788
  }
789
+ if (!reviewsAllowed) {
790
+ continue;
791
+ }
542
792
  for (const pr of prs) {
543
793
  const reviews = reviewsByPR.get(pr.number) ?? [];
544
794
  for (const review of reviews) {
@@ -586,8 +836,21 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
586
836
  });
587
837
  }
588
838
  }
589
- async writeDeployments(storage, items, page) {
839
+ async writeDeployments(storage, items, page, options) {
840
+ const statusesAllowed = this.isResourceAllowed(
841
+ options,
842
+ "deployment_statuses"
843
+ );
590
844
  if (page === null) {
845
+ if (!statusesAllowed) {
846
+ const existing = await storage.queryEntities({ type: "deployment" });
847
+ for (const entity of existing) {
848
+ const prev = entity.attributes["latest_status"];
849
+ if (typeof prev === "string") {
850
+ this.preservedDeploymentStatus.set(entity.id, prev);
851
+ }
852
+ }
853
+ }
591
854
  await storage.entities([], { types: ["deployment"] });
592
855
  }
593
856
  const pageItems = items;
@@ -598,9 +861,16 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
598
861
  "deployments"
599
862
  );
600
863
  for (const deployment of deployments) {
601
- const status = latestStatusById.get(deployment.id) ?? null;
602
864
  const createdMs = new Date(deployment.created_at).getTime();
603
- const statusUpdatedMs = status?.updated_at ? new Date(status.updated_at).getTime() : null;
865
+ let latestStatus;
866
+ let statusUpdatedMs = null;
867
+ if (statusesAllowed) {
868
+ const status = latestStatusById.get(deployment.id) ?? null;
869
+ latestStatus = status?.state ?? "unknown";
870
+ statusUpdatedMs = status?.updated_at ? new Date(status.updated_at).getTime() : null;
871
+ } else {
872
+ latestStatus = this.preservedDeploymentStatus.get(String(deployment.id)) ?? "unknown";
873
+ }
604
874
  await storage.entity({
605
875
  type: "deployment",
606
876
  id: String(deployment.id),
@@ -610,7 +880,7 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
610
880
  sha: deployment.sha,
611
881
  creator: deployment.creator?.login ?? "",
612
882
  created_at: createdMs,
613
- latest_status: status?.state ?? "unknown"
883
+ latest_status: latestStatus
614
884
  },
615
885
  updated_at: Math.max(createdMs, statusUpdatedMs ?? 0)
616
886
  });
@@ -676,10 +946,12 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
676
946
  }
677
947
  async sync(options, storage, signal) {
678
948
  const cursor = this.resolveCursor(options.cursor);
949
+ const phases = selectPhases(options.resources);
679
950
  return paginateChunked({
680
- phases: PHASE_ORDER,
951
+ phases,
681
952
  cursor,
682
953
  signal,
954
+ logger: this.logger,
683
955
  fetchPage: async (phase, page, sig) => {
684
956
  switch (phase) {
685
957
  case "repo_stats":
@@ -687,13 +959,13 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
687
959
  case "workflow_runs":
688
960
  return options.mode === "latest" ? this.fetchWorkflowRunsLatest(sig) : this.fetchWorkflowRunsFull(options, page, sig);
689
961
  case "pull_requests":
690
- return this.fetchPullRequests(page, sig);
962
+ return this.fetchPullRequests(options, page, sig);
691
963
  case "issues":
692
964
  return this.fetchIssues(options, page, sig);
693
965
  case "deployments":
694
- return this.fetchDeployments(page, sig);
966
+ return this.fetchDeployments(options, page, sig);
695
967
  case "releases":
696
- return this.fetchReleases(page, sig);
968
+ return this.fetchReleases(options, page, sig);
697
969
  case "contributors":
698
970
  return this.fetchContributors(sig);
699
971
  }
@@ -705,11 +977,11 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
705
977
  case "workflow_runs":
706
978
  return options.mode === "latest" ? this.writeWorkflowRunsLatest(storage, items) : this.writeWorkflowRunsFull(storage, items, page);
707
979
  case "pull_requests":
708
- return this.writePullRequests(storage, items, page);
980
+ return this.writePullRequests(storage, items, page, options);
709
981
  case "issues":
710
982
  return this.writeIssues(storage, items, page);
711
983
  case "deployments":
712
- return this.writeDeployments(storage, items, page);
984
+ return this.writeDeployments(storage, items, page, options);
713
985
  case "releases":
714
986
  return this.writeReleases(storage, items, page);
715
987
  case "contributors":
@@ -725,6 +997,7 @@ var index_default = GitHubConnector;
725
997
  export {
726
998
  GitHubConnector,
727
999
  configFields,
728
- index_default as default
1000
+ index_default as default,
1001
+ doc
729
1002
  };
730
1003
  //# sourceMappingURL=index.js.map