comfy-pr 0.2.19 → 0.2.24

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 (78) hide show
  1. package/README.md +225 -225
  2. package/next-env.d.ts +5 -5
  3. package/package.json +10 -2
  4. package/src/Authors.ts +48 -0
  5. package/src/CMNodes.ts +60 -60
  6. package/src/CNRepos.ts +74 -72
  7. package/src/CRNodes.ts +27 -27
  8. package/src/CRPulls.ts +4 -4
  9. package/src/FORK_OWNER.ts +5 -5
  10. package/src/FORK_PREFIX.ts +5 -5
  11. package/src/FollowRules.ts +25 -25
  12. package/src/GIT_USEREMAIL.ts +5 -5
  13. package/src/GIT_USERNAME.ts +9 -9
  14. package/src/GithubIssueComments.ts +3 -3
  15. package/src/PushedBranch.ts +3 -3
  16. package/src/Totals.ts +9 -9
  17. package/src/TrackingPRs.ts +12 -12
  18. package/src/WorkerInstances.ts +89 -89
  19. package/src/addCommentAction.ts +65 -0
  20. package/src/analyzePullsStatus.ts +170 -125
  21. package/src/analyzeTotals.test.ts +5 -5
  22. package/src/analyzeTotals.ts +118 -120
  23. package/src/checkComfyActivated.ts +39 -39
  24. package/src/cli.ts +40 -40
  25. package/src/clone_modify_push_Branches.ts +21 -21
  26. package/src/createComfyRegistryPRsFromCandidates.ts +55 -55
  27. package/src/createComfyRegistryPullRequests.ts +22 -22
  28. package/src/createGithubForkForRepo.ts +37 -37
  29. package/src/createGithubPullRequest.ts +94 -94
  30. package/src/createIssueComment.ts +29 -29
  31. package/src/fetchCMNodes.ts +22 -22
  32. package/src/fetchComfyRegistryNodes.ts +11 -11
  33. package/src/fetchCurrentGeoInfo.ts +6 -6
  34. package/src/fetchRelatedPullWithComments.ts +15 -15
  35. package/src/fetchRepoDescriptionMap.ts +15 -15
  36. package/src/followRuleSchema.ts +76 -75
  37. package/src/getActivatedShell.ts +18 -18
  38. package/src/getBranchWorkingDir.ts +16 -16
  39. package/src/getRepoWorkingDir.ts +5 -5
  40. package/src/ghUser.ts +6 -6
  41. package/src/index.ts +22 -21
  42. package/src/initializeFollowRules.ts +26 -26
  43. package/src/makePublishBranch.ts +58 -58
  44. package/src/makeTomlBranch.ts +53 -53
  45. package/src/matchRelatedPulls.ts +60 -25
  46. package/src/muteInstances.ts +16 -16
  47. package/src/parseIssueUrl.ts +6 -6
  48. package/src/parseOwnerRepo.ts +33 -33
  49. package/src/parsePullUrl.ts +6 -6
  50. package/src/parsePullsState.ts +6 -6
  51. package/src/parseTitleBodyOfMarkdown.ts +8 -8
  52. package/src/postSlackMessage.ts +21 -21
  53. package/src/preload.ts +30 -30
  54. package/src/pullStatusFollowSchema.ts +12 -12
  55. package/src/readTemplateTitle.ts +11 -11
  56. package/src/showFollowRuleSet.ts +12 -0
  57. package/src/summaryLastPullComment.ts +8 -8
  58. package/src/tomlFillDescription.ts +17 -17
  59. package/src/updateAuthors.ts +7 -0
  60. package/src/updateAuthorsForGithub.ts +50 -0
  61. package/src/updateAuthorsFromCNRepo.ts +30 -0
  62. package/src/updateCMNodesDuplicationWarnings.ts +56 -56
  63. package/src/updateCMRepos.ts +27 -27
  64. package/src/updateCNRepos.ts +58 -58
  65. package/src/updateCNReposCRPullsComments.ts +40 -40
  66. package/src/updateCNReposInfo.ts +57 -57
  67. package/src/updateCNReposPRCandidate.ts +85 -85
  68. package/src/updateCNReposPulls.ts +28 -28
  69. package/src/updateCNReposPullsDashboard.ts +49 -49
  70. package/src/updateCNReposRelatedPulls.ts +25 -25
  71. package/src/updateCRNodes.ts +50 -50
  72. package/src/updateCRRepos.ts +26 -26
  73. package/src/updateComfyTotals.ts +43 -43
  74. package/src/updateFollowRuleSet.ts +127 -182
  75. package/src/updateOutdatedPullsTemplates.ts +160 -156
  76. package/src/updateSlackMessages.ts +43 -43
  77. package/tailwind.config.ts +20 -20
  78. package/CHANGELOG.md +0 -109
