@rawdash/connector-github 0.15.0 → 0.17.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,63 @@ 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
+ });
432
+ var id = "github-actions";
204
433
  var GitHubConnector = class _GitHubConnector extends BaseConnector {
205
- 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 id = id;
435
+ static resources = githubResources;
436
+ static schemas = schemasFromResources(githubResources);
217
437
  static create(input, ctx) {
218
438
  const parsed = configFields.parse(input);
219
439
  return new _GitHubConnector(
@@ -222,14 +442,15 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
222
442
  ctx
223
443
  );
224
444
  }
225
- id = "github-actions";
445
+ id = id;
226
446
  credentials = githubCredentials;
227
447
  seenWorkflowRunIds = /* @__PURE__ */ new Set();
448
+ preservedDeploymentStatus = /* @__PURE__ */ new Map();
228
449
  buildHeaders() {
229
450
  const headers = {
230
451
  Accept: "application/vnd.github+json",
231
452
  "X-GitHub-Api-Version": "2022-11-28",
232
- "User-Agent": "rawdash/connector-github (+https://rawdash.dev)"
453
+ "User-Agent": connectorUserAgent("github")
233
454
  };
234
455
  if (this.creds.token) {
235
456
  headers["Authorization"] = `Bearer ${this.creds.token}`;
@@ -263,22 +484,21 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
263
484
  }
264
485
  }
265
486
  sanitizePageUrl(phase, pageUrl) {
266
- if (pageUrl === null) {
267
- return null;
268
- }
269
487
  const allowedPath = this.allowedPageBasePath(phase);
270
488
  if (allowedPath === null) {
271
489
  return null;
272
490
  }
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;
491
+ return sanitizeAllowedUrl({
492
+ url: pageUrl,
493
+ host: "api.github.com",
494
+ pathname: allowedPath
495
+ });
496
+ }
497
+ isResourceAllowed(options, resource) {
498
+ if (!options.resources) {
499
+ return true;
281
500
  }
501
+ return options.resources.has(resource);
282
502
  }
283
503
  resolveCursor(cursor) {
284
504
  if (!isGitHubSyncCursor(cursor)) {
@@ -334,24 +554,30 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
334
554
  next: cutoffReached ? null : nextLink
335
555
  };
336
556
  }
337
- async fetchPullRequests(page, signal) {
557
+ async fetchPullRequests(options, page, signal) {
338
558
  const { owner, repo } = this.settings;
339
- const url = page ?? `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&per_page=100`;
559
+ const url = page ?? `https://api.github.com/repos/${owner}/${repo}/pulls?state=all&sort=updated&direction=desc&per_page=100`;
340
560
  const res = await this.fetch(url, "pull_requests", signal);
341
561
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
342
562
  const prs = res.body;
563
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
564
+ const filteredPrs = cutoff !== null ? prs.filter((pr) => new Date(pr.updated_at).getTime() >= cutoff) : prs;
565
+ const lastPr = prs.at(-1);
566
+ const cutoffReached = cutoff !== null && lastPr !== void 0 && new Date(lastPr.updated_at).getTime() < cutoff;
343
567
  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);
568
+ if (this.isResourceAllowed(options, "pull_request_reviews")) {
569
+ for (const pr of filteredPrs) {
570
+ signal?.throwIfAborted();
571
+ const reviews = await this.fetch(
572
+ `https://api.github.com/repos/${owner}/${repo}/pulls/${pr.number}/reviews`,
573
+ "pull_request_reviews",
574
+ signal
575
+ );
576
+ reviewsByPR.set(pr.number, reviews.body);
577
+ }
352
578
  }
353
- const items = [{ prs, reviewsByPR }];
354
- return { items, next: nextLink };
579
+ const items = [{ prs: filteredPrs, reviewsByPR }];
580
+ return { items, next: cutoffReached ? null : nextLink };
355
581
  }
356
582
  async fetchIssues(options, page, signal) {
357
583
  const { owner, repo } = this.settings;
@@ -371,7 +597,7 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
371
597
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
372
598
  return { items: res.body, next: nextLink };
373
599
  }
374
- async fetchDeployments(page, signal) {
600
+ async fetchDeployments(options, page, signal) {
375
601
  const { owner, repo } = this.settings;
376
602
  const url = page ?? `https://api.github.com/repos/${owner}/${repo}/deployments?per_page=100`;
377
603
  const res = await this.fetch(
@@ -381,25 +607,41 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
381
607
  );
382
608
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
383
609
  const deployments = res.body;
610
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
611
+ const filteredDeployments = cutoff !== null ? deployments.filter((d) => new Date(d.created_at).getTime() >= cutoff) : deployments;
612
+ const lastDeployment = deployments.at(-1);
613
+ const cutoffReached = cutoff !== null && lastDeployment !== void 0 && new Date(lastDeployment.created_at).getTime() < cutoff;
384
614
  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);
615
+ if (this.isResourceAllowed(options, "deployment_statuses")) {
616
+ for (const deployment of filteredDeployments) {
617
+ signal?.throwIfAborted();
618
+ const statusRes = await this.fetch(
619
+ `https://api.github.com/repos/${owner}/${repo}/deployments/${deployment.id}/statuses?per_page=1`,
620
+ "deployment_statuses",
621
+ signal
622
+ );
623
+ latestStatusById.set(deployment.id, statusRes.body[0] ?? null);
624
+ }
393
625
  }
394
- const items = [{ deployments, latestStatusById }];
395
- return { items, next: nextLink };
626
+ const items = [
627
+ { deployments: filteredDeployments, latestStatusById }
628
+ ];
629
+ return { items, next: cutoffReached ? null : nextLink };
396
630
  }
397
- async fetchReleases(page, signal) {
631
+ async fetchReleases(options, page, signal) {
398
632
  const { owner, repo } = this.settings;
399
633
  const url = page ?? `https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`;
400
634
  const res = await this.fetch(url, "releases", signal);
401
635
  const nextLink = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
402
- return { items: res.body, next: nextLink };
636
+ const releases = res.body;
637
+ const cutoff = options.since ? new Date(options.since).getTime() : null;
638
+ const filtered = cutoff !== null ? releases.filter((r) => {
639
+ const ts = new Date(r.published_at ?? r.created_at).getTime();
640
+ return ts >= cutoff;
641
+ }) : releases;
642
+ const lastRelease = releases.at(-1);
643
+ const cutoffReached = cutoff !== null && lastRelease !== void 0 && new Date(lastRelease.published_at ?? lastRelease.created_at).getTime() < cutoff;
644
+ return { items: filtered, next: cutoffReached ? null : nextLink };
403
645
  }
404
646
  async fetchContributors(signal) {
405
647
  const { owner, repo } = this.settings;
@@ -513,10 +755,16 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
513
755
  });
514
756
  }
