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
@@ -1,18 +1,23 @@
1
1
  import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
- import { csvFormatBody } from "d3";
3
2
  import sflow from "sflow";
4
3
  import { CNRepos } from "./CNRepos";
4
+ import { yaml } from "./utils/yaml";
5
5
 
6
6
  if (import.meta.main) {
7
7
  const pipeline = $pipeline(CNRepos)
8
8
  .match({ "createdPulls.state": "error" })
9
9
  .set({ error: "$createdPulls.error" })
10
- .project({ _id: 0, repository: 1, error: 1 })
11
- .as<{ repository: string; error: string }>()
10
+ .set({ mtime: "$createdPulls.mtime" })
11
+ .project({ _id: 0, repository: 1, error: 1, mtime: 1 })
12
+ .as<{ repository: string; error: string; mtime: Date }>()
12
13
  .aggregate();
13
- const csv = await sflow(pipeline)
14
- .map((e, i) => ((!i && "repository,error\n") || "") + csvFormatBody([e]) + "\n")
15
- .log()
16
- .text();
17
- await Bun.write(".cache/pr-errors.csv", csv);
14
+ // const csv = await sflow(pipeline)
15
+ // .map((e, i) => ((!i && "repository,error\n") || "") + csvFormatBody([e]) + "\n")
16
+ // .log()
17
+ // .text();
18
+ // await Bun.write(".cache/pr-errors.csv", csv);
19
+ // await Bun.$`code .cache/pr-errors.csv`
20
+ const yml = yaml.stringify(await sflow(pipeline).toArray());
21
+ await Bun.write(".cache/pr-errors.yml", yml);
22
+ await Bun.$`code .cache/pr-errors.yml`;
18
23
  }
package/src/cli.test.ts CHANGED
@@ -0,0 +1,2 @@
1
+ // TODO: Add CLI tests
2
+ export {};
package/src/cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!bun
1
+ #!/usr/bin/env bun
2
2
  import DIE from "@snomiao/die";
3
3
  import { readFile } from "fs/promises";
4
4
  import { argv, $ as zx } from "zx";
@@ -0,0 +1,2 @@
1
+ export const UNCLAIMED_ADMIN_PUBLISHER_ID = "admin-11338bd3-f081-43cf-b3f9-295c829826f7"; // copy from https://github.com/Comfy-Org/comfy-api/blob/main/db/publisher.go#L13
2
+ export const VERBOSE = process.env.VERBOSE;
@@ -4,18 +4,18 @@ import { match } from "ts-pattern";
4
4
  import { $OK, TaskError, TaskOK } from "../packages/mongodb-pipeline-ts/Task";
5
5
  import { CNRepos } from "./CNRepos";
6
6
  import { createComfyRegistryPullRequests } from "./createComfyRegistryPullRequests";
7
- import { $filaten, $stale } from "./db";
8
- import { parseUrlRepoOwner, stringifyOwnerRepo } from "./parseOwnerRepo";
9
- import { notifySlackLinks } from "./slack/notifySlackLinks";
7
+ import { $flatten, $stale } from "./db";
8
+ import { parseGithubRepoUrl, stringifyOwnerRepo } from "./parseOwnerRepo";
9
+ import { notifySlackLinks } from "@/lib/slack/notifySlackLinks";
10
10
  import { tLog } from "./utils/tLog";
11
11
  if (import.meta.main) {
12
12
  await tLog("createComfyRegistryPRsFromCandidates", createComfyRegistryPRsFromCandidates);
13
13
  console.log("all done");
14
14
  }
