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/CNRepos.ts CHANGED
@@ -4,15 +4,14 @@ bun index.ts
4
4
  */
5
5
  import type { ObjectId, WithId } from "mongodb";
6
6
  import "react-hook-form";
7
- import { nil } from "sflow";
8
7
  import { type Task } from "../packages/mongodb-pipeline-ts/Task";
9
8
  import { type CMNode } from "./CMNodes";
10
9
  import { type CRNode } from "./CRNodes";
11
10
  import { db } from "./db";
12
- import { gh } from "./gh";
11
+ import { gh } from "@/lib/github";
13
12
  import { type RelatedPull } from "./matchRelatedPulls";
14
13
  import type { GithubPullParsed } from "./parsePullsState";
15
- import { type SlackMsg } from "./slack/SlackMsgs";
14
+ import { type SlackMsg } from "@/lib/slack/SlackMsgs";
16
15
 
17
16
  type Email = {
18
17
  from?: string;
@@ -21,7 +20,7 @@ type Email = {
21
20
  to: string;
22
21
  };
23
22
 
24
- type Sent = {
23
+ type _Sent = {
25
24
  slack?: SlackMsg;
26
25
  emails?: Email[];
27
26
  };
@@ -39,7 +38,9 @@ export type CRPull = RelatedPull & {
39
38
  };
40
39
  // TODO: refactor into interface to improve performance
41
40
  export type CustomNodeRepo = {
41
+ // github url
42
42
  repository: string;
43
+
43
44
  info?: Task<Pick<GithubRepo, "html_url" | "archived" | "default_branch" | "private">>;
44
45
 
45
46
  // author?: Author;
@@ -48,7 +49,9 @@ export type CustomNodeRepo = {
48
49
  cr?: Pick<WithId<CRNode>, "_id" | "id" | "name">;
49
50
 
50
51
  cr_ids?: ObjectId[];
51
- on_registry?: Task<boolean>; // check if cr_ids is not empty
52
+
53
+ on_registry?: Task<boolean>; // check if cr_ids is not empty, exclude publisher.id === ADMIN
54
+ on_registry_all?: Task<boolean>; // check if cr_ids is not empty, including publisher.id === ADMIN
52
55
 
53
56
  /** @deprecated use cm_ids */
54
57
  cm?: Pick<WithId<CMNode>, "_id" | "id" | "title">;
@@ -66,13 +69,33 @@ export type CustomNodeRepo = {
66
69
  // createFork?: Task<GithubRepo>;
67
70
  // createBranches?: Task<{ type: CRType; assigned: Worker } & PushedBranch>[];
68
71
 
69
- /** @deprecated use CRPulls.pull */
72
+ /** TODO: @deprecated use CRPulls.pull */
70
73
  createdPulls?: Task<GithubPullParsed[]>;
71
74
  };
72
75
  export type CNRepo = CustomNodeRepo;
73
76
  export const CNRepos = db.collection<CNRepo>("CNRepos");
74
77
 
75
- CNRepos.createIndex({ repository: 1 }, { unique: true }).catch(nil);
78
+ CNRepos.createIndex({ repository: 1 }, { unique: true }).catch(() => {});
79
+
80
+ // Performance optimization: compound index for complex state + mtime queries
81
+ // This index improves queries filtering by multiple state and mtime fields
82
+ // Target query: aggregate with $match on crPulls.state, info.state, and various mtime fields
83
+ CNRepos.createIndex(
84
+ {
85
+ "crPulls.state": 1,
86
+ "info.state": 1,
87
+ "crPulls.mtime": 1,
88
+ "info.mtime": 1,
89
+ "candidate.mtime": 1,
90
+ },
91
+ {
92
+ name: "idx_states_mtimes",
93
+ background: true,
94
+ },
95
+ ).catch(() => {});
96
+
97
+ // Note: pulls.mtime index is created by setup-performance-indexes.ts script
98
+ // Run: bun run db:setup-indexes
76
99
 
77
100
  // fix cr null, it should be not exists
78
101
  // await CNRepos.updateMany({ cr: null as unknown as WithId<CRNode> }, { $unset: { cr: 1 } });
package/src/CRNodes.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
2
  import type { ObjectId } from "mongodb";
3
- import pMap from "p-map";
4
3
  import { peekYaml } from "peek-log";
4
+ import { sf } from "sflow";
5
5
  import { CNRepos } from "./CNRepos";
6
6
  import { db } from "./db";
7
7
  import { fetchCRNodes } from "./fetchComfyRegistryNodes";
8
- import { type SlackMsg } from "./slack/SlackMsgs";
8
+ import { type SlackMsg } from "@/lib/slack/SlackMsgs";
9
9
 
10
10
  export type CRNode = Awaited<ReturnType<typeof fetchCRNodes>>[number] & {
11
11
  sent?: { slack?: SlackMsg };
@@ -16,12 +16,14 @@ await CRNodes.createIndex({ id: 1 }, { unique: true });
16
16
  await CRNodes.createIndex({ repository: 1 }, { unique: false }); // WARN: duplicate is allowed
17
17
 
18
18
  if (import.meta.main) {
19
- const r = await pMap(
20
- $pipeline(CNRepos)
21
- .match({ cr: { $exists: true } })
22
- .replaceRoot({ newRoot: "$cr" })
23
- .aggregate(),
24
- (e) => e,
25
- );
19
+ // peek cr nodes
20
+ const r = await sf
21
+ .sflow(
22
+ $pipeline(CNRepos)
23
+ .match({ cr: { $exists: true } })
24
+ .replaceRoot({ newRoot: "$cr" })
25
+ .aggregate(),
26
+ )
27
+ .toArray();
26
28
  peekYaml({ r, len: r.length });
27
29
  }
package/src/CRPulls.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { CRPull } from "./CNRepos";
2
2
  import { db } from "./db";
3
3
 
4
+ /**
5
+ * @deprecated todo: utillize this collection @sno
6
+ */
4
7
  export const CRPulls = db.collection<CRPull>("CRPulls");
package/src/EmailTasks.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import DIE from "@snomiao/die";
2
2
  import markdownIt from "markdown-it";
3
3
  import type { WithId } from "mongodb";
4
- import { sf } from "snoflow";
4
+ import { sf } from "sflow";
5
5
  import type { z } from "zod";
6
6
  import { $stale, db } from "./db";
7
7
  import type { zSendEmailAction } from "./followRuleSchema";
@@ -25,10 +25,10 @@ export const EmailTasks = db.collection<EmailTask>("EmailTasks");
25
25
  await EmailTasks.createIndex({ from: 1, to: 1, subject: 1 });
26
26
 
27
27
  if (import.meta.main) {
28
- sf(EmailTasks.find({}))
28
+ sf.sflow(EmailTasks.find({}))
29
29
  .map((e) => e.state)
30
30
  .toLog();
31
- // sf(EmailTasks.watch([], { fullDocument: "whenAvailable" }))
31
+ // sf.sflow(EmailTasks.watch([], { fullDocument: "whenAvailable" }))
32
32
  // // .filter((c) => c.operationType === "modify")
33
33
  // .toLog()
34
34
  // .then(() => console.log("all done"));
@@ -54,7 +54,7 @@ if (import.meta.main) {
54
54
  * deduplicated by [from, to, subject].join(' ')
55
55
  */
56
56
  export async function enqueueEmailTask(task: z.infer<typeof zSendEmailAction>) {
57
- const { name, from, to, subject, body, provider } = task;
57
+ const { name: _name, from, to, subject, body: _body, provider: _provider } = task;
58
58
  return (
59
59
  (await EmailTasks.findOneAndUpdate(
60
60
  { from, to, subject },
@@ -64,8 +64,9 @@ export async function enqueueEmailTask(task: z.infer<typeof zSendEmailAction>) {
64
64
  );
65
65
  }
66
66
  export async function updateEmailTasks() {
67
- const count = await sf(EmailTasks.find({ state: "waiting" }))
68
- .join(sf(EmailTasks.find({ state: "sending", mtime: $stale("1m") })))
67
+ const count = await sf
68
+ .sflow(EmailTasks.find({ state: "waiting" }))
69
+ .merge(sf.sflow(EmailTasks.find({ state: "sending", mtime: $stale("1m") })))
69
70
  .map(async (e) => {
70
71
  const { _id, state } = e;
71
72
  if (state === "waiting") {
@@ -73,21 +74,38 @@ export async function updateEmailTasks() {
73
74
  return await sendEmailTask(e)
74
75
  .then(() => EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "sent" } }))
75
76
  .catch((err) =>
76
- EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "error", error: String(err) } }),
77
+ EmailTasks.updateOne(
78
+ { _id },
79
+ { $set: { mtime: new Date(), state: "error", error: String(err) } },
80
+ ),
77
81
  );
78
82
  }
79
83
  })
80
84
  .toCount();
81
85
  console.log(count, "email tasks processed");
82
86
  }
83
- export async function sendEmailTask({ _id, state, name, from, to, subject, body, provider }: WithId<EmailTask>) {
87
+ export async function sendEmailTask({
88
+ _id,
89
+ state: _state,
90
+ name,
91
+ from,
92
+ to,
93
+ subject,
94
+ body,
95
+ provider,
96
+ }: WithId<EmailTask>) {
84
97
  // prerequisites
85
98
  if (provider !== "google") DIE("providers other than google is implemented yet");
86
99
  const auth = await getGCloudOAuth2Client({
87
100
  email: from,
88
101
  scope: ["https://www.googleapis.com/auth/gmail.compose"],
89
102
  authorize: async (url) =>
90
- DIE("ERROR: Trying send email from " + from + " but it's not authorized, plz grant permission in " + url),
103
+ DIE(
104
+ "ERROR: Trying send email from " +
105
+ from +
106
+ " but it's not authorized, plz grant permission in " +
107
+ url,
108
+ ),
91
109
  });
92
110
  // do send
93
111
  return sendGmail({
package/src/FORK_OWNER.ts CHANGED
@@ -2,4 +2,6 @@ import DIE from "@snomiao/die";
2
2
  import { ghUser } from "./ghUser";
3
3
 
4
4
  export const FORK_OWNER =
5
- process.env.FORK_OWNER?.replace(/"/g, "")?.trim() || ghUser.login || DIE("Missing env.FORK_OWNER");
5
+ process.env.FORK_OWNER?.replace(/"/g, "")?.trim() ||
6
+ (await ghUser()).login ||
7
+ DIE("Missing env.FORK_OWNER");
@@ -15,11 +15,11 @@ export type FollowRuleSet = {
15
15
  matched?: Task<PullsStatus>;
16
16
  action_results?: {
17
17
  name: string;
18
- action: any;
19
- result: Task<any>;
18
+ action: unknown;
19
+ result: Task<unknown>;
20
20
  }[];
21
21
  enabled?: boolean;
22
22
  yamlWhenEnabled?: string;
23
23
  };
24
24
  export const FollowRuleSets = db.collection<FollowRuleSet>("FollowRuleSets");
25
- await FollowRuleSets.createIndex("name", { unique: true });
25
+ FollowRuleSets.createIndex("name", { unique: true }).catch(() => null);
@@ -1,3 +1,3 @@
1
- import { fetchIssueComments } from "./gh/fetchIssueComments";
1
+ import { fetchIssueComments } from "@/lib/github/fetchIssueComments";
2
2
 
3
3
  export type GithubIssueComment = Awaited<ReturnType<typeof fetchIssueComments>>[number];
package/src/Totals.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
2
  import { flatten } from "flat";
3
- import { sf } from "snoflow";
3
+ import { sf } from "sflow";
4
4
  import { type Task } from "../packages/mongodb-pipeline-ts/Task";
5
5
  import { analyzeTotals } from "./analyzeTotals";
6
6
  import { db } from "./db";
@@ -12,13 +12,14 @@ export const Totals = db.collection<{
12
12
  totals?: Task<Totals>;
13
13
  }>("Totals");
14
14
  if (import.meta.main) {
15
- sf(
16
- $pipeline(Totals)
17
- .match({ "totals.data": { $exists: true } })
18
- .set({ "totals.data.date": "$totals.mtime" })
19
- .replaceRoot({ newRoot: "$totals.data" })
20
- .aggregate(),
21
- )
15
+ await sf
16
+ .sflow(
17
+ $pipeline(Totals)
18
+ .match({ "totals.data": { $exists: true } })
19
+ .set({ "totals.data.date": "$totals.mtime" })
20
+ .replaceRoot({ newRoot: "$totals.data" })
21
+ .aggregate(),
22
+ )
22
23
  .map((e) => flatten(e) as { date: string } & Record<string, number>)
23
24
  .toLog();
24
25
  }
@@ -2,9 +2,12 @@ import { getMAC } from "@ctrl/mac-address";
2
2
  import { defer } from "lodash-es";
3
3
  import md5 from "md5";
4
4
  import type { WithId } from "mongodb";
5
- import { db } from "./db";
5
+ import sflow from "sflow";
6
+ import { $fresh, db } from "./db";
6
7
  import { fetchCurrentGeoInfo } from "./fetchCurrentGeoInfo";
7
8
  import { createInstanceId } from "./utils/createInstanceId";
9
+ import { yaml } from "./utils/yaml";
10
+
8
11
  export type GeoInfo = Awaited<ReturnType<typeof fetchCurrentGeoInfo>>;
9
12
  export type WorkerInstance = {
10
13
  /** id: rand */
@@ -14,26 +17,43 @@ export type WorkerInstance = {
14
17
  geo: GeoInfo;
15
18
  workerId: string;
16
19
  task?: string;
20
+ tasks?: string[];
17
21
  };
18
22
 
19
23
  const k = "COMFY_PR_WorkerInstanceKey";
20
- type g = typeof globalThis & { [k]: any };
24
+ const g = globalThis as typeof globalThis & { [k]: string };
21
25
  function getWorkerInstanceId() {
22
26
  // ensure only one instance
23
- if (!(global as any as g)[k])
27
+ if (!g[k])
24
28
  defer(async function () {
25
29
  await Promise.all([postWorkerHeartBeatLoop(), watchWorkerInstancesLoop()]);
26
30
  });
27
- const instanceId = ((global as any as g)[k] ??= createInstanceId());
31
+ const instanceId = (g[k] ??= createInstanceId());
28
32
  return instanceId;
29
33
  }
30
34
  export const WorkerInstances = db.collection<WorkerInstance>("WorkerInstances");
31
- await WorkerInstances.createIndex({ id: 1 }, { unique: true });
32
- await WorkerInstances.createIndex({ ip: 1 });
33
- export const _geoPromise = fetchCurrentGeoInfo(); // in background
35
+ let _geoPromise: Promise<GeoInfo> | undefined;
36
+ function getGeoPromise() {
37
+ if (!_geoPromise) {
38
+ _geoPromise = fetchCurrentGeoInfo();
39
+ }
40
+ return _geoPromise;
41
+ }
34
42
 
35
43
  if (import.meta.main) {
44
+ await WorkerInstances.createIndex({ id: 1 }, { unique: true });
45
+ await WorkerInstances.createIndex({ ip: 1 });
36
46
  console.log(await getWorkerInstance());
47
+ console.log(
48
+ await sflow(
49
+ WorkerInstances.watch([{ $match: { up: $fresh("5min") } }], {
50
+ fullDocument: "whenAvailable",
51
+ }),
52
+ )
53
+ .map((e) => yaml.stringify(e))
54
+ .log()
55
+ .run(),
56
+ );
37
57
  }
38
58
 
39
59
  async function postWorkerHeartBeatLoop() {
@@ -75,9 +95,12 @@ export async function getWorkerInstance(task?: string) {
75
95
  id,
76
96
  active: new Date(),
77
97
  workerId: getWorkerId(),
78
- geo: await _geoPromise,
98
+ geo: await getGeoPromise(),
79
99
  ...(task && { task }),
80
100
  },
101
+ $addToSet: {
102
+ ...(task && { tasks: task }),
103
+ },
81
104
  $setOnInsert: { up: new Date() },
82
105
  },
83
106
  { upsert: true, returnDocument: "after" },
@@ -8,11 +8,11 @@ import type { z } from "zod";
8
8
  import type { PullStatusShown } from "./analyzePullsStatus";
9
9
  import { CNRepos, type CRPull } from "./CNRepos";
10
10
  import { createIssueComment } from "./createIssueComment";
11
- import { $filaten } from "./db";
11
+ import { $flatten } from "./db";
12
12
  import type { zAddCommentAction } from "./followRuleSchema";
13
13
  import { ghUser } from "./ghUser";
14
14
  import type { GithubIssueComment } from "./GithubIssueComments";
15
- import { notifySlackLinks } from "./slack/notifySlackLinks";
15
+ import { notifySlackLinks } from "@/lib/slack/notifySlackLinks";
16
16
 
17
17
  export async function addCommentAction({
18
18
  matched,
@@ -32,14 +32,15 @@ export async function addCommentAction({
32
32
  action: "add-comment",
33
33
  url: payload.url,
34
34
  by: action.by,
35
- body: action.body.replace(
36
- /{{\$([_A-Za-z0-9]+)}}/g,
37
- (_, key: string) =>
38
- (payload as any)[key] || DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
35
+ body: action.body.replace(/{{\$([_A-Za-z0-9]+)}}/g, (_, key: string) =>
36
+ String(
37
+ (payload as Record<string, unknown>)[key] ??
38
+ DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
39
+ ),
39
40
  ),
40
41
  };
41
42
 
42
- if (runAction && loadedAction.by === ghUser.login) {
43
+ if (runAction && loadedAction.by === (await ghUser()).login) {
43
44
  const existedCommentsTask =
44
45
  (await $pipeline(CNRepos)
45
46
  .unwind("$crPulls.data")
@@ -48,7 +49,8 @@ export async function addCommentAction({
48
49
  .replaceRoot({ newRoot: "$crPulls.data.comments" })
49
50
  .as<Task<GithubIssueComment[]>>()
50
51
  .aggregate()
51
- .next()) ?? DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
52
+ .next()) ??
53
+ DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
52
54
 
53
55
  const existedComments =
54
56
  TaskDataOrNull(existedCommentsTask) ??
@@ -56,13 +58,19 @@ export async function addCommentAction({
56
58
  const existedComment = existedComments.find((e) => e.body === loadedAction.body);
57
59
 
58
60
  if (!existedComment) {
59
- const { comments, comment } = await createIssueComment(loadedAction.url, loadedAction.body, loadedAction.by);
61
+ const { comments, comment } = await createIssueComment(
62
+ loadedAction.url,
63
+ loadedAction.body,
64
+ loadedAction.by,
65
+ );
60
66
  const updateResult = await CNRepos.updateOne(
61
- $filaten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
67
+ $flatten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
62
68
  { $set: { "crPulls.data.$.comments": comments } },
63
69
  );
64
70
  if (!updateResult.matchedCount) DIE("created issue not matched");
65
- await notifySlackLinks("A New issue comment are created from rule " + rule.name, [comment.html_url]);
71
+ await notifySlackLinks("A New issue comment are created from rule " + rule.name, [
72
+ comment.html_url,
73
+ ]);
66
74
  }
67
75
  }
68
76
 
@@ -1,7 +1,7 @@
1
1
  import { $pipeline, type Pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
2
  import type { ObjectId } from "mongodb";
3
3
  import prettyMs from "pretty-ms";
4
- import { snoflow } from "snoflow";
4
+ import { sflow } from "sflow";
5
5
  import type { z } from "zod";
6
6
  import type { Task } from "../packages/mongodb-pipeline-ts/Task";
7
7
  import type { Author } from "./Authors";
@@ -14,7 +14,7 @@ import type { zPullStatus } from "./zod/zPullsStatus";
14
14
  // import { $pipeline } from "./db/$pipeline";
15
15
  // in case of dump production in local environment:
16
16
  // bun --env-file .env.production.local src/dump.ts > dump.csv
17
- export const DashboardDetails = db.collection<any>("DashboardDetails");
17
+ export const DashboardDetails = db.collection<Record<string, unknown>>("DashboardDetails");
18
18
  if (import.meta.main) {
19
19
  // const r = peekYaml(await analyzePullsStatus());
20
20
 
@@ -26,7 +26,7 @@ if (import.meta.main) {
26
26
  // await writeFile("src/zPullsStatus.ts", jsonToZod(await analyzePullsStatus({ limit: 1 }), "zPullsStatus", true));
27
27
 
28
28
  // analyzePullsStatusPipeline
29
- await snoflow(analyzePullsStatusPipeline().aggregate())
29
+ await sflow(analyzePullsStatusPipeline().aggregate())
30
30
  .filter((e) => e.email)
31
31
  .limit(2)
32
32
  .map((e) => yaml.stringify({ e }))
@@ -40,23 +40,37 @@ export type PullStatus = z.infer<typeof zPullStatus>;
40
40
  export type PullsStatus = PullStatus[];
41
41
  export type PullStatusShown = Awaited<ReturnType<typeof analyzePullsStatus>>[number];
42
42
 
43
- export async function analyzePullsStatus({ skip = 0, limit = 0, pipeline = analyzePullsStatusPipeline() } = {}) {
43
+ export async function analyzePullsStatus({
44
+ skip = 0,
45
+ limit = 0,
46
+ pipeline = analyzePullsStatusPipeline(),
47
+ } = {}) {
44
48
  "use server";
45
49
  return await pipeline
46
50
  .skip(skip)
47
51
  .limit(limit || 2 ** 31 - 1)
48
52
  .aggregate()
49
- .map(({ updated_at, created_at, actived_at, on_registry_at, ...pull }) => {
50
- const pull_updated = prettyMs(+new Date() - +new Date(updated_at), { compact: true }) + " ago";
51
- const repo_updated = prettyMs(+new Date() - +new Date(actived_at), { compact: true }) + " ago";
52
- return {
53
- updated: pull_updated, // @deprecated
54
- pull_updated,
55
- repo_updated,
56
- ...pull,
57
- lastcomment: pull.lastcomment?.replace(/\s+/g, " ").replace(/\*\*\*.*/g, "..."),
58
- };
59
- })
53
+ .map(
54
+ ({
55
+ updated_at,
56
+ created_at: _created_at,
57
+ actived_at,
58
+ on_registry_at: _on_registry_at,
59
+ ...pull
60
+ }) => {
61
+ const pull_updated =
62
+ prettyMs(+new Date() - +new Date(updated_at), { compact: true }) + " ago";
63
+ const repo_updated =
64
+ prettyMs(+new Date() - +new Date(actived_at), { compact: true }) + " ago";
65
+ return {
66
+ updated: pull_updated, // @deprecated
67
+ pull_updated,
68
+ repo_updated,
69
+ ...pull,
70
+ lastcomment: pull.lastcomment?.replace(/\s+/g, " ").replace(/\*\*\*.*/g, "..."),
71
+ };
72
+ },
73
+ )
60
74
  .toArray();
61
75
  }
62
76
  export function baseCRPullStatusPipeline(): Pipeline<
@@ -71,7 +85,11 @@ export function baseCRPullStatusPipeline(): Pipeline<
71
85
  return (
72
86
  $pipeline(CNRepos)
73
87
  // get latest pr comments time
74
- .set({ "crPulls.data.pull.latest_comment_at": { $max: { $max: "$crPulls.data.comments.data.updated_at" } } })
88
+ .set({
89
+ "crPulls.data.pull.latest_comment_at": {
90
+ $max: { $max: "$crPulls.data.comments.data.updated_at" },
91
+ },
92
+ })
75
93
  // unwind
76
94
  .stage({ $unwind: "$crPulls.data" })
77
95
  .match({ "crPulls.data.comments.data": { $exists: true } })
@@ -145,7 +163,11 @@ export function analyzePullsStatusPipeline(): Pipeline<{
145
163
  .project({ authors: 0 })
146
164
 
147
165
  .set({ lastcomment: { $arrayElemAt: ["$comments", -1] } })
148
- .set({ lastcomment: { $ifNull: [{ $concat: ["$lastcomment.user.login", ": ", "$lastcomment.body"] }, ""] } })
166
+ .set({
167
+ lastcomment: {
168
+ $ifNull: [{ $concat: ["$lastcomment.user.login", ": ", "$lastcomment.body"] }, ""],
169
+ },
170
+ })
149
171
 
150
172
  // calculate update_at max( )
151
173
  // date format convert
@@ -4,8 +4,9 @@ import promiseAllProperties from "promise-all-properties";
4
4
  import YAML from "yaml";
5
5
  import { CMNodes } from "./CMNodes";
6
6
  import { CNRepos } from "./CNRepos";
7
+ import { UNCLAIMED_ADMIN_PUBLISHER_ID } from "./constants";
7
8
  import { CRNodes } from "./CRNodes";
8
- import { $filaten } from "./db";
9
+ import { $flatten } from "./db";
9
10
  import { tLog } from "./utils/tLog";
10
11
 
11
12
  if (import.meta.main) {
@@ -24,23 +25,30 @@ export async function analyzeTotals() {
24
25
  "Total Nodes": promiseAllProperties({
25
26
  "on ComfyUI Manager": CMNodes.estimatedDocumentCount(),
26
27
  "on Registry": CRNodes.estimatedDocumentCount(),
28
+ "on Registry (exclude unclaimed)": CRNodes.countDocuments({
29
+ "publisher.id": { $ne: UNCLAIMED_ADMIN_PUBLISHER_ID },
30
+ } as Record<string, unknown>),
27
31
  }),
28
32
  "Total Repos": $pipeline(CNRepos)
29
33
  .group({
30
34
  _id: null,
31
- "on Comfy Manager List": { $sum: { $cond: [{ $eq: [{ $type: "$cm" }, "missing"] }, 0, 1] } },
35
+ "on Comfy Manager List": {
36
+ $sum: { $cond: [{ $eq: [{ $type: "$cm" }, "missing"] }, 0, 1] },
37
+ },
32
38
  "on Registry": { $sum: { $cond: [{ $eq: [{ $type: "$cr" }, "missing"] }, 0, 1] } },
33
39
  Archived: { $sum: { $cond: ["$info.data.archived", 1, 0] } },
34
40
  All: { $sum: 1 },
35
41
  // Candidates: { $sum: { $cond: ["$candidate.data", 1, 0] } },
36
- "Got ERROR on creating PR": { $sum: { $cond: [{ $eq: ["$createdPulls.state", "error"] }, 1, 0] } },
42
+ "Got ERROR on creating PR": {
43
+ $sum: { $cond: [{ $eq: ["$createdPulls.state", "error"] }, 1, 0] },
44
+ },
37
45
  })
38
46
  .project({ _id: 0 })
39
47
  .aggregate()
40
- // .map((e: any) => e.pairs)
48
+ // .map((e: unknown) => e.pairs)
41
49
  .next(),
42
50
  "Total Authors": $pipeline(CNRepos)
43
- .match($filaten({ info: { data: { owner: { login: { $exists: true } } } } }))
51
+ .match($flatten({ info: { data: { owner: { login: { $exists: true } } } } }))
44
52
  .group({
45
53
  _id: "$info.data.owner.login",
46
54
  // author: "$info.data.owner.login",
@@ -57,7 +65,7 @@ export async function analyzeTotals() {
57
65
  })
58
66
  .project({ _id: 0 })
59
67
  .aggregate()
60
- // .map((e: any) => e.pairs)
68
+ // .map((e: unknown) => e.pairs)
61
69
  .next(),
62
70
  "Total PRs Made": $pipeline(CNRepos)
63
71
  .unwind("$crPulls.data")
@@ -66,7 +74,7 @@ export async function analyzeTotals() {
66
74
  .set({ id_total: [["$_id", "$total"]] })
67
75
  .group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
68
76
  .aggregate()
69
- .map((e: any) => e.pairs)
77
+ .map((e: unknown) => (e as { pairs?: unknown }).pairs)
70
78
  .next(),
71
79
  "Total Open": $pipeline(CNRepos)
72
80
  .unwind("$crPulls.data")
@@ -76,7 +84,7 @@ export async function analyzeTotals() {
76
84
  .set({ id_total: [["$_id", "$total"]] })
77
85
  .group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
78
86
  .aggregate()
79
- .map((e: any) => e.pairs)
87
+ .map((e: unknown) => (e as { pairs?: unknown }).pairs)
80
88
  .next(),
81
89
  "Total Merged (on Registry)": $pipeline(CNRepos)
82
90
  .unwind("$crPulls.data")
@@ -86,7 +94,7 @@ export async function analyzeTotals() {
86
94
  .set({ id_total: [["$_id", "$total"]] })
87
95
  .group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
88
96
  .aggregate()
89
- .map((e: any) => e.pairs)
97
+ .map((e: unknown) => (e as { pairs?: unknown }).pairs)
90
98
  .next(),
91
99
  "Total Merged (not on Registry)": $pipeline(CNRepos)
92
100
  .unwind("$crPulls.data")
@@ -96,7 +104,7 @@ export async function analyzeTotals() {
96
104
  .set({ id_total: [["$_id", "$total"]] })
97
105
  .group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
98
106
  .aggregate()
99
- .map((e: any) => e.pairs)
107
+ .map((e: unknown) => (e as { pairs?: unknown }).pairs)
100
108
  .next(),
101
109
  "Total Closed": $pipeline(CNRepos)
102
110
  .unwind("$crPulls.data")
@@ -106,7 +114,7 @@ export async function analyzeTotals() {
106
114
  .set({ id_total: [["$_id", "$total"]] })
107
115
  .group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
108
116
  .aggregate()
109
- .map((e: any) => e.pairs)
117
+ .map((e: unknown) => (e as { pairs?: unknown }).pairs)
110
118
  .next(),
111
119
 
112
120
  // // Follow Rules
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { yaml } from "./utils/yaml";
3
3
 
4
- export const bypassRepos = z
5
- .string()
6
- .array()
7
- .parse(yaml.parse(await Bun.file("./bypass.yaml").text()).bypass_repos);
8
- export const isRepoBypassed = (repo: string) => bypassRepos.some((reg) => repo.match(reg));
4
+ export const { ignore_repos } = z
5
+ .object({
6
+ ignore_repos: z.string().array(),
7
+ })
8
+ .parse(yaml.parse(await Bun.file("./comfypr-ignore.yaml").text()));
9
+ export const isRepoBypassed = (repo: string) => ignore_repos.some((reg) => repo.match(reg));