515
757
  }
516
- async writePullRequests(storage, items, page) {
758
+ async writePullRequests(storage, items, page, options) {
759
+ const reviewsAllowed = this.isResourceAllowed(
760
+ options,
761
+ "pull_request_reviews"
762
+ );
517
763
  if (page === null) {
518
764
  await storage.entities([], { types: ["pull_request"] });
519
- await storage.edges([], { kinds: ["reviewed_by"] });
765
+ if (reviewsAllowed) {
766
+ await storage.edges([], { kinds: ["reviewed_by"] });
767
+ }
520
768
  }
521
769
  const pageItems = items;
522
770
  for (const { prs: rawPrs, reviewsByPR } of pageItems) {
@@ -539,6 +787,9 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
539
787
  updated_at: new Date(pr.updated_at).getTime()
540
788
  });
541
789
  }
790
+ if (!reviewsAllowed) {
791
+ continue;
792
+ }
542
793
  for (const pr of prs) {
543
794
  const reviews = reviewsByPR.get(pr.number) ?? [];
544
795
  for (const review of reviews) {
@@ -586,8 +837,21 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
586
837
  });
587
838
  }
588
839
  }
589
- async writeDeployments(storage, items, page) {
840
+ async writeDeployments(storage, items, page, options) {
841
+ const statusesAllowed = this.isResourceAllowed(
842
+ options,
843
+ "deployment_statuses"
844
+ );
590
845
  if (page === null) {
846
+ if (!statusesAllowed) {
847
+ const existing = await storage.queryEntities({ type: "deployment" });
848
+ for (const entity of existing) {
849
+ const prev = entity.attributes["latest_status"];
850
+ if (typeof prev === "string") {
851
+ this.preservedDeploymentStatus.set(entity.id, prev);
852
+ }
853
+ }
854
+ }
591
855
  await storage.entities([], { types: ["deployment"] });
592
856
  }
593
857
  const pageItems = items;
@@ -598,9 +862,16 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
598
862
  "deployments"
599
863
  );
600
864
  for (const deployment of deployments) {
601
- const status = latestStatusById.get(deployment.id) ?? null;
602
865
  const createdMs = new Date(deployment.created_at).getTime();
603
- const statusUpdatedMs = status?.updated_at ? new Date(status.updated_at).getTime() : null;
866
+ let latestStatus;
867
+ let statusUpdatedMs = null;
868
+ if (statusesAllowed) {
869
+ const status = latestStatusById.get(deployment.id) ?? null;
870
+ latestStatus = status?.state ?? "unknown";
871
+ statusUpdatedMs = status?.updated_at ? new Date(status.updated_at).getTime() : null;
872
+ } else {
873
+ latestStatus = this.preservedDeploymentStatus.get(String(deployment.id)) ?? "unknown";
874
+ }
604
875
  await storage.entity({
605
876
  type: "deployment",
606
877
  id: String(deployment.id),
@@ -610,7 +881,7 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
610
881
  sha: deployment.sha,
611
882
  creator: deployment.creator?.login ?? "",
612
883
  created_at: createdMs,
613
- latest_status: status?.state ?? "unknown"
884
+ latest_status: latestStatus
614
885
  },
615
886
  updated_at: Math.max(createdMs, statusUpdatedMs ?? 0)
616
887
  });
@@ -676,10 +947,12 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
676
947
  }