15
15
  export async function createComfyRegistryPRsFromCandidates() {
16
- await CNRepos.createIndex($filaten({ candidate: { data: 1 } }));
16
+ await CNRepos.createIndex($flatten({ candidate: { data: 1 } }));
17
17
  await CNRepos.createIndex(
18
- $filaten({
18
+ $flatten({
19
19
  candidate: { data: 1 },
20
20
  createdPulls: { state: 1, mtime: 1 },
21
21
  }),
@@ -23,7 +23,7 @@ export async function createComfyRegistryPRsFromCandidates() {
23
23
  return await pMap(
24
24
  $pipeline(CNRepos)
25
25
  .match(
26
- $filaten({
26
+ $flatten({
27
27
  candidate: { data: { $eq: true } },
28
28
  createdPulls: { state: { $ne: "ok" }, mtime: $stale("5m") },
29
29
  }),
@@ -32,19 +32,27 @@ export async function createComfyRegistryPRsFromCandidates() {
32
32
  async (repo) => {
33
33
  const { repository } = repo;
34
34
  console.log("Making PRs for " + repository);
35
- const createdPulls = await createComfyRegistryPullRequests(repository).then(TaskOK).catch(TaskError);
35
+ const createdPulls = await createComfyRegistryPullRequests(repository)
36
+ .then(TaskOK)
37
+ .catch(TaskError);
36
38
  match(createdPulls).with($OK, async ({ data }) => {
37
39
  const links = data.map((e) => ({
38
40
  href: e.html_url,
39
- name: stringifyOwnerRepo(parseUrlRepoOwner(e.html_url.replace(/\/pull\/.*$/, ""))) + " #" + e.title,
41
+ name:
42
+ stringifyOwnerRepo(parseGithubRepoUrl(e.html_url.replace(/\/pull\/.*$/, ""))) +
43
+ " #" +
44
+ e.title,
40
45
  }));
41
- await notifySlackLinks("PR just Created, @HaoHao check plz", links);
46
+ await notifySlackLinks("PR Created", links);
42
47
  await pMap(data, async (pull) => {
43
48
  const { html_url } = pull;
44
49
  // also update to crPulls
45
- await CNRepos.updateOne($filaten({ repository, crPulls: { data: { pull: { html_url } } } }), {
46
- $set: { "crPulls.data.$.pull": pull },
47
- });
50
+ await CNRepos.updateOne(
51
+ $flatten({ repository, crPulls: { data: { pull: { html_url } } } }),
52
+ {
53
+ $set: { "crPulls.data.$.pull": pull },
54
+ },
55
+ );
48
56
  });
49
57
  });
50
58
 
@@ -1,26 +1,51 @@
1
+ import minimist from "minimist";
1
2
  import pMap from "p-map";
2
- // import { chalk } from "zx";
3
- import { clone_modify_push_Branches } from "./clone_modify_push_Branches";
4
- import { createGithubForkForRepo } from "./createGithubForkForRepo";
3
+ import { createGithubForkForRepoEx } from "./createGithubForkForRepo";
5
4
  import { createGithubPullRequest } from "./createGithubPullRequest";
6
- import type { GithubPull } from "./gh/GithubPull";
7
- import { makeUpdateTomlLicenseBranch } from "./makeUpdateTomlLicenseBranch";
5
+ import type { GithubPull } from "@/lib/github/GithubPull";
6
+ import { makeGpl3LicenseBranch } from "./makeGpl3LicenseBranch";
7
+ import { makePublishcrBranch } from "./makePublishBranch";
8
+ import { makePyprojectBranch } from "./makeTomlBranch";
8
9
  import { parsePulls } from "./parsePullsState";
10
+ if (import.meta.main) {
11
+ // test repo
12
+ // const default_test_repo = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler";
13
+ const default_test_repo = "https://github.com/snomiao/ComfyNode-Registry-test";
14
+ const test_repo = minimist(process.argv.slice(2)).argv || default_test_repo;
15
+ console.info(await createComfyRegistryPullRequests(test_repo));
16
+ }
9
17
 
10
18
  export async function createComfyRegistryPullRequests(upstreamRepoUrl: string) {
11
- const forkedRepo = await createGithubForkForRepo(upstreamRepoUrl);
19
+ console.log("forking " + upstreamRepoUrl);
20
+ const forkedRepo = await createGithubForkForRepoEx(upstreamRepoUrl);
12
21
 
22
+ console.log("modifing " + forkedRepo.html_url);
13
23
  const PR_REQUESTS = await clone_modify_push_Branches(upstreamRepoUrl, forkedRepo.html_url);
14
- const prs = await pMap(PR_REQUESTS, async ({ type, ...prInfo }) => await createGithubPullRequest({ ...prInfo }));
24
+ const prs = await pMap(
25
+ PR_REQUESTS,
26
+ async ({ type: _type, ...prInfo }) => await createGithubPullRequest({ ...prInfo }),
27
+ );
15
28
 
16
29
  console.log("Registry PRs DONE");
17
30
 
18
31
  return ([...prs] as GithubPull[]).map((e) => parsePulls([e])[0]);
19
32
  }
20
33
 
21
- export async function clone_modify_push_Branches_for_updateTomlLicense(upstreamUrl: string, forkUrl: string) {
22
- return (await Promise.all([makeUpdateTomlLicenseBranch(upstreamUrl, forkUrl)]))
23
- .flatMap((e) => (e ? [e] : []))
34
+ /**
35
+ * this function will:
36
+ *
37
+ * for each branch (pyproject, publishcr)
38
+ * 1. clone forked srepo
39
+ * 2. modify on forked repo (add pyproject.toml, add .github/workflow/publish.yml)
40
+ * 3. commit changes, push to forked repo
41
+ * 4. check if all branch is ready, create PR to upstream repo
42
+ */
43
+ export async function clone_modify_push_Branches(upstreamUrl: string, forkUrl: string) {
44
+ const pyprojectBranchInfo = makePyprojectBranch(upstreamUrl, forkUrl);
45
+ const publishcrBranchInfo = makePublishcrBranch(upstreamUrl, forkUrl);
46
+ const gpl3LicenseBranchInfo = makeGpl3LicenseBranch(upstreamUrl, forkUrl);
47
+ return (await Promise.all([pyprojectBranchInfo, publishcrBranchInfo, gpl3LicenseBranchInfo]))
48
+ .filter((info): info is NonNullable<typeof info> => info !== null)
24
49
  .map(({ body, branch, title, type }) => ({
25
50
  body,
26
51
  branch,
@@ -1,14 +1,22 @@
1
1
  import DIE from "@snomiao/die";
2
2
  import md5 from "md5";
3
3
  import minimist from "minimist";
4
+ import { db } from "./db";
4
5
  import { FORK_OWNER } from "./FORK_OWNER";
5
6
  import { FORK_PREFIX } from "./FORK_PREFIX";
6
- import { createGithubFork } from "./gh/createGithubFork";
7
+ import { createGithubFork } from "@/lib/github/createGithubFork";
7
8
  import { ghUser } from "./ghUser";
8
- import { parseUrlRepoOwner } from "./parseOwnerRepo";
9
+ import { parseGithubRepoUrl } from "./parseOwnerRepo";
10
+
11
+ /** for cache */
12
+ const ForkedRepo = db.collection<{ repo: string; forkedRepo: string; updatedAt: Date }>(
13
+ "ForkedRepo",
14
+ );
9
15
 
10
16
  if (import.meta.main) {
11
- console.log(await createGithubForkForRepo("https://github.com/comfyanonymous/ComfyUI_TensorRT"));
17
+ console.log(
18
+ await createGithubForkForRepoEx("https://github.com/comfyanonymous/ComfyUI_TensorRT"),
19
+ );
12
20
  }
13
21
 
14
22
  /**
@@ -22,10 +30,19 @@ if (import.meta.main) {
22
30
  * @param upstreamRepoUrl
23
31
  * @returns forked repo info
24
32
  */
25
- export async function createGithubForkForRepo(
26
- upstreamRepoUrl: string,
27
- { forkUrl = createGithubForkUrlForRepo(upstreamRepoUrl) } = {},
28
- ) {
33
+ export async function createGithubForkForRepoEx(upstreamRepoUrl: string, { forkUrl = "" } = {}) {
34
+ forkUrl ||= await createGithubForkUrlForRepo(upstreamRepoUrl);
35
+ const alreadyForked = await ForkedRepo.findOne({ repo: upstreamRepoUrl, forkedRepo: forkUrl });
36
+ if (alreadyForked) {
37
+ console.debug(
38
+ `
39
+ Forked ${upstreamRepoUrl}
40
+ into ${forkUrl}
41
+ `.trim(),
42
+ );
43
+ return { html_url: forkUrl };
44
+ }
45
+
29
46
  console.debug(
30
47
  `
31
48
  Forking ${upstreamRepoUrl}
@@ -39,15 +56,23 @@ Forking ${upstreamRepoUrl}
39
56
  "forked url not expected, it's likely you already forked this repo in your account before, and now trying to fork it again with different salt. To recovery you could delete that repo forked before by manual. (the repo forked before is listed in FORK OK: .....)",
40
57
  ),
41
58
  );
59
+ await ForkedRepo.updateOne(
60
+ { repo: upstreamRepoUrl, forkedRepo: forkUrl },
61
+ { $set: { updatedAt: new Date() } },
62
+ { upsert: true },
63
+ );
42
64
  return forked;
43
65
  }
44
- export function createGithubForkUrlForRepo(upstreamRepoUrl: string) {
66
+ export async function createGithubForkUrlForRepo(upstreamRepoUrl: string) {
45
67
  // console.log(`* Change env.SALT=${salt} will fork into a different repo`);
46
- const upstream = parseUrlRepoOwner(upstreamRepoUrl);
68
+ const upstream = parseGithubRepoUrl(upstreamRepoUrl);
47
69
  const argv = minimist(process.argv.slice(2));
48
70
  const salt = argv.salt || process.env.SALT || "m3KMgZ2AeZGWYh7W";
49
- const repo_hash = md5(`${salt}-${ghUser.name}-${upstream.owner}/${upstream.repo}`).slice(0, 8);
50
- const forkRepoName = (FORK_PREFIX && `${FORK_PREFIX}${upstream.repo}-${repo_hash}`) || upstream.repo;
71
+ const repo_hash = md5(
72
+ `${salt}-${(await ghUser()).name}-${upstream.owner}/${upstream.repo}`,
73
+ ).slice(0, 8);
74
+ const forkRepoName =
75
+ (FORK_PREFIX && `${FORK_PREFIX}${upstream.repo}-${repo_hash}`) || upstream.repo;
51
76
  const forkDst = `${FORK_OWNER}/${forkRepoName}`;
52
77
  const forkUrl = `https://github.com/${forkDst}`;
53
78
  return forkUrl;
@@ -1,18 +1,19 @@
1
1
  import DIE, { catchArgs } from "@snomiao/die";
2
2
  import "git-diff";
3
- import { Octokit } from "octokit";
4
3
  import { pickAll } from "rambda";
5
4
  import sflow from "sflow";
6
5
  import { isRepoBypassed } from "./bypassRepos";
7
- import { gh } from "./gh";
8
- import type { GithubPull } from "./gh/GithubPull";
9
- import { parseUrlRepoOwner } from "./parseOwnerRepo";
6
+ import { createOctokit } from "@/lib/github/createOctokit";
7
+ import { gh } from "@/lib/github";
8
+ import type { GithubPull } from "@/lib/github/GithubPull";
9
+ import { parseGithubRepoUrl } from "./parseOwnerRepo";
10
+ import { parseTitleBodyOfMarkdown } from "./parseTitleBodyOfMarkdown";
10
11
  if (import.meta.main) {
11
12
  const srcUrl = "https://github.com/ComfyNodePRs/PR-ComfyUI-DareMerge-7bcbf6a9";
12
13
  const dstUrl = "https://github.com/54rt1n/ComfyUI-DareMerge";
13
- const src = parseUrlRepoOwner(srcUrl);
14
- const dst = parseUrlRepoOwner(dstUrl);
15
- const branch = "licence-update";
14
+ const src = parseGithubRepoUrl(srcUrl);
15
+ const dst = parseGithubRepoUrl(dstUrl);
16
+ const _branch = "licence-update";
16
17
  const repo = (await gh.repos.get({ ...dst })).data;
17
18
  const head_repo = `${src.owner}/${src.repo}`;
18
19
  // const head = `${src.owner}:${branch}`;
@@ -45,6 +46,21 @@ if (import.meta.main) {
45
46
  .toLog();
46
47
  console.log("all done");
47
48
  }
49
+ export async function createPR({
50
+ src: srcUrl,
51
+ dst: dstUrl,
52
+ branch,
53
+ msg,
54
+ }: {
55
+ src: string;
56
+ dst: string;
57
+ branch: string;
58
+ msg: string;
59
+ }) {
60
+ const { title, body } = parseTitleBodyOfMarkdown(msg);
61
+ return (await createGithubPullRequest({ srcUrl, dstUrl, branch, title, body })).html_url;
62
+ }
63
+
48
64
  export async function createGithubPullRequest({
49
65
  title,
50
66
  body,
@@ -60,42 +76,49 @@ export async function createGithubPullRequest({
60
76
  dstUrl: string; // upstream
61
77
  updateIfNotMatched?: boolean;
62
78
  }) {
79
+ // 2025-05-06 this function may create duplicated PR if calling too fast with same args
80
+ // TODO: try grab a lock with reop+branch
63
81
  if (isRepoBypassed(dstUrl)) DIE("dst repo is requested to be bypassed");
64
82
 
65
- const dst = parseUrlRepoOwner(dstUrl);
66
- const src = parseUrlRepoOwner(srcUrl);
83
+ const dst = parseGithubRepoUrl(dstUrl);
84
+ const src = parseGithubRepoUrl(srcUrl);
67
85
  const repo = (await gh.repos.get({ ...dst })).data;
68
86
 
69
- // TODO: seems has bugs on head_repo
70
- const existedList = (
87
+ // 2025-03-13 prevent duplicated PR, if there are PR is closed with same content.
88
+ // reported here
89
+ // https://github.com/Chaoses-Ib/ComfyUI_Ib_CustomNodes/pulls?q=is%3Apr
90
+ //
91
+ const sameContentPRList = (
71
92
  await gh.pulls.list({
72
93
  // source repo
73
- state: "all",
74
- head_repo: `${src.owner}/${src.repo}`,
75
- head: `${src.owner}:${branch}`,
94
+ // state: "open",
95
+ head: encodeURIComponent(`${src.owner}:${branch}`),
76
96
  // pr will merge into
77
97
  owner: dst.owner,
78
98
  repo: dst.repo,
79
99
  base: repo.default_branch,
80
100
  })
81
- ).data;
82
- if (existedList.length > 1)
101
+ ).data.filter((e) => e.title === title && e.body === body);
102
+
103
+ sameContentPRList.length <= 1 ||
83
104
  DIE(
84
- new Error(`expect only 1 pr, but got ${existedList.length}`, {
85
- cause: { existed: existedList.map((e) => ({ url: e.html_url, title: e.title })) },
105
+ new Error(`expect <= 1 same content pr, but got ${sameContentPRList.length}`, {
106
+ cause: {
107
+ sameContentPRList: sameContentPRList.map((e) => ({ url: e.html_url, title: e.title })),
108
+ },
86
109
  }),
87
110
  );
88
111
 
89
112
  const pr_result =
90
- existedList[0] ??
91
- (await ghPR()
113
+ // existedList[0] ??
114
+ await ghPR()
92
115
  .pulls.create({
93
116
  // pr info
94
117
  title,
95
118
  body,
96
119
  // source repo
97
- head_repo: src.owner + "/" + src.repo,
98
- head: src.owner + ":" + branch,
120
+ head_repo: `${src.owner}/${src.repo}`,
121
+ head: `${src.owner}:${branch}`,
99
122
  // pr will merge into
100
123
  owner: dst.owner,
101
124
  repo: dst.repo,
@@ -114,8 +137,8 @@ export async function createGithubPullRequest({
114
137
  await gh.pulls.list({
115
138
  // source repo
116
139
  state: "open",
117
- head_repo: `${src.owner}/${src.repo}`,
118
- head: `${src.owner}:${branch}`,
140
+ // head_repo: `${src.owner}/${src.repo}`,
141
+ head: encodeURIComponent(`${src.owner}:${branch}`),
119
142
  // pr will merge into
120
143
  owner: dst.owner,
121
144
  repo: dst.repo,
@@ -123,15 +146,27 @@ export async function createGithubPullRequest({
123
146
  })
124
147
  ).data; // .filter(existed => existed.title === title);
125
148
 
126
- if (existedList.length !== 1)
149
+ if (existedList.length !== 1) {
127
150
  DIE(
128
151
  new Error("expect only 1 pr, but got " + existedList.length, {
129
- cause: { existed: existedList.map((e) => ({ url: e.html_url, title: e.title })) },
152
+ cause: {
153
+ existed: existedList.map((e) => ({
154
+ url: e.html_url,
155
+ title: e.title,
156
+ })),
157
+ error: e,
158
+ state: "open",
159
+ head: `${src.owner}:${branch}`,
160
+ owner: dst.owner,
161
+ repo: dst.repo,
162
+ base: repo.default_branch,
163
+ },
130
164
  }),
131
165
  );
166
+ }
132
167
 
133
168
  return existedList[0];
134
- }));
169
+ });
135
170
 
136
171
  console.log("PR OK", pr_result.html_url);
137
172
  const mismatch = pr_result.title !== title || pr_result.body !== body;
@@ -139,19 +174,34 @@ export async function createGithubPullRequest({
139
174
  if (!updateIfNotMatched)
140
175
  DIE(
141
176
  new Error("pr content mismatch", {
142
- cause: { mismatch, expected: { title, body }, actual: pickAll(["title", "body"], pr_result) },
177
+ cause: {
178
+ mismatch,
179
+ expected: { title, body },
180
+ actual: pickAll(["title", "body"], pr_result),
181
+ },
143
182
  }),
144
183
  );
145
- const { owner, repo } = parseUrlRepoOwner(dstUrl); // upstream repo
146
- const updated = (await catchArgs(ghPR().pulls.update)({ pull_number: pr_result.number, body, title, owner, repo }))
147
- .data!;
184
+ const { owner, repo } = parseGithubRepoUrl(dstUrl); // upstream repo
185
+ const updated = (
186
+ await catchArgs(ghPR().pulls.update)({
187
+ pull_number: pr_result.number,
188
+ body,
189
+ title,
190
+ owner,
191
+ repo,
192
+ })
193
+ ).data!;
148
194
  const updatedPRStillMismatch = updated.title !== title || updated.body !== body;
149
195
  if (updatedPRStillMismatch) DIE(new Error("updatedPRStillMismatch", { cause: arguments }));
150
- console.warn(`PR content updated ${owner}/${repo} / \n<< ${pr_result.title}\n>> ${updated.title}`);
196
+ console.warn(
197
+ `PR content updated ${owner}/${repo} / \n<< ${pr_result.title}\n>> ${updated.title}`,
198
+ );
151
199
  }
152
200
  return pr_result as GithubPull;
153
201
  }
154
202
 
155
203
  function ghPR() {
156
- return new Octokit({ auth: process.env.GH_TOKEN_COMFY_PR || DIE(new Error("Missing env.GH_TOKEN_COMFY_PR")) }).rest;
204
+ return createOctokit({
205
+ auth: process.env.GH_TOKEN_COMFY_PR || DIE(new Error("Missing env.GH_TOKEN_COMFY_PR")),
206
+ }).rest;
157
207
  }
@@ -1,17 +1,17 @@
1
1
  import DIE from "@snomiao/die";
2
- import { gh } from "./gh";
2
+ import { gh } from "@/lib/github";
3
3
  import { ghUser } from "./ghUser";
4
4
  import { parseIssueUrl } from "./parseIssueUrl";
5
5
 
6
6
  if (import.meta.main) {
7
7
  // const url = "https://github.com/snomiao/ComfyNode-Registry-test/pull/1";
8
8
  // const body = "Hello World @snomiao";
9
- // const result = await createIssueComment(url, body, ghUser.login);
9
+ // const result = await createIssueComment(url, body, (await ghUser()).login);
10
10
  // console.log(result.comment.html_url);
11
11
  // console.log(result.comments.map((e) => e.html_url));
12
12
  const url = "https://github.com/snomiao/ComfyNode-Registry-test/pull/28";
13
13
  const body = "Hello World @robinjhuang";
14
- const result = await createIssueComment(url, body, ghUser.login);
14
+ const result = await createIssueComment(url, body, (await ghUser()).login);
15
15
  console.log(result.comment.html_url);
16
16
  console.log(result.comments.map((e) => e.html_url));
17
17
  }
@@ -25,7 +25,7 @@ export async function createIssueComment(issueUrl: string, body: string, by: str
25
25
  const result = await (async function () {
26
26
  const commentExisted = comments.find((e) => e.body === body);
27
27
  if (commentExisted) return { comment: commentExisted, comments };
28
- if (by !== ghUser.login) DIE("Fails to creating issue: user not match");
28
+ if (by !== (await ghUser()).login) DIE("Fails to creating issue: user not match");
29
29
  const comment = (await gh.issues.createComment({ ...parseIssueUrl(issueUrl), body })).data;
30
30
  console.log("+ IssueComment " + comment.html_url);
31
31
  return { comment, comments: [...comments, comment] };
@@ -1,11 +1,41 @@
1
- import type { mockPublishedNodes } from "../mocks/mockPublishedNodes";
2
1
  import DIE from "@snomiao/die";
2
+ import type { mockPublishedNodes } from "../mocks/mockPublishedNodes";
3
3
  import { fetchJson } from "./utils/fetchJson";
4
4
 
5
- export async function fetchCRNodes() {
5
+ export async function fetchCRNodes(): Promise<
6
+ {
7
+ author: string;
8
+ description: string;
9
+ icon: string;
10
+ id: string;
11
+ latest_version: {
12
+ changelog: string;
13
+ createdAt: string;
14
+ dependencies: never[];
15
+ deprecated: boolean;
16
+ id: string;
17
+ version: string;
18
+ };
19
+ license: string;
20
+ name: string;
21
+ publisher: {
22
+ createdAt: string;
23
+ description: string;
24
+ id: string;
25
+ logo: string;
26
+ members: never[];
27
+ name: string;
28
+ source_code_repo: string;
29
+ support: string;
30
+ website: string;
31
+ };
32
+ repository: string;
33
+ tags: never[];
34
+ }[]
35
+ > {
6
36
  const r = (await fetchJson<typeof mockPublishedNodes>(
7
- "https://api.comfy.org/nodes?page=1&limit=99999999"
37
+ "https://api.comfy.org/nodes?page=1&limit=99999999",
8
38
  )) as typeof mockPublishedNodes;
9
- r.totalPages === 1 || DIE("FAIL TO FETCH ALL NODES");
39
+ if (r.totalPages !== 1) DIE("FAIL TO FETCH ALL NODES");
10
40
  return r.nodes;
11
41
  }
@@ -1,6 +1,8 @@
1
1
  import { fetchJson } from "./utils/fetchJson";
2
2
  export async function fetchCurrentGeoInfo() {
3
- const { query, city, iat, lon, countryCode, region, regionName } = (await fetchJson("http://ip-api.com/json")) as any;
3
+ const { query, city, iat, lon, countryCode, region, regionName } = (await fetchJson(
4
+ "http://ip-api.com/json",
5
+ )) as Record<string, unknown>;
4
6
  const geo = { ip: query, city, iat, lon, countryCode, region, regionName };
5
7
  return geo;
6
8
  }
@@ -1,5 +1,5 @@
1
1
  import pMap from "p-map";
2
- import { fetchIssueComments } from "./gh/fetchIssueComments";
2
+ import { fetchIssueComments } from "@/lib/github/fetchIssueComments";
3
3
  import { matchRelatedPulls } from "./matchRelatedPulls";
4
4
  import type { GithubPullParsed } from "./parsePullsState";
5
5
  import { summaryLastPullComment } from "./summaryLastPullComment";
@@ -1,12 +1,9 @@
1
1
  import { fromPairs } from "rambda";
2
2
  import { fetchCMNodes } from "./fetchCMNodes";
3
3
 
4
-
5
4
  export async function fetchRepoDescriptionMap() {
6
5
  const nodeList = await fetchCMNodes();
7
- const repoDescriptionMap = fromPairs(
8
- nodeList.map((e) => [e.reference, e.description])
9
- );
6
+ const repoDescriptionMap = fromPairs(nodeList.map((e) => [e.reference, e.description]));
10
7
  repoDescriptionMap["https://github.com/snomiao/ComfyNode-Registry-test"] =
11
8
  "ComfyNode-Registry-test-description";
12
9
 
@@ -2,7 +2,9 @@ import { $before, $fresh, $stale } from "@/packages/mongodb-pipeline-ts/$fresh";
2
2
  import { tryCatch } from "rambda";
3
3
  import { z } from "zod";
4
4
 
5
- const znot_include = z.object({ "not-include": z.string() }).transform((x) => ({ $nin: x["not-include"] }));
5
+ const znot_include = z
6
+ .object({ "not-include": z.string() })
7
+ .transform((x) => ({ $nin: x["not-include"] }));
6
8
  const mString = z.string().transform((x) =>
7
9
  tryCatch<string, string | RegExp>(
8
10
  (x) => new RegExp(x) as RegExp,
@@ -12,8 +14,12 @@ const mString = z.string().transform((x) =>
12
14
  const zbefore = z
13
15
  .object({ $before: z.coerce.date().or(z.string()).or(z.number()) })
14
16
  .transform((x) => $before(x.$before));
15
- const zfresh = z.object({ $fresh: z.coerce.date().or(z.string()).or(z.number()) }).transform((x) => $fresh(x.$fresh));
16
- const mstale = z.object({ $stale: z.coerce.date().or(z.string()).or(z.number()) }).transform((x) => $stale(x.$stale));
17
+ const zfresh = z
18
+ .object({ $fresh: z.coerce.date().or(z.string()).or(z.number()) })
19
+ .transform((x) => $fresh(x.$fresh));
20
+ const mstale = z
21
+ .object({ $stale: z.coerce.date().or(z.string()).or(z.number()) })
22
+ .transform((x) => $stale(x.$stale));
17
23
  const mDate = z.date().or(zbefore).or(zfresh).or(mstale);
18
24
  const mAny = z.number().or(znot_include).or(mString).or(zbefore).or(zfresh);
19
25
  const mNumber = z
@@ -70,7 +76,7 @@ const zFollowUpRule = z.object({
70
76
  tags: mAny,
71
77
  })
72
78
  .strict(),
73
- // close: z.any(),
79
+ // close: z.unknown(),
74
80
  })
75
81
  .strict()
76
82
  .partial(),
@@ -1,13 +1,9 @@
1
1
  import { rm } from "fs/promises";
2
2
  import { getRepoWorkingDir } from "./getRepoWorkingDir";
3
- import { parseUrlRepoOwner } from "./parseOwnerRepo";
3
+ import { parseGithubRepoUrl } from "./parseOwnerRepo";
4
4
 
5
- export async function getBranchWorkingDir(
6
- upstreamUrl: string,
7
- forkUrl: string,
8
- branch: string
9
- ) {
10
- const src = parseUrlRepoOwner(upstreamUrl);
5
+ export async function getBranchWorkingDir(upstreamUrl: string, forkUrl: string, branch: string) {
6
+ const src = parseGithubRepoUrl(upstreamUrl);
11
7
  const dir = getRepoWorkingDir(forkUrl);
12
8
  const packageName = src.repo;
13
9
  const cwd = `${dir}/${branch}/${packageName}`;
@@ -1,6 +1,5 @@
1
- import { parseUrlRepoOwner } from "./parseOwnerRepo";
1
+ import { parseGithubRepoUrl } from "./parseOwnerRepo";
2
2
 
3
3
  export function getRepoWorkingDir(forkUrl: string) {
4
- return `prs/${parseUrlRepoOwner(forkUrl).repo}`;
4
+ return `prs/${parseGithubRepoUrl(forkUrl).repo}`;
5
5
  }
6
-