@@ -1,182 +1,127 @@
1
- "use server";
2
- import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
3
- import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
4
- import { TaskDataOrNull } from "@/packages/mongodb-pipeline-ts/Task";
5
- import DIE from "@snomiao/die";
6
- import pMap from "p-map";
7
- import { peekYaml } from "peek-log";
8
- import { TaskError, TaskOK, type Task } from "../packages/mongodb-pipeline-ts/Task";
9
- import { CNRepos, type CRPull } from "./CNRepos";
10
- import { FollowRuleSets } from "./FollowRules";
11
- import type { GithubIssueComment } from "./GithubIssueComments";
12
- import { analyzePullsStatus, analyzePullsStatusPipeline } from "./analyzePullsStatus";
13
- import { createIssueComment } from "./createIssueComment";
14
- import { $filaten } from "./db";
15
- import { zAddCommentAction, zFollowUpRules } from "./followRuleSchema";
16
- import { fetchIssueComments } from "./gh/fetchIssueComments";
17
- import { ghUser } from "./ghUser";
18
- import { initializeFollowRules } from "./initializeFollowRules";
19
- import { stringifyGithubRepoUrl } from "./parseOwnerRepo";
20
- import { parsePullUrl } from "./parsePullUrl";
21
- import { notifySlackLinks } from "./slack/notifySlackLinks";
22
- import { yaml } from "./utils/yaml";
23
-
24
- if (import.meta.main) {
25
- // updateFollowRuleSet({yaml: 'default'})
26
- await FollowRuleSets.drop();
27
- const defaultRuleSet = await initializeFollowRules();
28
-
29
- await updateFollowRuleSet({ name: "default", enable: true, yaml: defaultRuleSet.yaml });
30
- await runFollowRuleSet();
31
- }
32
-
33
- export async function runFollowRuleSet({ name = "default" } = {}) {
34
- const ruleset = (await FollowRuleSets.findOne({ name })) ?? DIE("default ruleset not found");
35
- return peekYaml(
36
- await updateFollowRuleSet({
37
- name: ruleset.name,
38
- enable: ruleset.enabled ?? DIE("Ruleset is not enabled"),
39
- yaml: ruleset.yamlWhenEnabled ?? DIE("Enabled yaml is not found"),
40
- runAction: true,
41
- }),
42
- );
43
- }
44
- export type updateFollowRuleSet = typeof updateFollowRuleSet;
45
- export async function updateFollowRuleSet({
46
- name,
47
- yaml: code,
48
- enable,
49
- runAction = false,
50
- }: {
51
- name: string;
52
- /** update yaml if provided */
53
- yaml?: string;
54
- /** update enable if provided */
55
- enable?: boolean | undefined;
56
- /** run action if set to true */
57
- runAction?: boolean;
58
- }) {
59
- "use server";
60
- return await (async function () {
61
- if (enable === false) {
62
- await FollowRuleSets.updateOne({ name }, { $set: { enabled: false, yamlWhenEnabled: "" } });
63
- DIE("ruleset is disabled, no need to run");
64
- }
65
-
66
- // must parse while run, because the date in code is dynamic generated
67
- const rules = zFollowUpRules.parse(yaml.parse(code || DIE("yaml is empty")));
68
- // save if parse succ
69
- // await FollowRuleSets.updateOne({ name }, { $set: { yaml: code, rules } });
70
- const parseResult = await pMap(
71
- rules,
72
- async (rule) => {
73
- if (runAction) {
74
- // pre-fetch comments before run a rule -> match -> action, to prevent comment on a outdated pull state
75
- const preMatched = await analyzePullsStatus({ pipeline: analyzePullsStatusPipeline().match(rule.$match) })
76
- .then(TaskOK)
77
- .catch(TaskError);
78
-
79
- // update comments before run action
80
- const matchedData = TaskDataOrNull(preMatched) ?? DIE("NO-PAYLOAD-AVAILABLE");
81
- await pMap(matchedData, async (payload) => {
82
- const html_url = payload.url;
83
- const { owner, repo, pull_number } = parsePullUrl(payload.url);
84
- const repository = stringifyGithubRepoUrl({ owner, repo });
85
-
86
- const comments = await fetchIssueComments(repository, { number: pull_number })
87
- .then(TaskOK)
88
- .catch(TaskError);
89
- (
90
- await CNRepos.updateOne($filaten({ repository, crPulls: { data: { pull: { html_url } } } }), {
91
- $set: { "crPulls.data.$.comments": comments },
92
- })
93
- ).matchedCount ?? DIE("pre-matched comments is not found");
94
- });
95
- }
96
-
97
- const matched = await analyzePullsStatus({ pipeline: analyzePullsStatusPipeline().match(rule.$match) })
98
- .then(TaskOK)
99
- .catch(TaskError);
100
-
101
- const actions = await (async function () {
102
- return await pMap(
103
- Object.entries(rule.action),
104
- async ([name, _action]) => {
105
- if (name === "add-comment") {
106
- const action = zAddCommentAction.parse(_action);
107
- const matchedData = TaskDataOrNull(matched) ?? DIE("NO-PAYLOAD-AVAILABLE");
108
- return await pMap(
109
- matchedData,
110
- async (payload) => {
111
- const loadedAction = {
112
- action: "add-comment",
113
- url: payload.url,
114
- by: action.by,
115
- body: action.body.replace(
116
- /{{\$(\w+)}}/,
117
- (_, key: string) =>
118
- (payload as any)[key] ||
119
- DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
120
- ),
121
- };
122
-
123
- if (runAction && loadedAction.by === ghUser.login) {
124
- const existedCommentsTask =
125
- (await $pipeline(CNRepos)
126
- .unwind("$crPulls.data")
127
- .match({ "crPulls.data.pull.html_url": loadedAction.url })
128
- .with<{ "crPulls.data": CRPull }>()
129
- .replaceRoot({ newRoot: "$crPulls.data.comments" })
130
- .as<Task<GithubIssueComment[]>>()
131
- .aggregate()
132
- .next()) ??
133
- DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
134
-
135
- const existedComments =
136
- TaskDataOrNull(existedCommentsTask) ??
137
- DIE("NO-COMMENTS-FOUND should never happen here, bcz pipeline filtered at first");
138
- const existedComment = existedComments.find((e) => e.body === loadedAction.body);
139
-
140
- if (!existedComment) {
141
- const { comments, comment } = await createIssueComment(
142
- loadedAction.url,
143
- loadedAction.body,
144
- loadedAction.by,
145
- );
146
- const updateResult = await CNRepos.updateOne(
147
- $filaten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
148
- { $set: { "crPulls.data.$.comments": comments } },
149
- );
150
- if (!updateResult.matchedCount) DIE("created issue not matched");
151
- await notifySlackLinks("A New issue comment are created from rule " + rule.name, [
152
- comment.html_url,
153
- ]);
154
- }
155
- }
156
-
157
- return loadedAction;
158
- },
159
- { concurrency: 1 },
160
- );
161
- }
162
- },
163
- { concurrency: 1 },
164
- );
165
- })()
166
- .then(TaskOK)
167
- .catch(TaskError);
168
-
169
- return { name: rule.name, matched, actions };
170
- },
171
- { concurrency: 1 },
172
- );
173
- if (enable) {
174
- await FollowRuleSets.updateOne({ name }, { $set: { enabled: true, yamlWhenEnabled: code, yaml: code } });
175
- } else {
176
- await FollowRuleSets.updateOne({ name }, { $set: { yaml: code } });
177
- }
178
- return parseResult;
179
- })()
180
- .then(TaskOK)
181
- .catch(TaskError);
182
- }
1
+ "use server";
2
+ import { TaskDataOrNull } from "@/packages/mongodb-pipeline-ts/Task";
3
+ import DIE from "@snomiao/die";
4
+ import pMap from "p-map";
5
+ import { peekYaml } from "peek-log";
6
+ import { TaskError, TaskOK } from "../packages/mongodb-pipeline-ts/Task";
7
+ import { CNRepos } from "./CNRepos";
8
+ import { FollowRuleSets } from "./FollowRules";
9
+ import { addCommentAction } from "./addCommentAction";
10
+ import { analyzePullsStatus, analyzePullsStatusPipeline } from "./analyzePullsStatus";
11
+ import { $filaten } from "./db";
12
+ import { zAddCommentAction, zFollowUpRules } from "./followRuleSchema";
13
+ import { fetchIssueComments } from "./gh/fetchIssueComments";
14
+ import { initializeFollowRules } from "./initializeFollowRules";
15
+ import { stringifyGithubRepoUrl } from "./parseOwnerRepo";
16
+ import { parsePullUrl } from "./parsePullUrl";
17
+ import { yaml } from "./utils/yaml";
18
+
19
+ if (import.meta.main) {
20
+ // updateFollowRuleSet({yaml: 'default'})
21
+ await FollowRuleSets.drop();
22
+ const defaultRuleSet = await initializeFollowRules();
23
+
24
+ await updateFollowRuleSet({ name: "default", enable: true, yaml: defaultRuleSet.yaml });
25
+ await runFollowRuleSet();
26
+ }
27
+
28
+ export async function runFollowRuleSet({ name = "default" } = {}) {
29
+ const ruleset = (await FollowRuleSets.findOne({ name })) ?? DIE("default ruleset not found");
30
+ return peekYaml(
31
+ await updateFollowRuleSet({
32
+ name: ruleset.name,
33
+ enable: ruleset.enabled ?? DIE("Ruleset is not enabled"),
34
+ yaml: ruleset.yamlWhenEnabled ?? DIE("Enabled yaml is not found"),
35
+ runAction: true,
36
+ }),
37
+ );
38
+ }
39
+ export type updateFollowRuleSet = typeof updateFollowRuleSet;
40
+ export async function updateFollowRuleSet({
41
+ name,
42
+ yaml: code,
43
+ enable,
44
+ runAction = false,
45
+ }: {
46
+ /** 'default' */
47
+ name: string;
48
+ /** update yaml if provided, unchange if undefined */
49
+ yaml?: string;
50
+ /** update enable if provided, unchange if undefined */
51
+ enable?: boolean | undefined;
52
+ /** run action if true */
53
+ runAction?: boolean;
54
+ }) {
55
+ "use server";
56
+ return await (async function () {
57
+ if (enable === false) {
58
+ await FollowRuleSets.updateOne({ name }, { $set: { enabled: false, yamlWhenEnabled: "" } });
59
+ DIE`ruleset is disabled, no need to run`;
60
+ }
61
+
62
+ // must parse while run, because the date in code is dynamic generated
63
+ const rules = zFollowUpRules.parse(yaml.parse(code || DIE`yaml is empty`));
64
+ // save if parse succ
65
+ // await FollowRuleSets.updateOne({ name }, { $set: { yaml: code, rules } });
66
+ const parseResult = await pMap(
67
+ rules,
68
+ async (rule) => {
69
+ if (runAction) {
70
+ // pre-fetch comments before run a rule -> match -> action, to prevent comment on a outdated pull state
71
+ const preMatched = await analyzePullsStatus({ pipeline: analyzePullsStatusPipeline().match(rule.$match) })
72
+ .then(TaskOK)
73
+ .catch(TaskError);
74
+
75
+ // update comments before run action
76
+ const matchedData = TaskDataOrNull(preMatched) ?? DIE("NO-PAYLOAD-AVAILABLE");
77
+ await pMap(matchedData, async (payload) => {
78
+ const html_url = payload.url;
79
+ const { owner, repo, pull_number } = parsePullUrl(payload.url);
80
+ const repository = stringifyGithubRepoUrl({ owner, repo });
81
+
82
+ const comments = await fetchIssueComments(repository, { number: pull_number })
83
+ .then(TaskOK)
84
+ .catch(TaskError);
85
+ (
86
+ await CNRepos.updateOne($filaten({ repository, crPulls: { data: { pull: { html_url } } } }), {
87
+ $set: { "crPulls.data.$.comments": comments },
88
+ })
89
+ ).matchedCount ?? DIE("pre-matched comments is not found");
90
+ });
91
+ }
92
+
93
+ const matched = await analyzePullsStatus({ pipeline: analyzePullsStatusPipeline().match(rule.$match) })
94
+ .then(TaskOK)
95
+ .catch(TaskError);
96
+
97
+ const actions = await (async function () {
98
+ return await pMap(
99
+ Object.entries(rule.action),
100
+ async ([name, _action]) => {
101
+ if (name === "add-comment") {
102
+ const action = zAddCommentAction.parse(_action);
103
+ return await addCommentAction({ matched, action, runAction, rule });
104
+ }
105
+ },
106
+ { concurrency: 1 },
107
+ );
108
+ })()
109
+ .then(TaskOK)
110
+ .catch(TaskError);
111
+
112
+ return { name: rule.name, matched, actions };
113
+ },
114
+ { concurrency: 1 },
115
+ );
116
+
117
+ if (enable === true) {
118
+ await FollowRuleSets.updateOne({ name }, { $set: { enabled: true, yamlWhenEnabled: code, yaml: code } });
119
+ } else {
120
+ await FollowRuleSets.updateOne({ name }, { $set: { yaml: code } });
121
+ }
122
+ return parseResult;
123
+ })()
124
+ .then(TaskOK)
125
+ .catch(TaskError);
126
+ }
127
+
@@ -1,156 +1,160 @@
1
- import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
2
- import DIE from "@snomiao/die";
3
- import pMap from "p-map";
4
- import { match } from "ts-pattern";
5
- import { $OK, TaskError, TaskOK, tsmatch } from "../packages/mongodb-pipeline-ts/Task";
6
- import { CNRepos, type CRPull } from "./CNRepos";
7
- import { $filaten, $fresh, $stale } from "./db";
8
- import { gh } from "./gh";
9
- import { parsePull } from "./gh/parsePull";
10
- import { ghUser } from "./ghUser";
11
- import { parseUrlRepoOwner } from "./parseOwnerRepo";
12
- import { readTemplate } from "./readTemplateTitle";
13
- import { notifySlackLinks } from "./slack/notifySlackLinks";
14
- import { tLog } from "./utils/tLog";
15
- if (import.meta.main) {
16
- await tLog("updateOutdatedPullsTemplates", updateOutdatedPullsTemplates);
17
- }
18
-
19
- export async function updateOutdatedPullsTemplates() {
20
- const pyproject = await readTemplate("add-toml.md");
21
- const publishcr = await readTemplate("add-action.md");
22
- const outdated_pyproject = await readTemplate("outdated/add-toml.md");
23
- const outdated_publishcr = await readTemplate("outdated/add-action.md");
24
- const outdated_pyproject_v2 = await readTemplate("outdated/add-toml-v2.md");
25
- const outdated_publishcr_v2 = await readTemplate("outdated/add-action-v2.md");
26
- const outdated_pyproject_v3 = await readTemplate("outdated/add-toml-v3.md");
27
- const outdated_publishcr_v3 = await readTemplate("outdated/add-action-v3.md");
28
- const outdateTitles = [
29
- outdated_pyproject.title,
30
- outdated_publishcr.title,
31
- outdated_pyproject_v2.title,
32
- outdated_publishcr_v2.title,
33
- outdated_pyproject_v3.title,
34
- outdated_publishcr_v3.title,
35
- ];
36
- const outdateBodies = [
37
- outdated_pyproject.body,
38
- outdated_publishcr.body,
39
- outdated_pyproject_v2.body,
40
- outdated_publishcr_v2.body,
41
- outdated_pyproject_v3.body,
42
- outdated_publishcr_v3.body,
43
- ];
44
-
45
- // const templateOutdate = new Date("2024-06-13T09:02:56.630Z");
46
-
47
- await CNRepos.createIndex({
48
- "crPulls.mtime": -1,
49
- "crPulls.data.edited.error": -1,
50
- "crPulls.data.edited.mtime": -1,
51
- "crPulls.data.edited.state": -1,
52
- "crPulls.data.pull.body": -1,
53
- "crPulls.data.pull.title": -1,
54
- "crPulls.data.pull.user.login": -1,
55
- });
56
- return await pMap(
57
- CNRepos.find(
58
- $filaten({
59
- crPulls: {
60
- mtime: $fresh("1h"), // retry if update fails
61
- data: $elemMatch({
62
- edited: {
63
- mtime: $stale("30m"), // retry if update fails
64
- state: { $ne: "ok" },
65
- error: { $nin: ["up to date", "body mismatch"] },
66
- },
67
- pull: {
68
- user: { login: ghUser.login },
69
- title: { $in: outdateTitles },
70
- body: { $in: outdateBodies },
71
- },
72
- }),
73
- },
74
- }),
75
- // $filaten({
76
- // crPulls: {
77
- // data: $elemMatch({
78
- // edited: {
79
- // mtime: $stale("30m"), // retry if update fails
80
- // state: { $ne: "ok" },
81
- // error: { $nin: ["not editable", "up to date", "body mismatch"] },
82
- // },
83
- // pull: {
84
- // updated_at: {
85
- // $lte: new Date().toISOString().replace(/.\d\d\dZ/, "Z"),
86
- // },
87
- // },
88
- // }),
89
- // },
90
- // }),
91
- ),
92
- async (repo) => {
93
- const { repository } = repo;
94
- console.log("Updating PR templates for: " + repository);
95
- const crPulls = match(repo.crPulls)
96
- .with($OK, (e) => e.data)
97
- .otherwise(() => null)!;
98
-
99
- // edit CRPulls templates to latest
100
- const crPullsEdited = await pMap(
101
- crPulls,
102
- async (data, i): Promise<CRPull> => {
103
- const { pull, type } = data;
104
- const { number } = pull;
105
- if (pull.user.login !== ghUser.login) DIE("not editable");
106
- const replacement = match(pull)
107
- .with(pyproject, () => DIE("Is already latest, should never happen here"))
108
- .with(publishcr, () => DIE("Is already latest, should never happen here"))
109
- .with(outdated_pyproject, () => pyproject)
110
- .with(outdated_pyproject_v2, () => pyproject)
111
- .with(outdated_pyproject_v3, () => pyproject)
112
- .with(outdated_publishcr, () => publishcr)
113
- .with(outdated_publishcr_v2, () => publishcr)
114
- .with(outdated_publishcr_v3, () => publishcr)
115
- // in case author clicked some task as completed, body will be different, may cause template mismatch
116
- .otherwise(() => DIE("Template not found: " + pull.title));
117
- if (!replacement) return { ...data, edited: TaskError("Template mismatch") };
118
-
119
- const edited = await gh.issues
120
- .update({
121
- ...parseUrlRepoOwner(repository),
122
- issue_number: number,
123
- ...(pull.title === replacement.title ? {} : { title: replacement.title }),
124
- ...(pull.body === replacement.body ? {} : { body: replacement.body }),
125
- })
126
- .then(() => true as const)
127
- .then(TaskOK)
128
- .catch(TaskError);
129
- const updatedPull = (
130
- await gh.pulls.get({
131
- ...parseUrlRepoOwner(repository),
132
- pull_number: number,
133
- })
134
- ).data;
135
- await CNRepos.updateOne({ repository }, { $set: { [`crPulls.data.${i}.edited`]: edited } });
136
- return { pull: parsePull(updatedPull), type, edited };
137
- },
138
- { concurrency: 2 },
139
- )
140
- .then(TaskOK)
141
- .catch(TaskError);
142
- tsmatch(crPullsEdited)
143
- .with($OK, async ({ data }) => {
144
- await notifySlackLinks(
145
- `Updated PR body to latest template`,
146
- data.map((e) => e.pull.html_url),
147
- );
148
- })
149
- .otherwise(() => {});
150
- await CNRepos.updateOne({ repository }, { $set: { crPullsEdited } });
151
- // update outdated pr issue bodies
152
- return crPullsEdited;
153
- },
154
- { concurrency: 2 },
155
- );
156
- }
1
+ import { $elemMatch } from "@/packages/mongodb-pipeline-ts/$elemMatch";
2
+ import DIE from "@snomiao/die";
3
+ import pMap from "p-map";
4
+ import { match } from "ts-pattern";
5
+ import { $OK, TaskError, TaskOK, tsmatch } from "../packages/mongodb-pipeline-ts/Task";
6
+ import { CNRepos, type CRPull } from "./CNRepos";
7
+ import { $filaten, $fresh, $stale } from "./db";
8
+ import { gh } from "./gh";
9
+ import { parsePull } from "./gh/parsePull";
10
+ import { ghUser } from "./ghUser";
11
+ import { parseUrlRepoOwner } from "./parseOwnerRepo";
12
+ import { readTemplate } from "./readTemplateTitle";
13
+ import { notifySlackLinks } from "./slack/notifySlackLinks";
14
+ import { tLog } from "./utils/tLog";
15
+ if (import.meta.main) {
16
+ await tLog("updateOutdatedPullsTemplates", updateOutdatedPullsTemplates);
17
+ }
18
+
19
+ export async function updateOutdatedPullsTemplates() {
20
+ const pyproject = await readTemplate("add-toml.md");
21
+ const publishcr = await readTemplate("add-action.md");
22
+ const outdated_pyproject = await readTemplate("outdated/add-toml.md");
23
+ const outdated_publishcr = await readTemplate("outdated/add-action.md");
24
+ const outdated_pyproject_v2 = await readTemplate("outdated/add-toml-v2.md");
25
+ const outdated_publishcr_v2 = await readTemplate("outdated/add-action-v2.md");
26
+ const outdated_pyproject_v3 = await readTemplate("outdated/add-toml-v3.md");
27
+ const outdated_publishcr_v3 = await readTemplate("outdated/add-action-v3.md");
28
+ const outdated_pyproject_v4 = await readTemplate("outdated/add-toml-v4.md");
29
+ const outdateTitles = [
30
+ outdated_pyproject.title,
31
+ outdated_publishcr.title,
32
+ outdated_pyproject_v2.title,
33
+ outdated_publishcr_v2.title,
34
+ outdated_pyproject_v3.title,
35
+ outdated_publishcr_v3.title,
36
+ outdated_pyproject_v4.title,
37
+ ];
38
+ const outdateBodies = [
39
+ outdated_pyproject.body,
40
+ outdated_publishcr.body,
41
+ outdated_pyproject_v2.body,
42
+ outdated_publishcr_v2.body,
43
+ outdated_pyproject_v3.body,
44
+ outdated_publishcr_v3.body,
45
+ outdated_pyproject_v4.body,
46
+ ];
47
+
48
+ // const templateOutdate = new Date("2024-06-13T09:02:56.630Z");
49
+
50
+ await CNRepos.createIndex({
51
+ "crPulls.mtime": -1,
52
+ "crPulls.data.edited.error": -1,
53
+ "crPulls.data.edited.mtime": -1,
54
+ "crPulls.data.edited.state": -1,
55
+ "crPulls.data.pull.body": -1,
56
+ "crPulls.data.pull.title": -1,
57
+ "crPulls.data.pull.user.login": -1,
58
+ });
59
+ return await pMap(
60
+ CNRepos.find(
61
+ $filaten({
62
+ crPulls: {
63
+ mtime: $fresh("1h"), // retry if update fails
64
+ data: $elemMatch({
65
+ edited: {
66
+ mtime: $stale("30m"), // retry if update fails
67
+ state: { $ne: "ok" },
68
+ error: { $nin: ["up to date", "body mismatch"] },
69
+ },
70
+ pull: {
71
+ user: { login: ghUser.login },
72
+ title: { $in: outdateTitles },
73
+ body: { $in: outdateBodies },
74
+ },
75
+ }),
76
+ },
77
+ }),
78
+ // $filaten({
79
+ // crPulls: {
80
+ // data: $elemMatch({
81
+ // edited: {
82
+ // mtime: $stale("30m"), // retry if update fails
83
+ // state: { $ne: "ok" },
84
+ // error: { $nin: ["not editable", "up to date", "body mismatch"] },
85
+ // },
86
+ // pull: {
87
+ // updated_at: {
88
+ // $lte: new Date().toISOString().replace(/.\d\d\dZ/, "Z"),
89
+ // },
90
+ // },
91
+ // }),
92
+ // },
93
+ // }),
94
+ ),
95
+ async (repo) => {
96
+ const { repository } = repo;
97
+ console.log("Updating PR templates for: " + repository);
98
+ const crPulls = match(repo.crPulls)
99
+ .with($OK, (e) => e.data)
100
+ .otherwise(() => null)!;
101
+
102
+ // edit CRPulls templates to latest
103
+ const crPullsEdited = await pMap(
104
+ crPulls,
105
+ async (data, i): Promise<CRPull> => {
106
+ const { pull, type } = data;
107
+ const { number } = pull;
108
+ if (pull.user.login !== ghUser.login) DIE("not editable");
109
+ const replacement = match(pull)
110
+ .with(pyproject, () => DIE("Is already latest, should never happen here"))
111
+ .with(publishcr, () => DIE("Is already latest, should never happen here"))
112
+ .with(outdated_pyproject, () => pyproject)
113
+ .with(outdated_pyproject_v2, () => pyproject)
114
+ .with(outdated_pyproject_v3, () => pyproject)
115
+ .with(outdated_pyproject_v4, () => pyproject)
116
+ .with(outdated_publishcr, () => publishcr)
117
+ .with(outdated_publishcr_v2, () => publishcr)
118
+ .with(outdated_publishcr_v3, () => publishcr)
119
+ // in case author clicked some task as completed, body will be different, may cause template mismatch
120
+ .otherwise(() => DIE("Template not found: " + pull.title));
121
+ if (!replacement) return { ...data, edited: TaskError("Template mismatch") };
122
+
123
+ const edited = await gh.issues
124
+ .update({
125
+ ...parseUrlRepoOwner(repository),
126
+ issue_number: number,
127
+ ...(pull.title === replacement.title ? {} : { title: replacement.title }),
128
+ ...(pull.body === replacement.body ? {} : { body: replacement.body }),
129
+ })
130
+ .then(() => true as const)
131
+ .then(TaskOK)
132
+ .catch(TaskError);
133
+ const updatedPull = (
134
+ await gh.pulls.get({
135
+ ...parseUrlRepoOwner(repository),
136
+ pull_number: number,
137
+ })
138
+ ).data;
139
+ await CNRepos.updateOne({ repository }, { $set: { [`crPulls.data.${i}.edited`]: edited } });
140
+ return { pull: parsePull(updatedPull), type, edited };
141
+ },
142
+ { concurrency: 2 },
143
+ )
144
+ .then(TaskOK)
145
+ .catch(TaskError);
146
+ tsmatch(crPullsEdited)
147
+ .with($OK, async ({ data }) => {
148
+ await notifySlackLinks(
149
+ `Updated PR body to latest template`,
150
+ data.map((e) => e.pull.html_url),
151
+ );
152
+ })
153
+ .otherwise(() => {});
154
+ await CNRepos.updateOne({ repository }, { $set: { crPullsEdited } });
155
+ // update outdated pr issue bodies
156
+ return crPullsEdited;
157
+ },
158
+ { concurrency: 2 },
159
+ );
160
+ }