677
948
  async sync(options, storage, signal) {
678
949
  const cursor = this.resolveCursor(options.cursor);
950
+ const phases = selectPhases(options.resources);
679
951
  return paginateChunked({
680
- phases: PHASE_ORDER,
952
+ phases,
681
953
  cursor,
682
954
  signal,
955
+ logger: this.logger,
683
956
  fetchPage: async (phase, page, sig) => {
684
957
  switch (phase) {
685
958
  case "repo_stats":
@@ -687,13 +960,13 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
687
960
  case "workflow_runs":
688
961
  return options.mode === "latest" ? this.fetchWorkflowRunsLatest(sig) : this.fetchWorkflowRunsFull(options, page, sig);
689
962
  case "pull_requests":
690
- return this.fetchPullRequests(page, sig);
963
+ return this.fetchPullRequests(options, page, sig);
691
964
  case "issues":
692
965
  return this.fetchIssues(options, page, sig);
693
966
  case "deployments":
694
- return this.fetchDeployments(page, sig);
967
+ return this.fetchDeployments(options, page, sig);
695
968
  case "releases":
696
- return this.fetchReleases(page, sig);
969
+ return this.fetchReleases(options, page, sig);
697
970
  case "contributors":
698
971
  return this.fetchContributors(sig);
699
972
  }
@@ -705,11 +978,11 @@ var GitHubConnector = class _GitHubConnector extends BaseConnector {
705
978
  case "workflow_runs":
706
979
  return options.mode === "latest" ? this.writeWorkflowRunsLatest(storage, items) : this.writeWorkflowRunsFull(storage, items, page);
707
980
  case "pull_requests":
708
- return this.writePullRequests(storage, items, page);
981
+ return this.writePullRequests(storage, items, page, options);
709
982
  case "issues":
710
983
  return this.writeIssues(storage, items, page);
711
984
  case "deployments":
712
- return this.writeDeployments(storage, items, page);
985
+ return this.writeDeployments(storage, items, page, options);
713
986
  case "releases":
714
987
  return this.writeReleases(storage, items, page);
715
988
  case "contributors":
@@ -725,6 +998,9 @@ var index_default = GitHubConnector;
725
998
  export {
726
999
  GitHubConnector,
727
1000
  configFields,
728
- index_default as default
1001
+ index_default as default,
1002
+ doc,
1003
+ id,
1004
+ githubResources as resources
729
1005
  };
730
1006
  //# sourceMappingURL=index.js.map