comfy-pr 0.2.24 → 0.2.26

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 (85) hide show
  1. package/README.md +247 -225
  2. package/index.ts +6 -0
  3. package/next-env.d.ts +5 -5
  4. package/package.json +52 -25
  5. package/src/Authors.ts +42 -48
  6. package/src/CMNodes.ts +60 -60
  7. package/src/CNRepos.ts +78 -74
  8. package/src/CRNodes.ts +27 -27
  9. package/src/CRPulls.ts +4 -4
  10. package/src/EmailTasks.ts +101 -0
  11. package/src/FORK_OWNER.ts +5 -5
  12. package/src/FORK_PREFIX.ts +5 -5
  13. package/src/FollowRules.ts +25 -25
  14. package/src/GIT_USEREMAIL.ts +5 -5
  15. package/src/GIT_USERNAME.ts +9 -9
  16. package/src/GithubIssueComments.ts +3 -3
  17. package/src/PushedBranch.ts +3 -3
  18. package/src/Totals.ts +24 -9
  19. package/src/TrackingPRs.ts +12 -12
  20. package/src/WorkerInstances.ts +89 -89
  21. package/src/addCommentAction.ts +73 -65
  22. package/src/analyzePullsStatus.ts +219 -170
  23. package/src/analyzeTotals.test.ts +5 -5
  24. package/src/analyzeTotals.ts +118 -118
  25. package/src/bypassRepos.spec.ts +10 -0
  26. package/src/bypassRepos.ts +8 -0
  27. package/src/checkComfyActivated.ts +39 -39
  28. package/src/checkPRsFailures.ts +18 -0
  29. package/src/cli.ts +40 -40
  30. package/src/clone_modify_push_Branches.ts +21 -21
  31. package/src/createComfyRegistryPRsFromCandidates.ts +55 -55
  32. package/src/createComfyRegistryPullRequests.ts +32 -22
  33. package/src/createGithubForkForRepo.ts +54 -37
  34. package/src/createGithubPullRequest.ts +157 -94
  35. package/src/createIssueComment.ts +34 -29
  36. package/src/fetchCMNodes.ts +22 -22
  37. package/src/fetchComfyRegistryNodes.ts +11 -11
  38. package/src/fetchCurrentGeoInfo.ts +6 -6
  39. package/src/fetchRelatedPullWithComments.ts +15 -15
  40. package/src/fetchRepoDescriptionMap.ts +15 -15
  41. package/src/followRuleSchema.ts +79 -76
  42. package/src/getActivatedShell.ts +18 -18
  43. package/src/getBranchWorkingDir.ts +16 -16
  44. package/src/getRepoWorkingDir.ts +5 -5
  45. package/src/ghUser.ts +9 -6
  46. package/src/index.ts +26 -22
  47. package/src/initializeFollowRules.ts +26 -26
  48. package/src/makePublishBranch.ts +58 -58
  49. package/src/makeTomlBranch.ts +53 -53
  50. package/src/makeUpdateTomlLicenseBranch.ts +245 -0
  51. package/src/matchRelatedPulls.ts +47 -60
  52. package/src/muteInstances.ts +16 -16
  53. package/src/parseIssueUrl.ts +6 -6
  54. package/src/parseOwnerRepo.ts +33 -33
  55. package/src/parsePullUrl.ts +6 -6
  56. package/src/parsePullsState.ts +6 -6
  57. package/src/parseTitleBodyOfMarkdown.ts +8 -8
  58. package/src/postSlackMessage.ts +21 -21
  59. package/src/preload.ts +30 -30
  60. package/src/pullStatusFollowSchema.ts +12 -12
  61. package/src/readTemplateTitle.ts +11 -11
  62. package/src/sendEmailAction.ts +56 -0
  63. package/src/sendGmail.ts +105 -0
  64. package/src/showFollowRuleSet.ts +12 -12
  65. package/src/summaryLastPullComment.ts +8 -8
  66. package/src/tomlFillDescription.ts +17 -17
  67. package/src/updateAuthors.ts +7 -7
  68. package/src/updateAuthorsForGithub.ts +54 -50
  69. package/src/updateAuthorsFromCNRepo.ts +30 -30
  70. package/src/updateCMNodesDuplicationWarnings.ts +56 -56
  71. package/src/updateCMRepos.ts +27 -27
  72. package/src/updateCNRepos.ts +58 -58
  73. package/src/updateCNReposCRPullsComments.ts +40 -40
  74. package/src/updateCNReposInfo.ts +58 -57
  75. package/src/updateCNReposPRCandidate.ts +85 -85
  76. package/src/updateCNReposPulls.ts +31 -28
  77. package/src/updateCNReposPullsDashboard.ts +49 -49
  78. package/src/updateCNReposRelatedPulls.ts +25 -25
  79. package/src/updateCRNodes.ts +50 -50
  80. package/src/updateCRRepos.ts +26 -26
  81. package/src/updateComfyTotals.ts +43 -43
  82. package/src/updateFollowRuleSet.ts +136 -127
  83. package/src/updateOutdatedPullsTemplates.ts +165 -160
  84. package/src/updateSlackMessages.ts +43 -43
  85. package/tailwind.config.ts +75 -20
