comfy-pr 0.2.26 → 1.0.1

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 (100) hide show
  1. package/README.md +258 -157
  2. package/bot/IdleWaiter.ts +31 -0
  3. package/bot/RestartManager.spec.ts +163 -0
  4. package/bot/RestartManager.ts +190 -0
  5. package/bot/WorkingTasksManager.spec.ts +154 -0
  6. package/bot/cli.ts +1171 -0
  7. package/bot/codesearch-ai-wip.ts +351 -0
  8. package/bot/error-collector.ts +133 -0
  9. package/bot/index.ts +14 -0
  10. package/bot/restart-example.ts +99 -0
  11. package/bot/slack-bolt.ts +688 -0
  12. package/bot/slack-bot.ts +1575 -0
  13. package/bot/state.ts +19 -0
  14. package/bot/templateLoader.test.ts +84 -0
  15. package/bot/templateLoader.ts +92 -0
  16. package/next.config.ts +26 -0
  17. package/package.json +161 -41
  18. package/post-summary.ts +44 -0
  19. package/src/Authors.ts +2 -2
  20. package/src/CMNodes.ts +3 -3
  21. package/src/CNRepos.ts +30 -7
  22. package/src/CRNodes.ts +11 -9
  23. package/src/CRPulls.ts +3 -0
  24. package/src/EmailTasks.ts +27 -9
  25. package/src/FORK_OWNER.ts +3 -1
  26. package/src/FollowRules.ts +3 -3
  27. package/src/GithubIssueComments.ts +1 -1
  28. package/src/Totals.ts +9 -8
  29. package/src/WorkerInstances.ts +31 -8
  30. package/src/addCommentAction.ts +19 -11
  31. package/src/analyzePullsStatus.ts +39 -17
  32. package/src/analyzeTotals.ts +19 -11
  33. package/src/bypassRepos.ts +6 -5
  34. package/src/checkPRsFailures.ts +13 -8
  35. package/src/cli.test.ts +2 -0
  36. package/src/cli.ts +1 -1
  37. package/src/constants.ts +2 -0
  38. package/src/createComfyRegistryPRsFromCandidates.ts +20 -12
  39. package/src/createComfyRegistryPullRequests.ts +35 -10
  40. package/src/createGithubForkForRepo.ts +36 -11
  41. package/src/createGithubPullRequest.ts +83 -33
  42. package/src/createIssueComment.ts +4 -4
  43. package/src/fetchComfyRegistryNodes.ts +34 -4
  44. package/src/fetchCurrentGeoInfo.ts +3 -1
  45. package/src/fetchRelatedPullWithComments.ts +1 -1
  46. package/src/fetchRepoDescriptionMap.ts +1 -4
  47. package/src/followRuleSchema.ts +10 -4
  48. package/src/getBranchWorkingDir.ts +3 -7
  49. package/src/getRepoWorkingDir.ts +2 -3
  50. package/src/gh.spec.ts +571 -0
  51. package/src/ghData.ts +13 -0
  52. package/src/ghPageFlow.ts +33 -0
  53. package/src/ghSubscriber.ts +76 -0
  54. package/src/ghUser.ts +28 -4
  55. package/src/index.ts +13 -4
  56. package/src/initializeFollowRules.ts +11 -4
  57. package/src/logger.spec.ts +95 -0
  58. package/src/logger.ts +46 -0
  59. package/src/makeGpl3LicenseBranch.ts +66 -0
  60. package/src/makePublishBranch.ts +21 -15
  61. package/src/makeTomlBranch.ts +48 -14
  62. package/src/makeUpdateTomlLicenseBranch.ts +40 -44
  63. package/src/matchRelatedPulls.ts +9 -4
  64. package/src/normalizeGithubUrl.spec.ts +131 -0
  65. package/src/normalizeGithubUrl.ts +64 -0
  66. package/src/parseIssueUrl.spec.ts +95 -0
  67. package/src/parseIssueUrl.ts +17 -3
  68. package/src/parseOwnerRepo.spec.ts +147 -0
  69. package/src/parseOwnerRepo.ts +8 -5
  70. package/src/parsePullsState.ts +2 -2
  71. package/src/parseTitleBodyOfMarkdown.ts +1 -1
  72. package/src/pickRepoInfo.ts +37 -0
  73. package/src/postSlackMessage.ts +5 -1
  74. package/src/preload.ts +30 -27
  75. package/src/readTemplateTitle.ts +1 -3
  76. package/src/sendEmailAction.ts +14 -10
  77. package/src/sendGmail.ts +3 -3
  78. package/src/updateAuthorsForGithub.ts +53 -35
  79. package/src/updateAuthorsFromCNRepo.ts +12 -5
  80. package/src/updateCMNodesDuplicationWarnings.ts +14 -9
  81. package/src/updateCMRepos.ts +1 -1
  82. package/src/updateCNRepos.ts +14 -14
  83. package/src/updateCNReposCRPullsComments.ts +7 -5
  84. package/src/updateCNReposInfo.ts +49 -40
  85. package/src/updateCNReposPRCandidate.ts +3 -3
  86. package/src/updateCNReposPulls.ts +9 -5
  87. package/src/updateCNReposPullsDashboard.ts +11 -9
  88. package/src/updateCNReposRelatedPulls.ts +3 -3
  89. package/src/updateCRNodes.ts +24 -5
  90. package/src/updateCRRepos.ts +1 -1
  91. package/src/updateComfyTotals.ts +7 -5
  92. package/src/updateFollowRuleSet.ts +17 -9
  93. package/src/updateOutdatedPullsTemplates.ts +61 -48
  94. package/src/updateSlackMessages.ts +8 -4
  95. package/tailwind.config.ts +7 -1
  96. package/next-env.d.ts +0 -5
  97. package/src/GIT_USEREMAIL.ts +0 -5
  98. package/src/GIT_USERNAME.ts +0 -9
  99. package/src/clone_modify_push_Branches.ts +0 -21
  100. package/src/tomlFillDescription.ts +0 -17
