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,3 +1,3 @@
1
- import { makePublishcrBranch } from "./makePublishBranch";
2
-
3
- export type PushedBranch = Awaited<ReturnType<typeof makePublishcrBranch>>;
1
+ import { makePublishcrBranch } from "./makePublishBranch";
2
+
3
+ export type PushedBranch = Awaited<ReturnType<typeof makePublishcrBranch>>;
package/src/Totals.ts CHANGED
@@ -1,9 +1,24 @@
1
- import { type Task } from "../packages/mongodb-pipeline-ts/Task";
2
- import { analyzeTotals } from "./analyzeTotals";
3
- import { db } from "./db";
4
- import { type AwaitedReturnType } from "./types/AwaitedReturnType";
5
- export type Totals = AwaitedReturnType<typeof analyzeTotals>;
6
- export const Totals = db.collection<{
7
- today?: string;
8
- totals?: Task<Totals>;
9
- }>("Totals");
1
+ import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
+ import { flatten } from "flat";
3
+ import { sf } from "snoflow";
4
+ import { type Task } from "../packages/mongodb-pipeline-ts/Task";
5
+ import { analyzeTotals } from "./analyzeTotals";
6
+ import { db } from "./db";
7
+ import { type AwaitedReturnType } from "./types/AwaitedReturnType";
8
+ export type Totals = AwaitedReturnType<typeof analyzeTotals>;
9
+ export const Totals = db.collection<{
10
+ /** @deprecated use totals.mtime */
11
+ today?: string;
12
+ totals?: Task<Totals>;
13
+ }>("Totals");
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
+ )
22
+ .map((e) => flatten(e) as { date: string } & Record<string, number>)
23
+ .toLog();
24
+ }
@@ -1,12 +1,12 @@
1
- import { db } from "./db";
2
-
3
- // 2) tracking the statuses of the PRs we've sent out (edited)
4
- // "status [open, closed, merged]",
5
- // "comments [int]",
6
- // "last active [date]",
7
- // "last updated [date]",
8
-
9
- export const TrackingPulls = db.collection("TrackingPulls");
10
- // bun --env-file .env.production.local src/dump.ts > dump.log
11
- if (import.meta.main) {
12
- }
1
+ import { db } from "./db";
2
+
3
+ // 2) tracking the statuses of the PRs we've sent out (edited)
4
+ // "status [open, closed, merged]",
5
+ // "comments [int]",
6
+ // "last active [date]",
7
+ // "last updated [date]",
8
+
9
+ export const TrackingPulls = db.collection("TrackingPulls");
10
+ // bun --env-file .env.production.local src/dump.ts > dump.log
11
+ if (import.meta.main) {
12
+ }
@@ -1,89 +1,89 @@
1
- import { getMAC } from "@ctrl/mac-address";
2
- import { defer } from "lodash-es";
3
- import md5 from "md5";
4
- import type { WithId } from "mongodb";
5
- import { db } from "./db";
6
- import { fetchCurrentGeoInfo } from "./fetchCurrentGeoInfo";
7
- import { createInstanceId } from "./utils/createInstanceId";
8
- export type GeoInfo = Awaited<ReturnType<typeof fetchCurrentGeoInfo>>;
9
- export type WorkerInstance = {
10
- /** id: rand */
11
- id: string;
12
- up: Date;
13
- active: Date;
14
- geo: GeoInfo;
15
- workerId: string;
16
- task?: string;
17
- };
18
-
19
- const k = "COMFY_PR_WorkerInstanceKey";
20
- type g = typeof globalThis & { [k]: any };
21
- function getWorkerInstanceId() {
22
- // ensure only one instance
23
- if (!(global as any as g)[k])
24
- defer(async function () {
25
- await Promise.all([postWorkerHeartBeatLoop(), watchWorkerInstancesLoop()]);
26
- });
27
- const instanceId = ((global as any as g)[k] ??= createInstanceId());
28
- return instanceId;
29
- }
30
- 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
34
-
35
- if (import.meta.main) {
36
- console.log(await getWorkerInstance());
37
- }
38
-
39
- async function postWorkerHeartBeatLoop() {
40
- // 30s heartbeat
41
- while (true) {
42
- await new Promise((r) => setTimeout(r, 30e3));
43
- await getWorkerInstance();
44
- }
45
- }
46
-
47
- async function watchWorkerInstancesLoop() {
48
- const me = await getWorkerInstance();
49
- console.log("[INIT] Worker instance " + me.id + " is up.");
50
- for await (const event of WorkerInstances.watch([], {
51
- fullDocument: "whenAvailable",
52
- })) {
53
- const { fullDocument: updated } = event as typeof event & {
54
- fullDocument?: WithId<WorkerInstance>;
55
- };
56
- if (updated && updated.id !== me.id) {
57
- console.log("Another worker is updated", updated);
58
- if (+updated.up > +me.up && updated.task === me.task) {
59
- console.log("[EXIT] I'm outdated, new instance is: " + updated.id);
60
- process.exit(0);
61
- }
62
- }
63
- }
64
- }
65
-
66
- export async function getWorkerInstance(task?: string) {
67
- const id = getWorkerInstanceId();
68
- if (task) {
69
- console.log("Working on task: ", task);
70
- }
71
- return (await WorkerInstances.findOneAndUpdate(
72
- { id },
73
- {
74
- $set: {
75
- id,
76
- active: new Date(),
77
- workerId: getWorkerId(),
78
- geo: await _geoPromise,
79
- ...(task && { task }),
80
- },
81
- $setOnInsert: { up: new Date() },
82
- },
83
- { upsert: true, returnDocument: "after" },
84
- ))!;
85
- }
86
- function getWorkerId() {
87
- const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME;
88
- return md5(`SALT=v9yJQouMC22do66t ${hostname} ${getMAC()}`).slice(0, 8);
89
- }
1
+ import { getMAC } from "@ctrl/mac-address";
2
+ import { defer } from "lodash-es";
3
+ import md5 from "md5";
4
+ import type { WithId } from "mongodb";
5
+ import { db } from "./db";
6
+ import { fetchCurrentGeoInfo } from "./fetchCurrentGeoInfo";
7
+ import { createInstanceId } from "./utils/createInstanceId";
8
+ export type GeoInfo = Awaited<ReturnType<typeof fetchCurrentGeoInfo>>;
9
+ export type WorkerInstance = {
10
+ /** id: rand */
11
+ id: string;
12
+ up: Date;
13
+ active: Date;
14
+ geo: GeoInfo;
15
+ workerId: string;
16
+ task?: string;
17
+ };
18
+
19
+ const k = "COMFY_PR_WorkerInstanceKey";
20
+ type g = typeof globalThis & { [k]: any };
21
+ function getWorkerInstanceId() {
22
+ // ensure only one instance
23
+ if (!(global as any as g)[k])
24
+ defer(async function () {
25
+ await Promise.all([postWorkerHeartBeatLoop(), watchWorkerInstancesLoop()]);
26
+ });
27
+ const instanceId = ((global as any as g)[k] ??= createInstanceId());
28
+ return instanceId;
29
+ }
30
+ 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
34
+
35
+ if (import.meta.main) {
36
+ console.log(await getWorkerInstance());
37
+ }
38
+
39
+ async function postWorkerHeartBeatLoop() {
40
+ // 30s heartbeat
41
+ while (true) {
42
+ await new Promise((r) => setTimeout(r, 30e3));
43
+ await getWorkerInstance();
44
+ }
45
+ }
46
+
47
+ async function watchWorkerInstancesLoop() {
48
+ const me = await getWorkerInstance();
49
+ console.log("[INIT] Worker instance " + me.id + " is up.");
50
+ for await (const event of WorkerInstances.watch([], {
51
+ fullDocument: "whenAvailable",
52
+ })) {
53
+ const { fullDocument: updated } = event as typeof event & {
54
+ fullDocument?: WithId<WorkerInstance>;
55
+ };
56
+ if (updated && updated.id !== me.id) {
57
+ console.log("Another worker is updated", updated);
58
+ if (+updated.up > +me.up && updated.task === me.task) {
59
+ console.log("[EXIT] I'm outdated, new instance is: " + updated.id);
60
+ process.exit(0);
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ export async function getWorkerInstance(task?: string) {
67
+ const id = getWorkerInstanceId();
68
+ if (task) {
69
+ console.log("Working on task: ", task);
70
+ }
71
+ return (await WorkerInstances.findOneAndUpdate(
72
+ { id },
73
+ {
74
+ $set: {
75
+ id,
76
+ active: new Date(),
77
+ workerId: getWorkerId(),
78
+ geo: await _geoPromise,
79
+ ...(task && { task }),
80
+ },
81
+ $setOnInsert: { up: new Date() },
82
+ },
83
+ { upsert: true, returnDocument: "after" },
84
+ ))!;
85
+ }
86
+ function getWorkerId() {
87
+ const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME;
88
+ return md5(`SALT=v9yJQouMC22do66t ${hostname} ${getMAC()}`).slice(0, 8);
89
+ }
@@ -1,65 +1,73 @@
1
- "use server";
2
- import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
3
- import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
4
- import { TaskDataOrNull, type Task } from "@/packages/mongodb-pipeline-ts/Task";
5
- import DIE from "@snomiao/die";
6
- import pMap from "p-map";
7
- import { CNRepos, type CRPull } from "./CNRepos";
8
- import { createIssueComment } from "./createIssueComment";
9
- import { $filaten } from "./db";
10
- import { ghUser } from "./ghUser";
11
- import type { GithubIssueComment } from "./GithubIssueComments";
12
- import { notifySlackLinks } from "./slack/notifySlackLinks";
13
-
14
- export async function addCommentAction({ matched, action, runAction, rule }: { matched: { state: "ok"; mtime: Date; data: { lastwords: string; email: string | ""; comments: number; head: string; on_registry: boolean; comments_author: string; ownername: string; nickName: string; repository: string; state: "OPEN" | "MERGED" | "CLOSED"; url: string; updated: string; pull_updated: string; repo_updated: string; }[]; } | { state: "error"; mtime: Date; error: string; }; action: { by: string; body: string; }; runAction: boolean; rule: { name: string; $match: { actived_at?: Date | { $lt: Date; } | { $gte: Date; } | { $not: { $gt: Date; }; } | undefined; email?: string | RegExp | undefined; comments?: number | { $gt: number; } | { $lt: number; } | { $eq: number; } | { $ne: number; } | { $gte: number; } | { $lte: number; } | undefined; created_at?: Date | { $lt: Date; } | { $gte: Date; } | { $not: { $gt: Date; }; } | undefined; comments_author?: string | RegExp | undefined; head?: string | RegExp | undefined; lastwords?: string | RegExp | undefined; on_registry?: boolean | undefined; nickName?: string | RegExp | undefined; ownername?: string | RegExp | undefined; state?: "OPEN" | "CLOSED" | "MERGED" | undefined; updated_at?: Date | { $lt: Date; } | { $gte: Date; } | { $not: { $gt: Date; }; } | undefined; url?: string | RegExp | undefined; }; action: { "add-comment"?: { by: string; body: string; } | undefined; "send-email"?: { body: string; provider: string; from: string; to: string; subject: string; } | undefined; "update-issue"?: { tags: string | number | RegExp | { $lt: Date; } | { $gte: Date; } | { $nin: string; }; } | undefined; }; }; }) {
15
- return await pMap(
16
- TaskDataOrNull(matched) ?? DIE("NO-PAYLOAD-AVAILABLE"),
17
- async (payload) => {
18
- const loadedAction = {
19
- action: "add-comment",
20
- url: payload.url,
21
- by: action.by,
22
- body: action.body.replace(
23
- /{{\$(\w+)}}/,
24
- (_, key: string) => (payload as any)[key] ||
25
- DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload))
26
- ),
27
- };
28
-
29
- if (runAction && loadedAction.by === ghUser.login) {
30
- const existedCommentsTask = (await $pipeline(CNRepos)
31
- .unwind("$crPulls.data")
32
- .match({ "crPulls.data.pull.html_url": loadedAction.url })
33
- .with<{ "crPulls.data": CRPull; }>()
34
- .replaceRoot({ newRoot: "$crPulls.data.comments" })
35
- .as<Task<GithubIssueComment[]>>()
36
- .aggregate()
37
- .next()) ??
38
- DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
39
-
40
- const existedComments = TaskDataOrNull(existedCommentsTask) ??
41
- DIE("NO-COMMENTS-FOUND should never happen here, bcz pipeline filtered at first");
42
- const existedComment = existedComments.find((e) => e.body === loadedAction.body);
43
-
44
- if (!existedComment) {
45
- const { comments, comment } = await createIssueComment(
46
- loadedAction.url,
47
- loadedAction.body,
48
- loadedAction.by
49
- );
50
- const updateResult = await CNRepos.updateOne(
51
- $filaten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
52
- { $set: { "crPulls.data.$.comments": comments } }
53
- );
54
- if (!updateResult.matchedCount) DIE("created issue not matched");
55
- await notifySlackLinks("A New issue comment are created from rule " + rule.name, [
56
- comment.html_url,
57
- ]);
58
- }
59
- }
60
-
61
- return loadedAction;
62
- },
63
- { concurrency: 1 }
64
- );
65
- }
1
+ "use server";
2
+ import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
3
+ import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
4
+ import { TaskDataOrNull, type Task } from "@/packages/mongodb-pipeline-ts/Task";
5
+ import DIE from "@snomiao/die";
6
+ import pMap from "p-map";
7
+ import type { z } from "zod";
8
+ import type { PullStatusShown } from "./analyzePullsStatus";
9
+ import { CNRepos, type CRPull } from "./CNRepos";
10
+ import { createIssueComment } from "./createIssueComment";
11
+ import { $filaten } from "./db";
12
+ import type { zAddCommentAction } from "./followRuleSchema";
13
+ import { ghUser } from "./ghUser";
14
+ import type { GithubIssueComment } from "./GithubIssueComments";
15
+ import { notifySlackLinks } from "./slack/notifySlackLinks";
16
+
17
+ export async function addCommentAction({
18
+ matched,
19
+ action,
20
+ runAction,
21
+ rule,
22
+ }: {
23
+ matched: Task<PullStatusShown[]>;
24
+ action: z.infer<typeof zAddCommentAction>;
25
+ runAction: boolean;
26
+ rule: { name: string };
27
+ }) {
28
+ return await pMap(
29
+ TaskDataOrNull(matched) ?? DIE("NO-PAYLOAD-AVAILABLE"),
30
+ async (payload) => {
31
+ const loadedAction = {
32
+ action: "add-comment",
33
+ url: payload.url,
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)),
39
+ ),
40
+ };
41
+
42
+ if (runAction && loadedAction.by === ghUser.login) {
43
+ const existedCommentsTask =
44
+ (await $pipeline(CNRepos)
45
+ .unwind("$crPulls.data")
46
+ .match({ "crPulls.data.pull.html_url": loadedAction.url })
47
+ .with<{ "crPulls.data": CRPull }>()
48
+ .replaceRoot({ newRoot: "$crPulls.data.comments" })
49
+ .as<Task<GithubIssueComment[]>>()
50
+ .aggregate()
51
+ .next()) ?? DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
52
+
53
+ const existedComments =
54
+ TaskDataOrNull(existedCommentsTask) ??
55
+ DIE("NO-COMMENTS-FOUND should never happen here, bcz pipeline filtered at first");
56
+ const existedComment = existedComments.find((e) => e.body === loadedAction.body);
57
+
58
+ if (!existedComment) {
59
+ const { comments, comment } = await createIssueComment(loadedAction.url, loadedAction.body, loadedAction.by);
60
+ const updateResult = await CNRepos.updateOne(
61
+ $filaten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
62
+ { $set: { "crPulls.data.$.comments": comments } },
63
+ );
64
+ if (!updateResult.matchedCount) DIE("created issue not matched");
65
+ await notifySlackLinks("A New issue comment are created from rule " + rule.name, [comment.html_url]);
66
+ }
67
+ }
68
+
69
+ return loadedAction;
70
+ },
71
+ { concurrency: 1 },
72
+ );
73
+ }