@@ -1,11 +1,11 @@
1
- import { readFile } from "fs/promises";
2
- import { parseTitleBodyOfMarkdown } from "./parseTitleBodyOfMarkdown";
3
-
4
- export async function readTemplateTitle(filename: string) {
5
- return await readTemplate(filename).then((e) => e.title);
6
- }
7
- export async function readTemplate(filename: string) {
8
- return readFile("./templates/" + filename, "utf8").then(
9
- parseTitleBodyOfMarkdown,
10
- );
11
- }
1
+ import { readFile } from "fs/promises";
2
+ import { parseTitleBodyOfMarkdown } from "./parseTitleBodyOfMarkdown";
3
+
4
+ export async function readTemplateTitle(filename: string) {
5
+ return await readTemplate(filename).then((e) => e.title);
6
+ }
7
+ export async function readTemplate(filename: string) {
8
+ return readFile("./templates/" + filename, "utf8").then(
9
+ parseTitleBodyOfMarkdown,
10
+ );
11
+ }
@@ -0,0 +1,56 @@
1
+ "use server";
2
+ import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
3
+ import { TaskDataOrNull, type Task } from "@/packages/mongodb-pipeline-ts/Task";
4
+ import DIE from "@snomiao/die";
5
+ import pMap from "p-map";
6
+ import { snoflow } from "snoflow";
7
+ import type { z } from "zod";
8
+ import type { PullStatusShown } from "./analyzePullsStatus";
9
+ import { CNRepos } from "./CNRepos";
10
+ import { $filaten } from "./db";
11
+ import { enqueueEmailTask } from "./EmailTasks";
12
+ import { zSendEmailAction } from "./followRuleSchema";
13
+ import { yaml } from "./utils/yaml";
14
+
15
+ export async function sendEmailAction({
16
+ matched,
17
+ action,
18
+ runAction,
19
+ rule,
20
+ }: {
21
+ matched: Task<PullStatusShown[]>;
22
+ action: z.infer<typeof zSendEmailAction>;
23
+ runAction: boolean;
24
+ rule: { name: string };
25
+ }) {
26
+ return await pMap(
27
+ TaskDataOrNull(matched) ?? DIE("NO-PAYLOAD-AVAILABLE"),
28
+ async (payload) => {
29
+ if (action.provider !== "google") DIE("Currently we only support gmail sender");
30
+ const loadedAction = await snoflow([action])
31
+ .map((e) => yaml.stringify(e))
32
+ .map((e) =>
33
+ // replace {{var}} s
34
+ e.replace(
35
+ /{{\$([_A-Za-z0-9]+)}}/g,
36
+ (_, key: string) =>
37
+ (payload as any)[key] || DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
38
+ ),
39
+ )
40
+ .map((e) => yaml.parse(e))
41
+ .map((y) => zSendEmailAction.parse(y))
42
+ .map((a) => ({ ...a, action: "send-email" }))
43
+ .toLast();
44
+
45
+ if (runAction) {
46
+ const task = await enqueueEmailTask(loadedAction);
47
+ console.log(rule.name + " email enqueued :" + yaml.stringify(loadedAction));
48
+ await CNRepos.updateOne($filaten({ crPulls: { data: $elemMatch({ pull: { html_url: payload.url } }) } }), {
49
+ $set: { "crPulls.data.$.emailTask_id": task._id },
50
+ });
51
+ }
52
+ return loadedAction;
53
+ },
54
+ { concurrency: 1 },
55
+ );
56
+ }
@@ -0,0 +1,105 @@
1
+ import DIE from "@snomiao/die";
2
+ // import type { Credentials } from "google-auth-library";
3
+ import type { Credentials } from "google-auth-library";
4
+ import { GoogleApis } from "googleapis";
5
+ import type { OAuth2Client } from "googleapis-common";
6
+ import { createMimeMessage } from "mail-mime-builder";
7
+ import markdownIt from "markdown-it";
8
+ import {
9
+ GCloudOAuth2Credentials,
10
+ getGCloudOAuth2Client,
11
+ handleGCloudOAuth2Callback,
12
+ } from "./gcloud/GCloudOAuth2Credentials";
13
+ if (import.meta.main) {
14
+ // send test email
15
+ const name = "snomiao";
16
+ const from = "snomiao@gmail.com";
17
+ const to = ".snomiao@gmail.com";
18
+ const subject = "Hello! snomiao";
19
+ const markdown = `
20
+ # hello from sno
21
+
22
+ You've <b>just</b> received an *email* from snomiao.
23
+
24
+ Thank you for receiving this email!!!!
25
+ `;
26
+
27
+ const html = markdownIt().render(markdown);
28
+ console.log(html);
29
+
30
+ const sent = await sendGmail({
31
+ name,
32
+ from,
33
+ to,
34
+ subject,
35
+ html,
36
+ auth: await getGCloudOAuth2Client({
37
+ email: from,
38
+ scope: ["https://www.googleapis.com/auth/gmail.compose"],
39
+ authorize: async (url) => {
40
+ // check saved credential in db
41
+ const getCred = async () =>
42
+ (
43
+ await GCloudOAuth2Credentials.findOne({
44
+ scopes: "https://www.googleapis.com/auth/gmail.compose",
45
+ email: from,
46
+ credentials: { $exists: true },
47
+ })
48
+ )?.credentials;
49
+ const cred = await getCred();
50
+ if (cred) return cred;
51
+
52
+ // otherwise wait for user approve
53
+ (await (await import("open")).default(url)).unref();
54
+ // setup one time server to receive ?code={{....}}
55
+ return await new Promise<Credentials>((r) => {
56
+ const server = Bun.serve({
57
+ fetch: async (req) => {
58
+ // wait for code, and then save to db
59
+ const res = await handleGCloudOAuth2Callback(req);
60
+ const cred = await getCred();
61
+ if (cred) r(cred);
62
+ server.stop();
63
+ return res;
64
+ },
65
+ });
66
+ });
67
+ },
68
+ }),
69
+ });
70
+ console.log(sent);
71
+ }
72
+
73
+ export async function sendGmail({
74
+ name,
75
+ from,
76
+ to,
77
+ subject,
78
+ text,
79
+ html,
80
+ auth,
81
+ }: {
82
+ name: string;
83
+ from: string;
84
+ to: string;
85
+ subject: string;
86
+ text?: string;
87
+ html?: string;
88
+ auth: OAuth2Client;
89
+ }) {
90
+ const msg = createMimeMessage();
91
+ msg.setSender({ name: name, addr: from });
92
+ msg.setRecipient(to);
93
+ msg.setSubject(subject);
94
+ text && msg.addMessage({ contentType: "text/plain", data: text });
95
+ html && msg.addMessage({ contentType: "text/html", data: html });
96
+ text || html || DIE("Missing msg body");
97
+
98
+ const result = await new GoogleApis().gmail("v1").users.messages.send({
99
+ auth,
100
+ userId: from,
101
+ requestBody: { raw: btoa(msg.asRaw()) },
102
+ });
103
+ const sent = result.data;
104
+ return sent;
105
+ }
@@ -1,12 +1,12 @@
1
- "use server";
2
- import DIE from "@snomiao/die";
3
- import { FollowRuleSets } from "./FollowRules";
4
- import { updateFollowRuleSet } from "./updateFollowRuleSet";
5
-
6
- export async function showFollowRuleSet({ name = "default" } = {}) {
7
- const ruleset = (await FollowRuleSets.findOne({ name })) ?? DIE("default ruleset not found");
8
- return await updateFollowRuleSet({
9
- name: ruleset.name,
10
- yaml: ruleset.yamlWhenEnabled ?? DIE("Rule not enabled"),
11
- });
12
- }
1
+ "use server";
2
+ import DIE from "@snomiao/die";
3
+ import { FollowRuleSets } from "./FollowRules";
4
+ import { updateFollowRuleSet } from "./updateFollowRuleSet";
5
+
6
+ export async function showFollowRuleSet({ name = "default" } = {}) {
7
+ const ruleset = (await FollowRuleSets.findOne({ name })) ?? DIE("default ruleset not found");
8
+ return await updateFollowRuleSet({
9
+ name: ruleset.name,
10
+ yaml: ruleset.yamlWhenEnabled ?? DIE("Rule not enabled"),
11
+ });
12
+ }
@@ -1,8 +1,8 @@
1
- import { type GithubIssueComment } from "./GithubIssueComments";
2
-
3
- export function summaryLastPullComment(comments: GithubIssueComment[]) {
4
- // assume ascending order
5
- const last = comments.toReversed()[0];
6
- const lastText = (last ?? "") && "@" + last.user!.name + ":" + last.body!.replace(/\s+/gim, " ");
7
- return lastText;
8
- }
1
+ import { type GithubIssueComment } from "./GithubIssueComments";
2
+
3
+ export function summaryLastPullComment(comments: GithubIssueComment[]) {
4
+ // assume ascending order
5
+ const last = comments.toReversed()[0];
6
+ const lastText = (last ?? "") && "@" + last.user!.name + ":" + last.body!.replace(/\s+/gim, " ");
7
+ return lastText;
8
+ }
@@ -1,17 +1,17 @@
1
- import { writeFile, readFile } from "fs/promises";
2
- import DIE from "@snomiao/die";
3
- import toml from "toml";
4
- import { fetchRepoDescriptionMap } from "./fetchRepoDescriptionMap";
5
-
6
- export async function tomlFillDescription(referenceUrl: string, pyprojectToml: string) {
7
- const repoDescriptionMap = await fetchRepoDescriptionMap();
8
- const matchedDescription = repoDescriptionMap[referenceUrl]?.toString() ||
9
- DIE("Warn: missing description for " + referenceUrl);
10
- const replaced = (await readFile(pyprojectToml, "utf8")).replace(
11
- `description = ""`,
12
- `description = ${JSON.stringify(matchedDescription)}`
13
- );
14
- // check validity
15
- toml.parse(replaced);
16
- await writeFile(pyprojectToml, replaced);
17
- }
1
+ import { writeFile, readFile } from "fs/promises";
2
+ import DIE from "@snomiao/die";
3
+ import toml from "toml";
4
+ import { fetchRepoDescriptionMap } from "./fetchRepoDescriptionMap";
5
+
6
+ export async function tomlFillDescription(referenceUrl: string, pyprojectToml: string) {
7
+ const repoDescriptionMap = await fetchRepoDescriptionMap();
8
+ const matchedDescription = repoDescriptionMap[referenceUrl]?.toString() ||
9
+ DIE("Warn: missing description for " + referenceUrl);
10
+ const replaced = (await readFile(pyprojectToml, "utf8")).replace(
11
+ `description = ""`,
12
+ `description = ${JSON.stringify(matchedDescription)}`
13
+ );
14
+ // check validity
15
+ toml.parse(replaced);
16
+ await writeFile(pyprojectToml, replaced);
17
+ }
@@ -1,7 +1,7 @@
1
- import { updateAuthorsForGithub } from "./updateAuthorsForGithub";
2
- import { updateAuthorsFromCNRepo } from "./updateAuthorsFromCNRepo";
3
-
4
- export async function updateAuthors() {
5
- await updateAuthorsFromCNRepo();
6
- await updateAuthorsForGithub();
7
- }
1
+ import { updateAuthorsForGithub } from "./updateAuthorsForGithub";
2
+ import { updateAuthorsFromCNRepo } from "./updateAuthorsFromCNRepo";
3
+
4
+ export async function updateAuthors() {
5
+ await updateAuthorsFromCNRepo();
6
+ await updateAuthorsForGithub();
7
+ }
@@ -1,50 +1,54 @@
1
- import { $OK, TaskDataOrNull, TaskError, TaskOK } from "@/packages/mongodb-pipeline-ts/Task";
2
- import DIE from "@snomiao/die";
3
- import console from "console";
4
- import { peekYaml } from "peek-log";
5
- import { snoflow } from "snoflow";
6
- import { Authors, GithubUsers } from "./Authors";
7
- import { $stale } from "./db";
8
- import { gh } from "./gh";
9
-
10
- export async function updateAuthorsForGithub() {
11
- await snoflow(Authors.find({ githubMtime: $stale("7d") }))
12
- .map((e) => e.githubId)
13
- .filter()
14
- .pMap(2, async (username) => {
15
- const cached = await GithubUsers.findOne({ username, mtime: $stale("1d"), ...$OK });
16
- if (cached) return cached;
17
- console.log("Fetching github user " + username);
18
- const result = await gh.users
19
- .getByUsername({ username })
20
- .then((e) => e.data)
21
- .then(TaskOK)
22
- .catch(TaskError);
23
- return (
24
- (await GithubUsers.findOneAndUpdate(
25
- { username },
26
- { $set: result },
27
- { returnDocument: "after", upsert: true }
28
- )) ?? DIE(`fail to udpate gh users`)
29
- );
30
- })
31
- .map((e) => TaskDataOrNull(e))
32
- .filter()
33
- .map(({ email, avatar_url, blog, updated_at, location, company, hireable, bio, login }) => Authors.findOneAndUpdate(
34
- { githubId: login },
35
- {
36
- $set: { githubMtime: new Date(), ...(email && { email }), ...(null != hireable && { hireable }) },
37
- $addToSet: {
38
- ...(bio && { bios: bio }),
39
- avatars: avatar_url,
40
- ...(location && { locations: location }),
41
- ...(blog && { blogs: blog }),
42
- ...(company && { companies: company }),
43
- },
44
- },
45
- { upsert: true, returnDocument: "after" }
46
- )
47
- )
48
- .peek(peekYaml)
49
- .done();
50
- }
1
+ import { $OK, TaskDataOrNull, TaskError, TaskOK } from "@/packages/mongodb-pipeline-ts/Task";
2
+ import DIE from "@snomiao/die";
3
+ import console from "console";
4
+ import { peekYaml } from "peek-log";
5
+ import { snoflow } from "snoflow";
6
+ import { Authors, GithubUsers } from "./Authors";
7
+ import { $stale } from "./db";
8
+ import { gh } from "./gh";
9
+
10
+ if (import.meta.main) {
11
+ await updateAuthorsForGithub();
12
+ }
13
+ export async function updateAuthorsForGithub() {
14
+ await snoflow(Authors.find({ githubMtime: $stale("7d") }))
15
+ .map((e) => e.githubId)
16
+ .filter()
17
+ .pMap(2, async (username) => {
18
+ const cached = await GithubUsers.findOne({ username, mtime: $stale("1d"), ...$OK });
19
+ if (cached) return cached;
20
+ console.log("Fetching github user " + username);
21
+ const result = await gh.users
22
+ .getByUsername({ username })
23
+ .then((e) => e.data)
24
+ .then(TaskOK)
25
+ .catch(TaskError);
26
+ return (
27
+ (await GithubUsers.findOneAndUpdate(
28
+ { username },
29
+ { $set: result },
30
+ { returnDocument: "after", upsert: true },
31
+ )) ?? DIE(`fail to udpate gh users`)
32
+ );
33
+ })
34
+ .map((e) => TaskDataOrNull(e))
35
+ .filter()
36
+ .map(({ email, avatar_url, blog, updated_at, location, company, hireable, bio, login }) =>
37
+ Authors.findOneAndUpdate(
38
+ { githubId: login },
39
+ {
40
+ $set: { githubMtime: new Date(), ...(email && { email }), ...(null != hireable && { hireable }) },
41
+ $addToSet: {
42
+ ...(bio && { bios: bio }),
43
+ avatars: avatar_url,
44
+ ...(location && { locations: location }),
45
+ ...(blog && { blogs: blog }),
46
+ ...(company && { companies: company }),
47
+ },
48
+ },
49
+ { upsert: true, returnDocument: "after" },
50
+ ),
51
+ )
52
+ .peek(peekYaml)
53
+ .done();
54
+ }
@@ -1,30 +1,30 @@
1
- import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
- import { snoflow } from "snoflow";
3
- import { Authors } from "./Authors";
4
- import { CNRepos } from "./CNRepos";
5
- import { $filaten } from "./db";
6
-
7
- /** Update authors for gh users, collecting emails/username/hireable */
8
- export async function updateAuthorsFromCNRepo() {
9
- return await snoflow(
10
- $pipeline(CNRepos)
11
- .match($filaten({ info: { data: { owner: { login: { $exists: true } } } } }))
12
- .group({
13
- _id: "$info.data.owner.login",
14
- // author: "$info.data.owner.login",
15
- cm: { $sum: { $cond: [{ $eq: [{ $type: "$cm" }, "missing"] }, 0, 1] } },
16
- cr: { $sum: { $cond: [{ $eq: [{ $type: "$cr" }, "missing"] }, 0, 1] } },
17
-
18
- // TODO: get totals open/closed/merged
19
- // pulls:{
20
- // OPEN: {"$crPulls.data.pull.prState", "open"}
21
- // }
22
- All: { $sum: 1 },
23
- })
24
- .set({ login: "$_id" })
25
- .project({ _id: 0 })
26
- .aggregate()
27
- )
28
- .peek(({ login, ...$set }) => Authors.updateOne({ login }, { $set }, { upsert: true }))
29
- .done();
30
- }
1
+ import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
+ import { snoflow } from "snoflow";
3
+ import { Authors } from "./Authors";
4
+ import { CNRepos } from "./CNRepos";
5
+ import { $filaten } from "./db";
6
+
7
+ /** Update authors for gh users, collecting emails/username/hireable */
8
+ export async function updateAuthorsFromCNRepo() {
9
+ return await snoflow(
10
+ $pipeline(CNRepos)
11
+ .match($filaten({ info: { data: { owner: { login: { $exists: true } } } } }))
12
+ .group({
13
+ _id: "$info.data.owner.login",
14
+ // author: "$info.data.owner.login",
15
+ cm: { $sum: { $cond: [{ $eq: [{ $type: "$cm" }, "missing"] }, 0, 1] } },
16
+ cr: { $sum: { $cond: [{ $eq: [{ $type: "$cr" }, "missing"] }, 0, 1] } },
17
+
18
+ // TODO: get totals open/closed/merged
19
+ // pulls:{
20
+ // OPEN: {"$crPulls.data.pull.prState", "open"}
21
+ // }
22
+ All: { $sum: 1 },
23
+ })
24
+ .set({ githubId: "$_id" })
25
+ .project({ _id: 0 })
26
+ .aggregate(),
27
+ )
28
+ .map(({ githubId, ...$set }) => Authors.updateOne({ githubId }, { $set }, { upsert: true }))
29
+ .done();
30
+ }
@@ -1,56 +1,56 @@
1
- import { unary } from "lodash-es";
2
- import pMap from "p-map";
3
- import { dissoc, filter, groupBy, map, prop, toPairs } from "rambda";
4
- import YAML from "yaml";
5
- import { CMNodes, type CMNode } from "./CMNodes";
6
- import { notifySlack } from "./slack/notifySlack";
7
-
8
- export async function updateCMNodesDuplicationWarnings(nodes: CMNode[]) {
9
- console.log("CMNodes checking duplicates");
10
- // prettier-ignore
11
- const dups = {
12
- ID: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.id, nodes)),
13
- TITLE: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.title, nodes)),
14
- REFERENCE: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.reference, nodes)),
15
- };
16
- const dupsSummary = JSON.stringify(map((x) => map((x) => x.length, x), dups));
17
- await notifySlack(
18
- `[WARN] CMNodes duplicates: ${dupsSummary}\nSolve them in https://github.com/ltdrdata/ComfyUI-Manager/blob/main/custom-node-list.json`,
19
- );
20
-
21
- await pMap(
22
- toPairs(dups),
23
- async ([topic, nodes]) =>
24
- await pMap(
25
- toPairs(nodes),
26
- async ([key, nodesRaw]) => {
27
- const nodes = nodesRaw.map(unary(dissoc("hash")));
28
- const hashes = nodesRaw.map(prop("hash"));
29
- // check sent
30
- const someDuplicateSent = await CMNodes.findOne({
31
- hash: { $in: hashes },
32
- [`duplicated.${topic}`]: { $exists: true },
33
- });
34
- if (someDuplicateSent) return;
35
- // send slack notification
36
- const slackNotification = await notifySlack(
37
- `[ACTION NEEDED WARNING]: please resolve duplicated node in ${topic}: ${key}\n` +
38
- "```\n" +
39
- YAML.stringify(nodes) +
40
- "```" +
41
- "\n\nSolve them in https://github.com/ltdrdata/ComfyUI-Manager/blob/main/custom-node-list.json",
42
- { unique: true },
43
- );
44
- // mark duplicates
45
- await CMNodes.updateMany(
46
- { hash: { $in: hashes } },
47
- {
48
- $set: { [`duplicated.${topic}`]: { hashes, slackNotification } },
49
- },
50
- );
51
- },
52
- { concurrency: 2 },
53
- ),
54
- { concurrency: 2 },
55
- );
56
- }
1
+ import { unary } from "lodash-es";
2
+ import pMap from "p-map";
3
+ import { dissoc, filter, groupBy, map, prop, toPairs } from "rambda";
4
+ import YAML from "yaml";
5
+ import { CMNodes, type CMNode } from "./CMNodes";
6
+ import { notifySlack } from "./slack/notifySlack";
7
+
8
+ export async function updateCMNodesDuplicationWarnings(nodes: CMNode[]) {
9
+ console.log("CMNodes checking duplicates");
10
+ // prettier-ignore
11
+ const dups = {
12
+ ID: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.id, nodes)),
13
+ TITLE: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.title, nodes)),
14
+ REFERENCE: filter((e: typeof nodes) => e.length > 1, groupBy((e) => e.reference, nodes)),
15
+ };
16
+ const dupsSummary = JSON.stringify(map((x) => map((x) => x.length, x), dups));
17
+ await notifySlack(
18
+ `[WARN] CMNodes duplicates: ${dupsSummary}\nSolve them in https://github.com/ltdrdata/ComfyUI-Manager/blob/main/custom-node-list.json`,
19
+ );
20
+
21
+ await pMap(
22
+ toPairs(dups),
23
+ async ([topic, nodes]) =>
24
+ await pMap(
25
+ toPairs(nodes),
26
+ async ([key, nodesRaw]) => {
27
+ const nodes = nodesRaw.map(unary(dissoc("hash")));
28
+ const hashes = nodesRaw.map(prop("hash"));
29
+ // check sent
30
+ const someDuplicateSent = await CMNodes.findOne({
31
+ hash: { $in: hashes },
32
+ [`duplicated.${topic}`]: { $exists: true },
33
+ });
34
+ if (someDuplicateSent) return;
35
+ // send slack notification
36
+ const slackNotification = await notifySlack(
37
+ `[ACTION NEEDED WARNING]: please resolve duplicated node in ${topic}: ${key}\n` +
38
+ "```\n" +
39
+ YAML.stringify(nodes) +
40
+ "```" +
41
+ "\n\nSolve them in https://github.com/ltdrdata/ComfyUI-Manager/blob/main/custom-node-list.json",
42
+ { unique: true },
43
+ );
44
+ // mark duplicates
45
+ await CMNodes.updateMany(
46
+ { hash: { $in: hashes } },
47
+ {
48
+ $set: { [`duplicated.${topic}`]: { hashes, slackNotification } },
49
+ },
50
+ );
51
+ },
52
+ { concurrency: 2 },
53
+ ),
54
+ { concurrency: 2 },
55
+ );
56
+ }
@@ -1,27 +1,27 @@
1
- import pMap from "p-map";
2
- import { CMNodes } from "./CMNodes";
3
- import { CNRepos } from "./CNRepos";
4
- import { tLog } from "./utils/tLog";
5
- if (import.meta.main) {
6
- await tLog("Update Repos from ComfyUI Manager", updateCMRepos);
7
- console.log(await CMNodes.estimatedDocumentCount());
8
- }
9
- export async function updateCMRepos() {
10
- await CMNodes.createIndex({ repo_id: 1 });
11
- return await pMap(CMNodes.find({ repo_id: { $exists: false } }), async (cm, i) => {
12
- const { reference: repository, _id } = cm;
13
- return await CNRepos.updateOne(
14
- { repository },
15
- {
16
- $set: {
17
- cm: (await CMNodes.findOneAndUpdate(
18
- { _id },
19
- { $set: { repo_id: cm._id } },
20
- { upsert: true, returnDocument: "after" },
21
- ))!,
22
- },
23
- },
24
- { upsert: true },
25
- );
26
- });
27
- }
1
+ import pMap from "p-map";
2
+ import { CMNodes } from "./CMNodes";
3
+ import { CNRepos } from "./CNRepos";
4
+ import { tLog } from "./utils/tLog";
5
+ if (import.meta.main) {
6
+ await tLog("Update Repos from ComfyUI Manager", updateCMRepos);
7
+ console.log(await CMNodes.estimatedDocumentCount());
8
+ }
9
+ export async function updateCMRepos() {
10
+ await CMNodes.createIndex({ repo_id: 1 });
11
+ return await pMap(CMNodes.find({ repo_id: { $exists: false } }), async (cm, i) => {
12
+ const { reference: repository, _id } = cm;
13
+ return await CNRepos.updateOne(
14
+ { repository },
15
+ {
16
+ $set: {
17
+ cm: (await CMNodes.findOneAndUpdate(
18
+ { _id },
19
+ { $set: { repo_id: cm._id } },
20
+ { upsert: true, returnDocument: "after" },
21
+ ))!,
22
+ },
23
+ },
24
+ { upsert: true },
25
+ );
26
+ });
27
+ }