package/src/gh.spec.ts ADDED
@@ -0,0 +1,571 @@
1
+ import { http, HttpResponse } from "msw";
2
+ import { server } from "./test/msw-setup";
3
+
4
+ // Import the GitHub client
5
+ // Note: We need to mock the environment variable before importing
6
+ process.env.GH_TOKEN = "test-token-for-gh-spec";
7
+
8
+ // Dynamic import to ensure env var is set
9
+ const ghModule = await import("../lib/github/githubCached");
10
+ const { gh } = await import("../lib/github/githubCached");
11
+ const { ghc, clearGhCache } = ghModule;
12
+
13
+ describe("GitHub API Client (gh)", () => {
14
+ beforeEach(async () => {
15
+ // Clear cache before each test to ensure clean state
16
+ await clearGhCache();
17
+ });
18
+
19
+ describe("Repos API", () => {
20
+ it("should get a repository", async () => {
21
+ const result = await gh.repos.get({
22
+ owner: "octocat",
23
+ repo: "Hello-World",
24
+ });
25
+
26
+ expect(result.data).toBeDefined();
27
+ expect(result.data.name).toBe("Hello-World");
28
+ expect(result.data.owner.login).toBe("octocat");
29
+ expect(result.data.default_branch).toBe("main");
30
+ });
31
+
32
+ it("should create a fork", async () => {
33
+ const result = await gh.repos.createFork({
34
+ owner: "octocat",
35
+ repo: "Hello-World",
36
+ organization: "test-org",
37
+ name: "Hello-World-fork",
38
+ });
39
+
40
+ expect(result.data).toBeDefined();
41
+ expect(result.data.fork).toBe(true);
42
+ expect(result.data.owner.login).toBe("test-org");
43
+ expect(result.data.name).toBe("Hello-World-fork");
44
+ });
45
+
46
+ it("should get a branch", async () => {
47
+ const result = await gh.repos.getBranch({
48
+ owner: "octocat",
49
+ repo: "Hello-World",
50
+ branch: "main",
51
+ });
52
+
53
+ expect(result.data).toBeDefined();
54
+ expect(result.data.name).toBe("main");
55
+ expect(result.data.commit.sha).toBeDefined();
56
+ });
57
+
58
+ it("should list tags", async () => {
59
+ const result = await gh.repos.listTags({
60
+ owner: "comfyanonymous",
61
+ repo: "ComfyUI",
62
+ per_page: 10,
63
+ });
64
+
65
+ expect(result.data).toBeDefined();
66
+ expect(Array.isArray(result.data)).toBe(true);
67
+ expect(result.data.length).toBeGreaterThan(0);
68
+ expect(result.data[0].name).toBeDefined();
69
+ expect(result.data[0].commit.sha).toBeDefined();
70
+ });
71
+
72
+ it("should get a commit", async () => {
73
+ const result = await gh.repos.getCommit({
74
+ owner: "octocat",
75
+ repo: "Hello-World",
76
+ ref: "abc123def456",
77
+ });
78
+
79
+ expect(result.data).toBeDefined();
80
+ expect(result.data.commit.author!.date).toBeDefined();
81
+ expect(result.data.commit.message).toBeDefined();
82
+ });
83
+
84
+ it("should list releases", async () => {
85
+ const result = await gh.repos.listReleases({
86
+ owner: "Comfy-Org",
87
+ repo: "desktop",
88
+ per_page: 3,
89
+ });
90
+
91
+ expect(result.data).toBeDefined();
92
+ expect(Array.isArray(result.data)).toBe(true);
93
+ expect(result.data.length).toBeGreaterThan(0);
94
+ expect(result.data[0].tag_name).toBeDefined();
95
+ });
96
+
97
+ it("should list webhooks", async () => {
98
+ const result = await gh.repos.listWebhooks({
99
+ owner: "octocat",
100
+ repo: "Hello-World",
101
+ });
102
+
103
+ expect(result.data).toBeDefined();
104
+ expect(Array.isArray(result.data)).toBe(true);
105
+ });
106
+
107
+ it("should create a webhook", async () => {
108
+ const result = await gh.repos.createWebhook({
109
+ owner: "octocat",
110
+ repo: "Hello-World",
111
+ config: {
112
+ url: "https://example.com/webhook",
113
+ content_type: "json",
114
+ },
115
+ events: ["push", "pull_request"],
116
+ });
117
+
118
+ expect(result.data).toBeDefined();
119
+ expect(result.data.config.url).toBe("https://example.com/webhook");
120
+ expect(result.data.events).toContain("push");
121
+ });
122
+ });
123
+
124
+ describe("Pulls API", () => {
125
+ it("should list pull requests", async () => {
126
+ const result = await gh.pulls.list({
127
+ owner: "octocat",
128
+ repo: "Hello-World",
129
+ state: "all",
130
+ });
131
+
132
+ expect(result.data).toBeDefined();
133
+ expect(Array.isArray(result.data)).toBe(true);
134
+ expect(result.data.length).toBeGreaterThan(0);
135
+ });
136
+
137
+ it("should filter pull requests by state", async () => {
138
+ const result = await gh.pulls.list({
139
+ owner: "octocat",
140
+ repo: "Hello-World",
141
+ state: "open",
142
+ });
143
+
144
+ expect(result.data).toBeDefined();
145
+ expect(result.data.every((pr) => pr.state === "open")).toBe(true);
146
+ });
147
+
148
+ it("should get a specific pull request", async () => {
149
+ const result = await gh.pulls.get({
150
+ owner: "octocat",
151
+ repo: "Hello-World",
152
+ pull_number: 101,
153
+ });
154
+
155
+ expect(result.data).toBeDefined();
156
+ expect(result.data.number).toBe(101);
157
+ expect(result.data.title).toBeDefined();
158
+ });
159
+
160
+ it("should create a pull request", async () => {
161
+ const result = await gh.pulls.create({
162
+ owner: "octocat",
163
+ repo: "Hello-World",
164
+ title: "Test PR",
165
+ body: "Test PR body",
166
+ head: "test-user:feature-branch",
167
+ base: "main",
168
+ });
169
+
170
+ expect(result.data).toBeDefined();
171
+ expect(result.data.title).toBe("Test PR");
172
+ expect(result.data.number).toBe(999);
173
+ });
174
+
175
+ it("should update a pull request", async () => {
176
+ const result = await gh.pulls.update({
177
+ owner: "octocat",
178
+ repo: "Hello-World",
179
+ pull_number: 101,
180
+ title: "Updated PR Title",
181
+ body: "Updated PR body",
182
+ });
183
+
184
+ expect(result.data).toBeDefined();
185
+ expect(result.data.title).toBe("Updated PR Title");
186
+ });
187
+
188
+ it("should request reviewers", async () => {
189
+ const result = await gh.pulls.requestReviewers({
190
+ owner: "octocat",
191
+ repo: "Hello-World",
192
+ pull_number: 101,
193
+ reviewers: ["reviewer1", "reviewer2"],
194
+ });
195
+
196
+ expect(result.data).toBeDefined();
197
+ expect(result.data.requested_reviewers).toBeDefined();
198
+ });
199
+
200
+ it("should list review comments", async () => {
201
+ const result = await gh.pulls.listReviewComments({
202
+ owner: "octocat",
203
+ repo: "Hello-World",
204
+ pull_number: 101,
205
+ });
206
+
207
+ expect(result.data).toBeDefined();
208
+ expect(Array.isArray(result.data)).toBe(true);
209
+ });
210
+ });
211
+
212
+ describe("Issues API", () => {
213
+ it("should list issues", async () => {
214
+ const result = await gh.issues.listForRepo({
215
+ owner: "octocat",
216
+ repo: "Hello-World",
217
+ state: "open",
218
+ });
219
+
220
+ expect(result.data).toBeDefined();
221
+ expect(Array.isArray(result.data)).toBe(true);
222
+ });
223
+
224
+ it("should filter issues by labels", async () => {
225
+ const result = await gh.issues.listForRepo({
226
+ owner: "octocat",
227
+ repo: "Hello-World",
228
+ labels: "bug",
229
+ });
230
+
231
+ expect(result.data).toBeDefined();
232
+ expect(
233
+ result.data.every((issue) =>
234
+ issue.labels.some((l) => (typeof l === "string" ? l === "bug" : l.name === "bug")),
235
+ ),
236
+ ).toBe(true);
237
+ });
238
+
239
+ it("should get a specific issue", async () => {
240
+ const result = await gh.issues.get({
241
+ owner: "octocat",
242
+ repo: "Hello-World",
243
+ issue_number: 50,
244
+ });
245
+
246
+ expect(result.data).toBeDefined();
247
+ expect(result.data.number).toBe(50);
248
+ });
249
+
250
+ it("should update an issue", async () => {
251
+ const result = await gh.issues.update({
252
+ owner: "octocat",
253
+ repo: "Hello-World",
254
+ issue_number: 50,
255
+ body: "Updated issue body",
256
+ });
257
+
258
+ expect(result.data).toBeDefined();
259
+ expect(result.data.body).toBe("Updated issue body");
260
+ });
261
+
262
+ it("should list issue comments", async () => {
263
+ const result = await gh.issues.listComments({
264
+ owner: "octocat",
265
+ repo: "Hello-World",
266
+ issue_number: 50,
267
+ });
268
+
269
+ expect(result.data).toBeDefined();
270
+ expect(Array.isArray(result.data)).toBe(true);
271
+ });
272
+
273
+ it("should create an issue comment", async () => {
274
+ const result = await gh.issues.createComment({
275
+ owner: "octocat",
276
+ repo: "Hello-World",
277
+ issue_number: 50,
278
+ body: "Test comment",
279
+ });
280
+
281
+ expect(result.data).toBeDefined();
282
+ expect(result.data.body).toBe("Test comment");
283
+ });
284
+
285
+ it("should update a comment", async () => {
286
+ const result = await gh.issues.updateComment({
287
+ owner: "octocat",
288
+ repo: "Hello-World",
289
+ comment_id: 1,
290
+ body: "Updated comment",
291
+ });
292
+
293
+ expect(result.data).toBeDefined();
294
+ expect(result.data.body).toBe("Updated comment");
295
+ });
296
+
297
+ it("should get a comment", async () => {
298
+ const result = await gh.issues.getComment({
299
+ owner: "octocat",
300
+ repo: "Hello-World",
301
+ comment_id: 1,
302
+ });
303
+
304
+ expect(result.data).toBeDefined();
305
+ expect(result.data.body).toBeDefined();
306
+ });
307
+
308
+ it("should add labels to an issue", async () => {
309
+ const result = await gh.issues.addLabels({
310
+ owner: "octocat",
311
+ repo: "Hello-World",
312
+ issue_number: 50,
313
+ labels: ["bug", "enhancement"],
314
+ });
315
+
316
+ expect(result.data).toBeDefined();
317
+ expect(Array.isArray(result.data)).toBe(true);
318
+ });
319
+
320
+ it("should list timeline events", async () => {
321
+ const result = await gh.issues.listEventsForTimeline({
322
+ owner: "octocat",
323
+ repo: "Hello-World",
324
+ issue_number: 50,
325
+ });
326
+
327
+ expect(result.data).toBeDefined();
328
+ expect(Array.isArray(result.data)).toBe(true);
329
+ });
330
+ });
331
+
332
+ describe("Git API", () => {
333
+ it("should get an annotated tag", async () => {
334
+ const result = await gh.git.getTag({
335
+ owner: "comfyanonymous",
336
+ repo: "ComfyUI",
337
+ tag_sha: "abc123",
338
+ });
339
+
340
+ expect(result.data).toBeDefined();
341
+ expect(result.data.tag).toBeDefined();
342
+ expect(result.data.tagger).toBeDefined();
343
+ expect(result.data.message).toBeDefined();
344
+ });
345
+
346
+ // TODO: Fix MSW handler override for error responses - currently times out
347
+ // The issue is that server.use() runtime handler override doesn't properly
348
+ // intercept requests when testing error scenarios with Octokit
349
+ it.skip("should handle non-annotated tag errors gracefully", async () => {
350
+ // Mock a 404 response for lightweight tags
351
+ server.use(
352
+ http.get("https://api.github.com/repos/:owner/:repo/git/tags/:tag_sha", () => {
353
+ return new HttpResponse(null, {
354
+ status: 404,
355
+ statusText: "Not Found",
356
+ });
357
+ }),
358
+ );
359
+
360
+ try {
361
+ await gh.git.getTag({
362
+ owner: "comfyanonymous",
363
+ repo: "ComfyUI",
364
+ tag_sha: "lightweight-tag",
365
+ });
366
+ // Should not reach here
367
+ expect(true).toBe(false);
368
+ } catch (error: unknown) {
369
+ expect((error as { status: number }).status).toBe(404);
370
+ }
371
+ });
372
+ });
373
+
374
+ describe("Users API", () => {
375
+ it("should get authenticated user", async () => {
376
+ const result = await gh.users.getAuthenticated();
377
+
378
+ expect(result.data).toBeDefined();
379
+ expect(result.data.login).toBe("test-authenticated-user");
380
+ expect(result.data.email).toBeDefined();
381
+ });
382
+
383
+ it("should get a user by username", async () => {
384
+ const result = await gh.users.getByUsername({
385
+ username: "octocat",
386
+ });
387
+
388
+ expect(result.data).toBeDefined();
389
+ expect(result.data.login).toBe("octocat");
390
+ });
391
+ });
392
+
393
+ // TODO: Fix MSW handler override for error responses - currently times out
394
+ // The issue is that server.use() runtime handler override doesn't properly
395
+ // intercept requests when testing error scenarios with Octokit
396
+ describe("Error Handling", () => {
397
+ it.skip("should handle 404 errors", async () => {
398
+ server.use(
399
+ http.get("https://api.github.com/repos/:owner/:repo", () => {
400
+ return new HttpResponse(
401
+ JSON.stringify({
402
+ message: "Not Found",
403
+ documentation_url: "https://docs.github.com/rest",
404
+ }),
405
+ {
406
+ status: 404,
407
+ headers: {
408
+ "Content-Type": "application/json",
409
+ },
410
+ },
411
+ );
412
+ }),
413
+ );
414
+
415
+ try {
416
+ await gh.repos.get({
417
+ owner: "nonexistent",
418
+ repo: "nonexistent",
419
+ });
420
+ // Should not reach here
421
+ expect(true).toBe(false);
422
+ } catch (error: unknown) {
423
+ expect((error as { status: number }).status).toBe(404);
424
+ }
425
+ });
426
+
427
+ it.skip("should handle rate limit errors", async () => {
428
+ server.use(
429
+ http.get("https://api.github.com/repos/:owner/:repo", () => {
430
+ return new HttpResponse(
431
+ JSON.stringify({
432
+ message: "API rate limit exceeded",
433
+ documentation_url:
434
+ "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting",
435
+ }),
436
+ {
437
+ status: 403,
438
+ headers: {
439
+ "Content-Type": "application/json",
440
+ "X-RateLimit-Limit": "60",
441
+ "X-RateLimit-Remaining": "0",
442
+ },
443
+ },
444
+ );
445
+ }),
446
+ );
447
+
448
+ try {
449
+ await gh.repos.get({
450
+ owner: "octocat",
451
+ repo: "Hello-World",
452
+ });
453
+ // Should not reach here
454
+ expect(true).toBe(false);
455
+ } catch (error: unknown) {
456
+ expect((error as { status: number }).status).toBe(403);
457
+ }
458
+ });
459
+ });
460
+ });
461
+
462
+ describe("GitHub Cached Client (ghc)", () => {
463
+ beforeEach(async () => {
464
+ await clearGhCache();
465
+ });
466
+
467
+ it("should cache API responses", async () => {
468
+ // First call - should hit the API
469
+ const result1 = await ghc.repos.get({
470
+ owner: "octocat",
471
+ repo: "Hello-World",
472
+ });
473
+
474
+ expect(result1.data).toBeDefined();
475
+ expect(result1.data.name).toBe("Hello-World");
476
+
477
+ // Modify the mock to return different data
478
+ server.use(
479
+ http.get("https://api.github.com/repos/:owner/:repo", () => {
480
+ return HttpResponse.json({
481
+ id: 999999,
482
+ name: "Modified-Repo",
483
+ full_name: "octocat/Modified-Repo",
484
+ owner: {
485
+ login: "octocat",
486
+ id: 1,
487
+ },
488
+ default_branch: "main",
489
+ });
490
+ }),
491
+ );
492
+
493
+ // Second call - should use cache and return original data
494
+ const result2 = await ghc.repos.get({
495
+ owner: "octocat",
496
+ repo: "Hello-World",
497
+ });
498
+
499
+ // Should still have the original cached data
500
+ expect(result2.data.name).toBe("Hello-World");
501
+ expect(result2.data.id).toBe(123456);
502
+ });
503
+
504
+ it("should cache different parameters separately", async () => {
505
+ const result1 = await ghc.repos.get({
506
+ owner: "octocat",
507
+ repo: "Hello-World",
508
+ });
509
+
510
+ const result2 = await ghc.repos.get({
511
+ owner: "octocat",
512
+ repo: "Spoon-Knife",
513
+ });
514
+
515
+ expect(result1.data.name).toBe("Hello-World");
516
+ expect(result2.data.name).toBe("Spoon-Knife");
517
+ });
518
+
519
+ it("should clear cache when requested", async () => {
520
+ // First call - populate cache
521
+ const result1 = await ghc.repos.get({
522
+ owner: "octocat",
523
+ repo: "Hello-World",
524
+ });
525
+ expect(result1.data.name).toBe("Hello-World");
526
+
527
+ // Modify the mock
528
+ server.use(
529
+ http.get("https://api.github.com/repos/:owner/:repo", () => {
530
+ return HttpResponse.json({
531
+ id: 999999,
532
+ name: "Modified-Repo",
533
+ full_name: "octocat/Modified-Repo",
534
+ owner: {
535
+ login: "octocat",
536
+ id: 1,
537
+ },
538
+ default_branch: "main",
539
+ });
540
+ }),
541
+ );
542
+
543
+ // Clear cache
544
+ await clearGhCache();
545
+
546
+ // Third call - should hit the API and get new data
547
+ const result3 = await ghc.repos.get({
548
+ owner: "octocat",
549
+ repo: "Hello-World",
550
+ });
551
+
552
+ expect(result3.data.name).toBe("Modified-Repo");
553
+ });
554
+
555
+ it("should handle concurrent requests efficiently", async () => {
556
+ const promises = Array(10)
557
+ .fill(null)
558
+ .map(() =>
559
+ ghc.repos.get({
560
+ owner: "octocat",
561
+ repo: "Hello-World",
562
+ }),
563
+ );
564
+
565
+ const results = await Promise.all(promises);
566
+
567
+ // All results should be identical
568
+ expect(results.every((r) => r.data.name === "Hello-World")).toBe(true);
569
+ expect(results.every((r) => r.data.id === 123456)).toBe(true);
570
+ });
571
+ });
package/src/ghData.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Helper to fetch GitHub data using a provided fetch endpoint.
3
+ *
4
+ * @param fetchEndpoint - A function that fetches data from GitHub API.
5
+ * @returns A function that takes parameters and returns the fetched data.
6
+ *
7
+ * @deprecated use .then(e=>e.data) instead, because this function adds little value
8
+ */
9
+ export function ghData<Params extends object & { page?: number; per_page?: number }, Item>(
10
+ fetchEndpoint: (params?: Params) => Promise<{ data: Item }>,
11
+ ): (params: Params) => Promise<Item> {
12
+ return (params) => fetchEndpoint(params).then((res) => res.data);
13
+ }
@@ -0,0 +1,33 @@
1
+ import type sflow from "sflow";
2
+ import { pageFlow } from "sflow";
3
+
4
+ /**
5
+ * Helper to paginate through GitHub API endpoints that support pagination.
6
+ *
7
+ * @param listEndpoint The GitHub API endpoint function that returns a paginated list.
8
+ * @returns A function that takes the same parameters as the endpoint (excluding pagination parameters)
9
+ *
10
+ * @example
11
+ * const flow = ghPaged(ghc.pulls.listCommentsForReview)({ owner: "snomiao", repo: "ComfyNode-Registry-test", pull_number: 1, review_id: 1 })
12
+ * const allComments = flow.toArray()
13
+ *
14
+ * @see https://docs.github.com/en/rest/guides/traversing-with-pagination
15
+ *
16
+ * @template Params The type of parameters accepted by the GitHub API endpoint.
17
+ * @template Item The type of items returned by the GitHub API endpoint.
18
+ *
19
+ * @author snomiao <snomiao@gmail.com>
20
+ */
21
+
22
+ export function ghPageFlow<Params extends object & { page?: number; per_page?: number }, Item>(
23
+ listEndpoint: (params?: Params) => Promise<{ data: Item[] }>,
24
+ // options
25
+ { per_page = 100, startPage = 1 } = {},
26
+ ): (params: Params & { page?: never; per_page?: never }) => sflow<Item> {
27
+ return (params) =>
28
+ pageFlow(startPage, async (page) => {
29
+ const { data } = await listEndpoint({ ...params, page, per_page });
30
+
31
+ return { data, next: data.length >= per_page ? page + 1 : null };
32
+ }).flat();
33
+ }
@@ -0,0 +1,76 @@
1
+ import sflow from "sflow";
2
+ import { createOctokit } from "@/lib/github/createOctokit";
3
+ import { parseGithubRepoUrl } from "./parseOwnerRepo";
4
+
5
+ const tokens = process.env.GH_SUBSCRIBER_TOKENS?.split(",") || [];
6
+ const gh = (token: string) => createOctokit({ auth: token }).rest;
7
+
8
+ // dont await
9
+ showSubscriberUsers();
10
+
11
+ if (import.meta.main) {
12
+ await showSubscriberUsers();
13
+ // subscribeToRepo('')
14
+ await watchRepo("https://github.com/snomiao/ComfyNode-Registry-test");
15
+ // await listNotifications()
16
+ }
17
+
18
+ async function showSubscriberUsers() {
19
+ console.log(
20
+ `GH_TOKEN_SUBSCRIBER Users: `,
21
+ await sflow(tokens)
22
+ .map((token) => gh(token))
23
+ .map(async (gh) => {
24
+ const { data: user } = await gh.users.getAuthenticated();
25
+ return `${user.login} <${user.email}>`;
26
+ })
27
+ .join(", ")
28
+ .text(),
29
+ );
30
+ }
31
+ async function _listNotifications() {
32
+ console.log(
33
+ `Messages: `,
34
+ await sflow(tokens)
35
+ .map((token) => gh(token))
36
+ .map(async (gh) => {
37
+ const { data: result } = await gh.activity.listNotificationsForAuthenticatedUser();
38
+ return JSON.stringify(result);
39
+ })
40
+ .toArray(),
41
+ );
42
+ }
43
+
44
+ // list subscribed threads
45
+ // await sflow(tokens)
46
+ // .map((token) => gh(token))
47
+ // .map(async (gh) => {
48
+ // console.log(await gh.activity.getRepoSubscription({owner: }));
49
+ // })
50
+ // .run();
51
+
52
+ export async function watchRepo(repoUrl: string) {
53
+ await sflow(tokens)
54
+ .map((token) => gh(token))
55
+ .map(async (gh) => {
56
+ const result = await gh.activity.setRepoSubscription({
57
+ ...parseGithubRepoUrl(repoUrl),
58
+ subscribed: true,
59
+ });
60
+
61
+ console.log(result);
62
+ })
63
+ .run();
64
+ }
65
+ export async function unwatchRepo(repoUrl: string) {
66
+ await sflow(tokens)
67
+ .map((token) => gh(token))
68
+ .map(async (gh) => {
69
+ const result = await gh.activity.setRepoSubscription({
70
+ ...parseGithubRepoUrl(repoUrl),
71
+ subscribed: false,
72
+ });
73
+ console.log(result);
74
+ })
75
+ .run();
76
+ }