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
package/src/Authors.ts CHANGED
@@ -1,48 +1,42 @@
1
- import { type Task } from "@/packages/mongodb-pipeline-ts/Task";
2
- import console from "console";
3
- import { db } from "./db";
4
- import { gh } from "./gh";
5
- import type { AwaitedReturnType } from "./types/AwaitedReturnType";
6
- import { updateAuthors } from "./updateAuthors";
7
- export type Author = {
8
- email?: string;
9
- // cold down
10
- githubMtime?: Date;
11
- instagramMtime?: Date;
12
- discordMtime?: Date;
13
- twitterMtime?: Date;
14
-
15
- // id
16
- githubId?: string;
17
- instagramId?: string;
18
- discordId?: string;
19
- twitterId?: string;
20
-
21
- // repos on cm and cr
22
- cm: number; // comfy node manager
23
- cr: number; // comfy registry
24
-
25
- // common info
26
- nicknames?: string[];
27
- bios?: string[];
28
- links?: string[]; // one could be url or markdown style link: [name](link)
29
- blogs?: string[]; // one could be url or markdown style link: [name](link)
30
- companies?: string[];
31
- locations?: string[];
32
- avatars?: string[];
33
- hireable?: boolean; // github
34
- };
35
-
36
- export const Authors = db.collection<Author>("Authors");
37
- await Authors.createIndex('githubId')
38
- await Authors.createIndex('email')
39
- export const GithubUsers = db.collection<
40
- { username: string } & Task<AwaitedReturnType<typeof gh.users.getByUsername>["data"]>
41
- >("GithubUsers");
42
-
43
- if (import.meta.main) {
44
- // collect github id from cn repos
45
- await updateAuthors();
46
- console.log("done");
47
- }
48
-
1
+ import { db } from "./db";
2
+ import type { GHUser } from "./ghUser";
3
+ import { updateAuthors } from "./updateAuthors";
4
+ export type Author = {
5
+ email?: string;
6
+ // cold down
7
+ githubMtime?: Date;
8
+ instagramMtime?: Date;
9
+ discordMtime?: Date;
10
+ twitterMtime?: Date;
11
+
12
+ // id
13
+ githubId?: string;
14
+ instagramId?: string;
15
+ discordId?: string;
16
+ twitterId?: string;
17
+
18
+ // repos on cm and cr
19
+ cm: number; // comfy node manager
20
+ cr: number; // comfy registry
21
+
22
+ // common info
23
+ nicknames?: string[];
24
+ bios?: string[];
25
+ links?: string[]; // one could be url or markdown style link: [name](link)
26
+ blogs?: string[]; // one could be url or markdown style link: [name](link)
27
+ companies?: string[];
28
+ locations?: string[];
29
+ avatars?: string[];
30
+ hireable?: boolean; // github
31
+ };
32
+
33
+ export const Authors = db.collection<Author>("Authors");
34
+ await Authors.createIndex("githubId");
35
+ await Authors.createIndex("email");
36
+ export const GithubUsers = db.collection<{ username: string } & GHUser>("GithubUsers");
37
+
38
+ if (import.meta.main) {
39
+ // collect github id from cn repos
40
+ await updateAuthors();
41
+ console.log("done");
42
+ }
package/src/CMNodes.ts CHANGED
@@ -1,60 +1,60 @@
1
- import jsonStableStringify from "json-stable-stringify";
2
- import md5 from "md5";
3
- import type { ObjectId } from "mongodb";
4
- import { db } from "./db";
5
- import { fetchCMNodes } from "./fetchCMNodes";
6
- import { type SlackMsg } from "./slack/SlackMsgs";
7
- import { updateCMNodesDuplicationWarnings } from "./updateCMNodesDuplicationWarnings";
8
-
9
- // Raw version maybe duplicated with id or reference
10
- type CMNodeRaw = Awaited<ReturnType<typeof fetchCMNodesWithHash>>[number];
11
- export type CMNode = CMNodeRaw & {
12
- repo_id?: ObjectId;
13
- duplicated?: {
14
- [k: string]: { hashes: string[]; slackNotification: SlackMsg };
15
- };
16
- };
17
- export const CMNodes = db.collection<CMNode>("CMNodes");
18
- await CMNodes.createIndex({ mtime: -1 });
19
- await CMNodes.createIndex({ hash: 1 }, { unique: true }).catch(() => {});
20
-
21
- if (import.meta.main) {
22
- console.time("CMNodes");
23
- console.log(await updateCMNodes());
24
- // check dups
25
- console.timeLog("CMNodes");
26
- const all = await CMNodes.countDocuments({});
27
- console.timeLog("CMNodes");
28
- const dups = await CMNodes.countDocuments({ duplicated: { $exists: true } });
29
- console.timeEnd("CMNodes");
30
- console.log("CMNodes updated, duplicates:", dups, "all:", all);
31
- }
32
- export async function updateCMNodes() {
33
- const nodes = await fetchCMNodesWithHash();
34
- console.log("CMNodes updating (" + nodes.length + " nodes)");
35
-
36
- // updating nodes
37
- const updateResult = await CMNodes.bulkWrite(
38
- nodes.flatMap((node) => ({
39
- updateOne: {
40
- filter: { hash: node.hash },
41
- update: { $set: node },
42
- upsert: true,
43
- },
44
- })),
45
- { ordered: false },
46
- );
47
- // delete outdated
48
- // await CMNodes.deleteMany({ hash: { $nin: nodes.map(prop("hash")) } });
49
-
50
- await updateCMNodesDuplicationWarnings(nodes);
51
-
52
- return [updateResult];
53
- }
54
-
55
- async function fetchCMNodesWithHash() {
56
- return (await fetchCMNodes()).map((e) => ({
57
- ...e,
58
- hash: md5("SALT=Bvxmh8mGYh6qzLGE " + jsonStableStringify(e)),
59
- }));
60
- }
1
+ import jsonStableStringify from "json-stable-stringify";
2
+ import md5 from "md5";
3
+ import type { ObjectId } from "mongodb";
4
+ import { db } from "./db";
5
+ import { fetchCMNodes } from "./fetchCMNodes";
6
+ import { type SlackMsg } from "./slack/SlackMsgs";
7
+ import { updateCMNodesDuplicationWarnings } from "./updateCMNodesDuplicationWarnings";
8
+
9
+ // Raw version maybe duplicated with id or reference
10
+ type CMNodeRaw = Awaited<ReturnType<typeof fetchCMNodesWithHash>>[number];
11
+ export type CMNode = CMNodeRaw & {
12
+ repo_id?: ObjectId;
13
+ duplicated?: {
14
+ [k: string]: { hashes: string[]; slackNotification: SlackMsg };
15
+ };
16
+ };
17
+ export const CMNodes = db.collection<CMNode>("CMNodes");
18
+ await CMNodes.createIndex({ mtime: -1 });
19
+ await CMNodes.createIndex({ hash: 1 }, { unique: true }).catch(() => {});
20
+
21
+ if (import.meta.main) {
22
+ console.time("CMNodes");
23
+ console.log(await updateCMNodes());
24
+ // check dups
25
+ console.timeLog("CMNodes");
26
+ const all = await CMNodes.countDocuments({});
27
+ console.timeLog("CMNodes");
28
+ const dups = await CMNodes.countDocuments({ duplicated: { $exists: true } });
29
+ console.timeEnd("CMNodes");
30
+ console.log("CMNodes updated, duplicates:", dups, "all:", all);
31
+ }
32
+ export async function updateCMNodes() {
33
+ const nodes = await fetchCMNodesWithHash();
34
+ console.log("CMNodes updating (" + nodes.length + " nodes)");
35
+
36
+ // updating nodes
37
+ const updateResult = await CMNodes.bulkWrite(
38
+ nodes.flatMap((node) => ({
39
+ updateOne: {
40
+ filter: { hash: node.hash },
41
+ update: { $set: node },
42
+ upsert: true,
43
+ },
44
+ })),
45
+ { ordered: false },
46
+ );
47
+ // delete outdated
48
+ // await CMNodes.deleteMany({ hash: { $nin: nodes.map(prop("hash")) } });
49
+
50
+ await updateCMNodesDuplicationWarnings(nodes);
51
+
52
+ return [updateResult];
53
+ }
54
+
55
+ async function fetchCMNodesWithHash() {
56
+ return (await fetchCMNodes()).map((e) => ({
57
+ ...e,
58
+ hash: md5("SALT=Bvxmh8mGYh6qzLGE " + jsonStableStringify(e)),
59
+ }));
60
+ }
package/src/CNRepos.ts CHANGED
@@ -1,74 +1,78 @@
1
- #!/usr/bin/env bun
2
- /*
3
- bun index.ts
4
- */
5
- import type { ObjectId, WithId } from "mongodb";
6
- import "react-hook-form";
7
- import { type Task } from "../packages/mongodb-pipeline-ts/Task";
8
- import { type CMNode } from "./CMNodes";
9
- import { type CRNode } from "./CRNodes";
10
- import { db } from "./db";
11
- import { gh } from "./gh";
12
- import { type RelatedPull } from "./matchRelatedPulls";
13
- import type { GithubPullParsed } from "./parsePullsState";
14
- import { type SlackMsg } from "./slack/SlackMsgs";
15
-
16
- type Email = {
17
- from?: string;
18
- title: string;
19
- body: string;
20
- to: string;
21
- };
22
-
23
- type Sent = {
24
- slack?: SlackMsg;
25
- emails?: Email[];
26
- };
27
-
28
- type GithubIssueComment = Awaited<ReturnType<typeof gh.issues.getComment>>["data"];
29
- type GithubIssue = Awaited<ReturnType<typeof gh.issues.get>>["data"];
30
- type GithubRepo = Awaited<ReturnType<typeof gh.repos.get>>["data"];
31
- export type CRType = "pyproject" | "publichcr";
32
- export type CRPull = RelatedPull & {
33
- edited?: Task<boolean>;
34
- comments?: Task<Pick<GithubIssueComment, "body">[]>;
35
- issue?: Task<Pick<GithubIssue, "number" | "html_url" | "body" | "updated_at">>;
36
- };
37
-
38
- export type CustomNodeRepo = {
39
- repository: string;
40
- info?: Task<Pick<GithubRepo, "html_url" | "archived" | "default_branch" | "private">>;
41
-
42
- // author?: Author;
43
-
44
- /** @deprecated use cr_ids or on_registry */
45
- cr?: Pick<WithId<CRNode>, "_id" | "id" | "name">;
46
-
47
- cr_ids?: ObjectId[];
48
- on_registry?: Task<boolean>; // check if cr_ids is not empty
49
-
50
- /** @deprecated use cm_ids */
51
- cm?: Pick<WithId<CMNode>, "_id" | "id" | "title">;
52
- cm_ids?: ObjectId[];
53
-
54
- // cache
55
- pulls?: Task<GithubPullParsed[]>;
56
-
57
- crPulls?: Task<CRPull[]>;
58
-
59
- /** @deprecated TODO: NOT IMPLEMENTD */
60
- crPull_ids: ObjectId[];
61
-
62
- candidate?: Task<boolean>;
63
- // createFork?: Task<GithubRepo>;
64
- // createBranches?: Task<{ type: CRType; assigned: Worker } & PushedBranch>[];
65
-
66
- /** @deprecated use CRPulls.pull */
67
- createdPulls?: Task<GithubPullParsed[]>;
68
- };
69
- export type CNRepo = CustomNodeRepo;
70
- export const CNRepos = db.collection<CNRepo>("CNRepos");
71
- await CNRepos.createIndex({ repository: 1 }, { unique: true });
72
-
73
- // fix cr null, it should be not exists
74
- await CNRepos.updateMany({ cr: null as unknown as WithId<CRNode> }, { $unset: { cr: 1 } });
1
+ #!/usr/bin/env bun
2
+ /*
3
+ bun index.ts
4
+ */
5
+ import type { ObjectId, WithId } from "mongodb";
6
+ import "react-hook-form";
7
+ import { nil } from "sflow";
8
+ import { type Task } from "../packages/mongodb-pipeline-ts/Task";
9
+ import { type CMNode } from "./CMNodes";
10
+ import { type CRNode } from "./CRNodes";
11
+ import { db } from "./db";
12
+ import { gh } from "./gh";
13
+ import { type RelatedPull } from "./matchRelatedPulls";
14
+ import type { GithubPullParsed } from "./parsePullsState";
15
+ import { type SlackMsg } from "./slack/SlackMsgs";
16
+
17
+ type Email = {
18
+ from?: string;
19
+ title: string;
20
+ body: string;
21
+ to: string;
22
+ };
23
+
24
+ type Sent = {
25
+ slack?: SlackMsg;
26
+ emails?: Email[];
27
+ };
28
+
29
+ // TODO: refactor into interface to improve performance
30
+ type GithubIssueComment = Awaited<ReturnType<typeof gh.issues.getComment>>["data"];
31
+ type GithubIssue = Awaited<ReturnType<typeof gh.issues.get>>["data"];
32
+ type GithubRepo = Awaited<ReturnType<typeof gh.repos.get>>["data"];
33
+ export type CRType = "pyproject" | "publichcr";
34
+ export type CRPull = RelatedPull & {
35
+ edited?: Task<boolean>;
36
+ comments?: Task<Pick<GithubIssueComment, "body">[]>;
37
+ issue?: Task<Pick<GithubIssue, "number" | "html_url" | "body" | "updated_at">>;
38
+ emailTask_id?: ObjectId;
39
+ };
40
+ // TODO: refactor into interface to improve performance
41
+ export type CustomNodeRepo = {
42
+ repository: string;
43
+ info?: Task<Pick<GithubRepo, "html_url" | "archived" | "default_branch" | "private">>;
44
+
45
+ // author?: Author;
46
+
47
+ /** @deprecated use cr_ids or on_registry */
48
+ cr?: Pick<WithId<CRNode>, "_id" | "id" | "name">;
49
+
50
+ cr_ids?: ObjectId[];
51
+ on_registry?: Task<boolean>; // check if cr_ids is not empty
52
+
53
+ /** @deprecated use cm_ids */
54
+ cm?: Pick<WithId<CMNode>, "_id" | "id" | "title">;
55
+ cm_ids?: ObjectId[];
56
+
57
+ // cache
58
+ pulls?: Task<GithubPullParsed[]>;
59
+
60
+ crPulls?: Task<CRPull[]>;
61
+
62
+ /** @deprecated TODO: NOT IMPLEMENTD */
63
+ crPull_ids: ObjectId[];
64
+
65
+ candidate?: Task<boolean>;
66
+ // createFork?: Task<GithubRepo>;
67
+ // createBranches?: Task<{ type: CRType; assigned: Worker } & PushedBranch>[];
68
+
69
+ /** @deprecated use CRPulls.pull */
70
+ createdPulls?: Task<GithubPullParsed[]>;
71
+ };
72
+ export type CNRepo = CustomNodeRepo;
73
+ export const CNRepos = db.collection<CNRepo>("CNRepos");
74
+
75
+ CNRepos.createIndex({ repository: 1 }, { unique: true }).catch(nil);
76
+
77
+ // fix cr null, it should be not exists
78
+ // await CNRepos.updateMany({ cr: null as unknown as WithId<CRNode> }, { $unset: { cr: 1 } });
package/src/CRNodes.ts CHANGED
@@ -1,27 +1,27 @@
1
- import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
- import type { ObjectId } from "mongodb";
3
- import pMap from "p-map";
4
- import { peekYaml } from "peek-log";
5
- import { CNRepos } from "./CNRepos";
6
- import { db } from "./db";
7
- import { fetchCRNodes } from "./fetchComfyRegistryNodes";
8
- import { type SlackMsg } from "./slack/SlackMsgs";
9
-
10
- export type CRNode = Awaited<ReturnType<typeof fetchCRNodes>>[number] & {
11
- sent?: { slack?: SlackMsg };
12
- repo_id?: ObjectId;
13
- };
14
- export const CRNodes = db.collection<CRNode>("CRNodes");
15
- await CRNodes.createIndex({ id: 1 }, { unique: true });
16
- await CRNodes.createIndex({ repository: 1 }, { unique: false }); // WARN: duplicate is allowed
17
-
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
- );
26
- peekYaml({ r, len: r.length });
27
- }
1
+ import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
+ import type { ObjectId } from "mongodb";
3
+ import pMap from "p-map";
4
+ import { peekYaml } from "peek-log";
5
+ import { CNRepos } from "./CNRepos";
6
+ import { db } from "./db";
7
+ import { fetchCRNodes } from "./fetchComfyRegistryNodes";
8
+ import { type SlackMsg } from "./slack/SlackMsgs";
9
+
10
+ export type CRNode = Awaited<ReturnType<typeof fetchCRNodes>>[number] & {
11
+ sent?: { slack?: SlackMsg };
12
+ repo_id?: ObjectId;
13
+ };
14
+ export const CRNodes = db.collection<CRNode>("CRNodes");
15
+ await CRNodes.createIndex({ id: 1 }, { unique: true });
16
+ await CRNodes.createIndex({ repository: 1 }, { unique: false }); // WARN: duplicate is allowed
17
+
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
+ );
26
+ peekYaml({ r, len: r.length });
27
+ }
package/src/CRPulls.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CRPull } from "./CNRepos";
2
- import { db } from "./db";
3
-
4
- export const CRPulls = db.collection<CRPull>("CRPulls");
1
+ import type { CRPull } from "./CNRepos";
2
+ import { db } from "./db";
3
+
4
+ export const CRPulls = db.collection<CRPull>("CRPulls");
@@ -0,0 +1,101 @@
1
+ import DIE from "@snomiao/die";
2
+ import markdownIt from "markdown-it";
3
+ import type { WithId } from "mongodb";
4
+ import { sf } from "snoflow";
5
+ import type { z } from "zod";
6
+ import { $stale, db } from "./db";
7
+ import type { zSendEmailAction } from "./followRuleSchema";
8
+ import { getGCloudOAuth2Client } from "./gcloud/GCloudOAuth2Credentials";
9
+ import { sendGmail } from "./sendGmail";
10
+
11
+ export type EmailTask = {
12
+ mtime: Date;
13
+ state: "waiting" | "sending" | "sent" | "error";
14
+ error?: string;
15
+ provider: "google";
16
+ name: string;
17
+ from: string;
18
+ to: string;
19
+ subject: string;
20
+ /** body is markdown */
21
+ body: string;
22
+ };
23
+
24
+ export const EmailTasks = db.collection<EmailTask>("EmailTasks");
25
+ await EmailTasks.createIndex({ from: 1, to: 1, subject: 1 });
26
+
27
+ if (import.meta.main) {
28
+ sf(EmailTasks.find({}))
29
+ .map((e) => e.state)
30
+ .toLog();
31
+ // sf(EmailTasks.watch([], { fullDocument: "whenAvailable" }))
32
+ // // .filter((c) => c.operationType === "modify")
33
+ // .toLog()
34
+ // .then(() => console.log("all done"));
35
+ // const saved = await enqueueEmailTask({
36
+ // provider: "google",
37
+ // name: "Snowstar Miao",
38
+ // from: "snomiao@gmail.com",
39
+ // to: "snomiao@gmail.com",
40
+ // subject: "hello again from email queue 2",
41
+ // body: `
42
+ // # hello from sno
43
+
44
+ // You've <b>just</b> received an *email* from snomiao.
45
+
46
+ // Thank you for receiving this email!!!!
47
+ // `,
48
+ // });
49
+ // console.log({ saved });
50
+ // await updateEmailTasks();
51
+ }
52
+
53
+ /**
54
+ * deduplicated by [from, to, subject].join(' ')
55
+ */
56
+ export async function enqueueEmailTask(task: z.infer<typeof zSendEmailAction>) {
57
+ const { name, from, to, subject, body, provider } = task;
58
+ return (
59
+ (await EmailTasks.findOneAndUpdate(
60
+ { from, to, subject },
61
+ { $set: task, $setOnInsert: { mtime: new Date(), state: "waiting" } },
62
+ { upsert: true, returnDocument: "after" },
63
+ )) ?? DIE(new Error("fail to enqueue email task"))
64
+ );
65
+ }
66
+ export async function updateEmailTasks() {
67
+ const count = await sf(EmailTasks.find({ state: "waiting" }))
68
+ .join(sf(EmailTasks.find({ state: "sending", mtime: $stale("1m") })))
69
+ .map(async (e) => {
70
+ const { _id, state } = e;
71
+ if (state === "waiting") {
72
+ await EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "sending" } });
73
+ return await sendEmailTask(e)
74
+ .then(() => EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "sent" } }))
75
+ .catch((err) =>
76
+ EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "error", error: String(err) } }),
77
+ );
78
+ }
79
+ })
80
+ .toCount();
81
+ console.log(count, "email tasks processed");
82
+ }
83
+ export async function sendEmailTask({ _id, state, name, from, to, subject, body, provider }: WithId<EmailTask>) {
84
+ // prerequisites
85
+ if (provider !== "google") DIE("providers other than google is implemented yet");
86
+ const auth = await getGCloudOAuth2Client({
87
+ email: from,
88
+ scope: ["https://www.googleapis.com/auth/gmail.compose"],
89
+ authorize: async (url) =>
90
+ DIE("ERROR: Trying send email from " + from + " but it's not authorized, plz grant permission in " + url),
91
+ });
92
+ // do send
93
+ return sendGmail({
94
+ auth,
95
+ from,
96
+ name,
97
+ to,
98
+ subject,
99
+ html: markdownIt().render(body),
100
+ });
101
+ }
package/src/FORK_OWNER.ts CHANGED
@@ -1,5 +1,5 @@
1
- import DIE from "@snomiao/die";
2
- import { ghUser } from "./ghUser";
3
-
4
- export const FORK_OWNER =
5
- process.env.FORK_OWNER?.replace(/"/g, "")?.trim() || ghUser.login || DIE("Missing env.FORK_OWNER");
1
+ import DIE from "@snomiao/die";
2
+ import { ghUser } from "./ghUser";
3
+
4
+ export const FORK_OWNER =
5
+ process.env.FORK_OWNER?.replace(/"/g, "")?.trim() || ghUser.login || DIE("Missing env.FORK_OWNER");
@@ -1,5 +1,5 @@
1
- import DIE from "@snomiao/die";
2
-
3
- export const FORK_PREFIX =
4
- process.env.FORK_PREFIX?.replace(/"/g, "")?.trim() ||
5
- DIE('Missing env.FORK_PREFIX, if you want empty maybe try FORK_PREFIX=""');
1
+ import DIE from "@snomiao/die";
2
+
3
+ export const FORK_PREFIX =
4
+ process.env.FORK_PREFIX?.replace(/"/g, "")?.trim() ||
5
+ DIE('Missing env.FORK_PREFIX, if you want empty maybe try FORK_PREFIX=""');
@@ -1,25 +1,25 @@
1
- import { z } from "zod";
2
- import { type Task } from "../packages/mongodb-pipeline-ts/Task";
3
- import type { PullsStatus } from "./analyzePullsStatus";
4
- import { db } from "./db";
5
- import type { zFollowUpRules } from "./followRuleSchema";
6
-
7
- // migrate data
8
- // await db.renameCollection("FollowRules", "FollowRuleSets").catch(() => null);
9
- export type FollowRule = z.infer<typeof zFollowUpRules>[number];
10
- export type FollowRuleSet = {
11
- // rules?: Task<FollowRule[]>;
12
- name: string;
13
- yaml: string;
14
- rules?: FollowRule[];
15
- matched?: Task<PullsStatus>;
16
- action_results?: {
17
- name: string;
18
- action: any;
19
- result: Task<any>;
20
- }[];
21
- enabled?: boolean;
22
- yamlWhenEnabled?: string;
23
- };
24
- export const FollowRuleSets = db.collection<FollowRuleSet>("FollowRuleSets");
25
- await FollowRuleSets.createIndex("name", { unique: true });
1
+ import { z } from "zod";
2
+ import { type Task } from "../packages/mongodb-pipeline-ts/Task";
3
+ import type { PullsStatus } from "./analyzePullsStatus";
4
+ import { db } from "./db";
5
+ import type { zFollowUpRules } from "./followRuleSchema";
6
+
7
+ // migrate data
8
+ // await db.renameCollection("FollowRules", "FollowRuleSets").catch(() => null);
9
+ export type FollowRule = z.infer<typeof zFollowUpRules>[number];
10
+ export type FollowRuleSet = {
11
+ // rules?: Task<FollowRule[]>;
12
+ name: string;
13
+ yaml: string;
14
+ rules?: FollowRule[];
15
+ matched?: Task<PullsStatus>;
16
+ action_results?: {
17
+ name: string;
18
+ action: any;
19
+ result: Task<any>;
20
+ }[];
21
+ enabled?: boolean;
22
+ yamlWhenEnabled?: string;
23
+ };
24
+ export const FollowRuleSets = db.collection<FollowRuleSet>("FollowRuleSets");
25
+ await FollowRuleSets.createIndex("name", { unique: true });
@@ -1,5 +1,5 @@
1
- import DIE from "@snomiao/die";
2
- import { ghUser } from "./ghUser";
3
-
4
- export const GIT_USEREMAIL =
5
- process.env.GIT_USEREMAIL || (ghUser.email && ghUser.email) || DIE("Missing env.GIT_USEREMAIL");
1
+ import DIE from "@snomiao/die";
2
+ import { ghUser } from "./ghUser";
3
+
4
+ export const GIT_USEREMAIL =
5
+ process.env.GIT_USEREMAIL || (ghUser.email && ghUser.email) || DIE("Missing env.GIT_USEREMAIL");
@@ -1,9 +1,9 @@
1
- import DIE from "@snomiao/die";
2
- import { GIT_USEREMAIL } from "./GIT_USEREMAIL";
3
- import { ghUser } from "./ghUser";
4
-
5
- export const GIT_USERNAME =
6
- process.env.GIT_USERNAME || (ghUser.email && ghUser.name) || DIE("Missing env.GIT_USERNAME");
7
-
8
- // read env/parameters
9
- console.log(`GIT COMMIT USER: ${GIT_USERNAME} <${GIT_USEREMAIL}>`);
1
+ import DIE from "@snomiao/die";
2
+ import { GIT_USEREMAIL } from "./GIT_USEREMAIL";
3
+ import { ghUser } from "./ghUser";
4
+
5
+ export const GIT_USERNAME =
6
+ process.env.GIT_USERNAME || (ghUser.email && ghUser.name) || DIE("Missing env.GIT_USERNAME");
7
+
8
+ // read env/parameters
9
+ console.log(`GIT COMMIT USER: ${GIT_USERNAME} <${GIT_USEREMAIL}>`);
@@ -1,3 +1,3 @@
1
- import { fetchIssueComments } from "./gh/fetchIssueComments";
2
-
3
- export type GithubIssueComment = Awaited<ReturnType<typeof fetchIssueComments>>[number];
1
+ import { fetchIssueComments } from "./gh/fetchIssueComments";
2
+
3
+ export type GithubIssueComment = Awaited<ReturnType<typeof fetchIssueComments>>[number];