comfy-pr 0.2.25 → 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.
@@ -1,22 +1,67 @@
1
- import DIE from "@snomiao/die";
2
- import yaml from "yaml";
1
+ import DIE, { catchArgs } from "@snomiao/die";
2
+ import "git-diff";
3
+ import { Octokit } from "octokit";
4
+ import { pickAll } from "rambda";
5
+ import sflow from "sflow";
6
+ import { isRepoBypassed } from "./bypassRepos";
3
7
  import { gh } from "./gh";
4
8
  import type { GithubPull } from "./gh/GithubPull";
5
9
  import { parseUrlRepoOwner } from "./parseOwnerRepo";
10
+ if (import.meta.main) {
11
+ const srcUrl = "https://github.com/ComfyNodePRs/PR-ComfyUI-DareMerge-7bcbf6a9";
12
+ const dstUrl = "https://github.com/54rt1n/ComfyUI-DareMerge";
13
+ const src = parseUrlRepoOwner(srcUrl);
14
+ const dst = parseUrlRepoOwner(dstUrl);
15
+ const branch = "licence-update";
16
+ const repo = (await gh.repos.get({ ...dst })).data;
17
+ const head_repo = `${src.owner}/${src.repo}`;
18
+ // const head = `${src.owner}:${branch}`;
19
+ const head = `${src.owner}:licence-update`;
20
+ console.log("headrepo " + head_repo);
21
+ console.log("head " + head);
22
+ await sflow(
23
+ (
24
+ await gh.pulls.list({
25
+ // source repo
26
+ state: "all",
27
+ // head_repo: head_repo,
6
28
 
29
+ head: head,
30
+ // pr will merge into
31
+ owner: dst.owner,
32
+ repo: dst.repo,
33
+ base: repo.default_branch,
34
+ })
35
+ ).data,
36
+ )
37
+ // .filter(e => head === e.head.label )
38
+ .map((e) => ({
39
+ url: e.html_url,
40
+ hEAD: head.trim() === e.head.label.trim(),
41
+ heaD: head,
42
+ head: e.head.label,
43
+ head_repo: e.head.repo.full_name,
44
+ }))
45
+ .toLog();
46
+ console.log("all done");
47
+ }
7
48
  export async function createGithubPullRequest({
8
49
  title,
9
50
  body,
10
51
  branch,
11
52
  srcUrl,
12
53
  dstUrl,
54
+ updateIfNotMatched = true,
13
55
  }: {
14
56
  title: string;
15
57
  body: string;
16
58
  branch: string;
17
- srcUrl: string;
18
- dstUrl: string;
59
+ srcUrl: string; // forked branch
60
+ dstUrl: string; // upstream
61
+ updateIfNotMatched?: boolean;
19
62
  }) {
63
+ if (isRepoBypassed(dstUrl)) DIE("dst repo is requested to be bypassed");
64
+
20
65
  const dst = parseUrlRepoOwner(dstUrl);
21
66
  const src = parseUrlRepoOwner(srcUrl);
22
67
  const repo = (await gh.repos.get({ ...dst })).data;
@@ -26,69 +71,87 @@ export async function createGithubPullRequest({
26
71
  await gh.pulls.list({
27
72
  // source repo
28
73
  state: "all",
29
- head_repo: src.owner + "/" + src.repo,
30
- head: src.owner + ":" + branch,
74
+ head_repo: `${src.owner}/${src.repo}`,
75
+ head: `${src.owner}:${branch}`,
31
76
  // pr will merge into
32
77
  owner: dst.owner,
33
78
  repo: dst.repo,
34
79
  base: repo.default_branch,
35
80
  })
36
81
  ).data;
37
- if (existedList.length) {
38
- const msg = {
39
- PR_Existed: existedList.map((e) => ({ url: e.html_url, title: e.title })),
40
- };
41
- console.log(yaml.stringify(msg));
42
- return existedList[0];
43
- }
44
- if (!process.env.GH_TOKEN_COMFY_PR) {
45
- DIE("Missing env.GH_TOKEN_COMFY_PR");
46
- }
47
- const pr_result = await gh.pulls
48
- .create({
49
- // pr info
50
- title,
51
- body,
52
- // source repo
53
- head_repo: src.owner + "/" + src.repo,
54
- head: src.owner + ":" + branch,
55
- // pr will merge into
56
- owner: dst.owner,
57
- repo: dst.repo,
58
- base: repo.default_branch,
59
- maintainer_can_modify: true,
60
- // draft: true,
61
- })
62
- .then((e) => e.data)
63
- .catch(async (e) => {
64
- if (e.message.match("A pull request already exists for")) {
65
- console.log("PR Existed ", e);
82
+ if (existedList.length > 1)
83
+ DIE(
84
+ new Error(`expect only 1 pr, but got ${existedList.length}`, {
85
+ cause: { existed: existedList.map((e) => ({ url: e.html_url, title: e.title })) },
86
+ }),
87
+ );
88
+
89
+ const pr_result =
90
+ existedList[0] ??
91
+ (await ghPR()
92
+ .pulls.create({
93
+ // pr info
94
+ title,
95
+ body,
96
+ // source repo
97
+ head_repo: src.owner + "/" + src.repo,
98
+ head: src.owner + ":" + branch,
99
+ // pr will merge into
100
+ owner: dst.owner,
101
+ repo: dst.repo,
102
+ base: repo.default_branch,
103
+ maintainer_can_modify: true,
104
+ // draft: true,
105
+ })
106
+ .then((e) => e.data)
107
+
108
+ // handle existed error
109
+ .catch(async (e) => {
110
+ if (!e.message.match("A pull request already exists for")) throw e;
111
+ console.error("PR Existed\n", e);
66
112
  // WARN: will search all prs
67
113
  const existedList = (
68
114
  await gh.pulls.list({
69
115
  // source repo
70
116
  state: "open",
71
- head_repo: src.owner + "/" + src.repo,
72
- // head: src.owner + ":" + branch,
117
+ head_repo: `${src.owner}/${src.repo}`,
118
+ head: `${src.owner}:${branch}`,
73
119
  // pr will merge into
74
120
  owner: dst.owner,
75
121
  repo: dst.repo,
76
122
  base: repo.default_branch,
77
123
  })
78
- ).data;
79
- if (existedList.length) {
80
- const msg = {
81
- PR_Existed: existedList.map((e) => ({
82
- url: e.html_url,
83
- title: e.title,
84
- })),
85
- };
86
- console.log(yaml.stringify(msg));
87
- return existedList[0];
88
- }
89
- }
90
- throw e;
91
- });
124
+ ).data; // .filter(existed => existed.title === title);
125
+
126
+ if (existedList.length !== 1)
127
+ DIE(
128
+ new Error("expect only 1 pr, but got " + existedList.length, {
129
+ cause: { existed: existedList.map((e) => ({ url: e.html_url, title: e.title })) },
130
+ }),
131
+ );
132
+
133
+ return existedList[0];
134
+ }));
135
+
92
136
  console.log("PR OK", pr_result.html_url);
137
+ const mismatch = pr_result.title !== title || pr_result.body !== body;
138
+ if (mismatch) {
139
+ if (!updateIfNotMatched)
140
+ DIE(
141
+ new Error("pr content mismatch", {
142
+ cause: { mismatch, expected: { title, body }, actual: pickAll(["title", "body"], pr_result) },
143
+ }),
144
+ );
145
+ const { owner, repo } = parseUrlRepoOwner(dstUrl); // upstream repo
146
+ const updated = (await catchArgs(ghPR().pulls.update)({ pull_number: pr_result.number, body, title, owner, repo }))
147
+ .data!;
148
+ const updatedPRStillMismatch = updated.title !== title || updated.body !== body;
149
+ if (updatedPRStillMismatch) DIE(new Error("updatedPRStillMismatch", { cause: arguments }));
150
+ console.warn(`PR content updated ${owner}/${repo} / \n<< ${pr_result.title}\n>> ${updated.title}`);
151
+ }
93
152
  return pr_result as GithubPull;
94
153
  }
154
+
155
+ function ghPR() {
156
+ return new Octokit({ auth: process.env.GH_TOKEN_COMFY_PR || DIE(new Error("Missing env.GH_TOKEN_COMFY_PR")) }).rest;
157
+ }
@@ -4,8 +4,13 @@ import { ghUser } from "./ghUser";
4
4
  import { parseIssueUrl } from "./parseIssueUrl";
5
5
 
6
6
  if (import.meta.main) {
7
- const url = "https://github.com/snomiao/ComfyNode-Registry-test/pull/1";
8
- const body = "Hello World @snomiao";
7
+ // const url = "https://github.com/snomiao/ComfyNode-Registry-test/pull/1";
8
+ // const body = "Hello World @snomiao";
9
+ // const result = await createIssueComment(url, body, ghUser.login);
10
+ // console.log(result.comment.html_url);
11
+ // console.log(result.comments.map((e) => e.html_url));
12
+ const url = "https://github.com/snomiao/ComfyNode-Registry-test/pull/28";
13
+ const body = "Hello World @robinjhuang";
9
14
  const result = await createIssueComment(url, body, ghUser.login);
10
15
  console.log(result.comment.html_url);
11
16
  console.log(result.comments.map((e) => e.html_url));
@@ -22,7 +27,7 @@ export async function createIssueComment(issueUrl: string, body: string, by: str
22
27
  if (commentExisted) return { comment: commentExisted, comments };
23
28
  if (by !== ghUser.login) DIE("Fails to creating issue: user not match");
24
29
  const comment = (await gh.issues.createComment({ ...parseIssueUrl(issueUrl), body })).data;
25
- console.log("comment created: " + comment.html_url);
30
+ console.log("+ IssueComment " + comment.html_url);
26
31
  return { comment, comments: [...comments, comment] };
27
32
  })();
28
33
  return result;
@@ -50,11 +50,12 @@ const zFollowUpRule = z.object({
50
50
  created_at: mDate,
51
51
  comments_author: mString,
52
52
  head: mString,
53
- lastwords: mString,
53
+ lastcomment: mString,
54
54
  on_registry: z.boolean(),
55
55
  nickName: mString,
56
56
  ownername: mString,
57
57
  state: z.enum(["OPEN", "CLOSED", "MERGED"]),
58
+ emailState: z.enum(["", "waiting", "sending", "sent", "error"]),
58
59
  updated_at: mDate,
59
60
  url: mString,
60
61
  })
package/src/ghUser.ts CHANGED
@@ -1,6 +1,9 @@
1
+ import type { Task } from "@/packages/mongodb-pipeline-ts/Task";
1
2
  import { gh } from "./gh";
3
+ import type { AwaitedReturnType } from "./types/AwaitedReturnType";
2
4
 
3
5
  export const ghUser = (await gh.users.getAuthenticated()).data;
4
6
 
5
7
  console.log("Fetch Current Github User...");
6
8
  console.log(`Current Github User: ${ghUser.login} <${ghUser.email}>`);
9
+ export type GHUser = Task<AwaitedReturnType<typeof gh.users.getByUsername>["data"]>;
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import "dotenv/config";
2
2
  import { checkComfyActivated } from "./checkComfyActivated";
3
3
  import { updateEmailTasks } from "./EmailTasks";
4
4
  import { initializeFollowRules } from "./initializeFollowRules";
5
+ import { updateTomlLicenseTasks } from "./makeUpdateTomlLicenseBranch";
5
6
  import { updateAuthors } from "./updateAuthors";
6
7
  import { updateCNRepos } from "./updateCNRepos";
7
8
  import { runFollowRuleSet } from "./updateFollowRuleSet";
@@ -16,6 +17,7 @@ if (import.meta.main) {
16
17
  updateCNRepos(),
17
18
  updateAuthors(),
18
19
  updateEmailTasks(),
20
+ updateTomlLicenseTasks(),
19
21
  ]);
20
22
  await initializeFollowRules();
21
23
  await tLog("runFollowRuleSet", runFollowRuleSet);
@@ -0,0 +1,245 @@
1
+ import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
2
+ import { TaskError, TaskErrorOrNull, TaskOK, type Task } from "@/packages/mongodb-pipeline-ts/Task";
3
+ import DIE from "@snomiao/die";
4
+ import { sleep } from "bun";
5
+ import { readFile } from "fs/promises";
6
+ import type { WithId } from "mongodb";
7
+ import { basename, dirname } from "path";
8
+ import sflow, { nil } from "sflow";
9
+ import { CNRepos } from "./CNRepos";
10
+ import { GIT_USEREMAIL } from "./GIT_USEREMAIL";
11
+ import { GIT_USERNAME } from "./GIT_USERNAME";
12
+ import { isRepoBypassed } from "./bypassRepos";
13
+ import { $ } from "./cli/echoBunShell";
14
+ import { createGithubForkForRepo } from "./createGithubForkForRepo";
15
+ import { createGithubPullRequest } from "./createGithubPullRequest";
16
+ import { $filaten, $stale, db } from "./db";
17
+ import { getBranchWorkingDir } from "./getBranchWorkingDir";
18
+ import { gh } from "./gh";
19
+ import type { GithubPull } from "./gh/GithubPull";
20
+ import { parseUrlRepoOwner, stringifyGithubOrigin } from "./parseOwnerRepo";
21
+ import { parsePullUrl } from "./parsePullUrl";
22
+ import { parseTitleBodyOfMarkdown } from "./parseTitleBodyOfMarkdown";
23
+
24
+ type LicenseUpdateTask = {
25
+ repository: string;
26
+ tomlUpdated?: boolean;
27
+ prTask: Task<any>;
28
+ updatedAt?: Date;
29
+ };
30
+
31
+ const LicenseTasks = db.collection<LicenseUpdateTask>("LicenseTasks");
32
+ await LicenseTasks.createIndex({ repository: 1 }, { unique: true });
33
+
34
+ if (import.meta.main) {
35
+ // - [Add pyproject.toml for Custom Node Registry by haohaocreates · Pull Request #1 · loopyd/ComfyUI-FD-Tagger]( https://github.com/loopyd/ComfyUI-FD-Tagger/pull/1 )
36
+ // await LicenseTasks.deleteMany({});
37
+ // await _pullTemplate();
38
+ // const testUpstreamRepo = "https://github.com/haohaocreates/ComfyUI-HH-Image-Selector";
39
+ // await _testMakeUpdateTomlLicenseBranch();
40
+ // await _listReposWithNoLicense();
41
+ const testMapping = {
42
+ [`license = "MIT"`]: `license = { text = "MIT" }`,
43
+ [`license = "LICENSE.txt"`]: `license = { file = "LICENSE.txt" }`,
44
+ };
45
+
46
+ const repoExamples = {
47
+ "https://github.com/MuziekMagie/ComfyUI-Matchering": "license already updated",
48
+ // - [ComfyUI_FizzNodes/LICENCE.txt at main · FizzleDorf/ComfyUI_FizzNodes]( https://github.com/FizzleDorf/ComfyUI_FizzNodes/blob/main/LICENCE.txt )
49
+ "https://github.com/FizzleDorf/ComfyUI_FizzNodes": "licenCe",
50
+ };
51
+
52
+ await updateTomlLicenseTasks();
53
+ console.log("ALL DONE");
54
+ }
55
+
56
+ async function _listReposWithNoLicense() {
57
+ console.log(
58
+ await $pipeline(CNRepos)
59
+ .match({ cr: { $exists: true }, "info.data": { $exists: true }, "info.data.license": null })
60
+ .project({ _id: 0, repository: 1, gh_license: "$info.data.license" })
61
+ .aggregate()
62
+ .next(),
63
+ );
64
+ }
65
+
66
+ export async function updateTomlLicenseTasks() {
67
+ // collect tasks
68
+ await $pipeline(CNRepos)
69
+ .match({ cr: { $exists: true } })
70
+ .project({ _id: 0, repository: 1 })
71
+ .merge({ into: LicenseTasks.collectionName, on: "repository" })
72
+ .aggregate()
73
+ .next();
74
+
75
+ // reset retry-able errors with a cd interval
76
+ await LicenseTasks.updateMany($filaten({ prTask: { error: /was submitted too quickly/, mtime: $stale("5m") } }), {
77
+ $unset: { prTask: 1 },
78
+ });
79
+ await LicenseTasks.updateMany($filaten({ prTask: { error: /Missing env.GH_TOKEN_COMFY_PR/, mtime: $stale("1h") } }), {
80
+ $unset: { prTask: 1 },
81
+ });
82
+ console.log((await LicenseTasks.estimatedDocumentCount()) + " license tasks");
83
+
84
+ // update tasks
85
+ await sflow(
86
+ $pipeline(LicenseTasks)
87
+ .match({ tomlUpdated: { $ne: true }, updatedAt: $stale("5m") })
88
+ .as<WithId<LicenseUpdateTask>>()
89
+ .aggregate(),
90
+ )
91
+ .filter(({ repository }) => !isRepoBypassed(repository))
92
+ .pMap(
93
+ async ({ _id, repository }) => {
94
+ const prTask = await createTomlLicensePR(repository).then(TaskOK).catch(TaskError);
95
+ const tomlUpdated = !!TaskErrorOrNull(prTask)?.match("not matched outdated case");
96
+ return await LicenseTasks.findOneAndUpdate(
97
+ { _id },
98
+ { $set: { tomlUpdated, prTask, updatedAt: new Date() } },
99
+ { returnDocument: "after" },
100
+ );
101
+ },
102
+ { concurrency: 3 },
103
+ )
104
+ .forEach(() => sleep(2e3))
105
+ .toLog();
106
+ }
107
+
108
+ async function _testMakeUpdateTomlLicenseBranch() {
109
+ const testUpstreamRepo = "https://github.com/snomiao/comfy-malicious-node-test";
110
+
111
+ const prTask = await createTomlLicensePR(testUpstreamRepo).then(TaskOK).catch(TaskError);
112
+ if (TaskErrorOrNull(prTask)?.match("Not matched outdated case")) console.log("not matched outdated case");
113
+ console.log(prTask);
114
+ console.log("prs_updateTomlLicense PRs DONE");
115
+ }
116
+
117
+ async function createTomlLicensePR(upstreamUrl: string): Promise<GithubPull> {
118
+ const { html_url: forkUrl } = await createGithubForkForRepo(upstreamUrl);
119
+ const branchInfo = await makeUpdateTomlLicenseBranch(upstreamUrl, forkUrl);
120
+ // console.log(forkUrl); // note: this forkUrl may not be final forked url
121
+ console.log({ branchInfo });
122
+ return (
123
+ (await sflow([branchInfo])
124
+ .map(({ upstreamUrl, forkUrl, ...e }) => ({
125
+ ...e,
126
+ srcUrl: forkUrl,
127
+ dstUrl: upstreamUrl,
128
+ }))
129
+ .map(async ({ type, ...prInfo }) => await createGithubPullRequest({ ...prInfo }))
130
+ .forEach((e) => e || DIE("missing pr result"))
131
+ .toOne()) ?? DIE("never")
132
+ );
133
+ }
134
+
135
+ export async function makeUpdateTomlLicenseBranch(upstreamUrl: string, forkUrl: string) {
136
+ const type = "licence-update" as const;
137
+ const branch = "licence-update";
138
+ const tmpl = await readFile("./templates/update-toml-license.md", "utf8");
139
+ const { title, body } = parseTitleBodyOfMarkdown(tmpl);
140
+
141
+ // check forked repo if target branch existed
142
+ const origin = await stringifyGithubOrigin(parseUrlRepoOwner(forkUrl));
143
+ const repo = parseUrlRepoOwner(forkUrl);
144
+ const existedBranch = await gh.repos.getBranch({ ...repo, branch }).catch(() => null);
145
+
146
+ const cwd = await getBranchWorkingDir(upstreamUrl, forkUrl, branch);
147
+ // commit changes
148
+ await $`
149
+ rm -rf ${cwd}
150
+ git clone ${upstreamUrl} ${cwd}
151
+ `;
152
+
153
+ // // also pull from existed forked branch
154
+ // if (existedBranch)
155
+ // await $`
156
+ // cd ${cwd}
157
+ // git pull ${forkUrl} ${branch}
158
+ // `;
159
+
160
+ const pyprojectToml = cwd + "/pyproject.toml";
161
+ const { updated, license } = await pyprojectTomlUpdateLicenses(pyprojectToml, upstreamUrl);
162
+ if (!updated) throw new Error("License field not matched outdated case, skip pr");
163
+
164
+ if (existedBranch) {
165
+ console.log("[debug] skip update already forked branch " + branch);
166
+ return { type, title, body, branch, upstreamUrl, forkUrl, license };
167
+ }
168
+
169
+ // prepare local branch
170
+ await $`
171
+ cd ${cwd}
172
+ git config user.name ${GIT_USERNAME} && \
173
+ git config user.email ${GIT_USEREMAIL} && \
174
+ git checkout -b ${branch} && \
175
+ git add . && \
176
+ git commit -am ${`chore(${branch}): ${title}`}
177
+ `;
178
+
179
+ await $`
180
+ cd ${cwd}
181
+ git push "${origin}" ${branch}:${branch}
182
+ `;
183
+ const branchUrl = `https://github.com/${repo.owner}/${repo.repo}/tree/${branch}`;
184
+ console.log(`Branch Push OK: ${branchUrl}`);
185
+
186
+ return { type, title, body, branch, upstreamUrl, forkUrl, license };
187
+ }
188
+
189
+ export async function pyprojectTomlUpdateLicenses(tomlFile: string, upstreamRepoUrl: string) {
190
+ const raw =
191
+ (await Bun.file(tomlFile).text().catch(nil)) || DIE(new Error("pyproject.toml file not existed or got empty file"));
192
+ const m = raw.match(/^license\s*=(.*)/im);
193
+
194
+ const licenseLine = m?.[0];
195
+ const license = m?.[1]?.trim();
196
+ const outdatedDesiredLicense = license?.match(/^"([^"\n\r]+)"$/i)?.[1];
197
+ const isOutdated = !!outdatedDesiredLicense;
198
+ // const isOutdated = !!raw.match(outdated);
199
+ license && (await LicenseTasks.updateOne({ repository: upstreamRepoUrl }, { $set: { license: license } }));
200
+ if (!licenseLine) throw new Error("no license line was found, please check toml file");
201
+ if (!isOutdated) return { updated: false }; // not outdated
202
+
203
+ let updated: string | null = "";
204
+
205
+ // try load local license file first
206
+ updated ||= await (async function () {
207
+ const desiredLicenseIsFile = !!outdatedDesiredLicense.match(/LICEN[SC]E/);
208
+ if (!desiredLicenseIsFile) return null;
209
+ const licenses = await Array.fromAsync(new Bun.Glob(dirname(tomlFile) + "/LICEN[CS]E*").scan()); // note: LICENCE will be mismatch in this case
210
+ if (licenses.length > 1) DIE(new Error("Multiple license found: " + JSON.stringify(licenses)));
211
+
212
+ const licenseFilename = licenses[0];
213
+ if (!licenseFilename) return null;
214
+ return `license = { file = "${basename(licenseFilename)}" }`;
215
+ })();
216
+
217
+ // - [Writing your pyproject.toml - Python Packaging User Guide]( https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license )
218
+ updated ||= await (async function () {
219
+ const resp = await gh.repos.get({ ...parseUrlRepoOwner(upstreamRepoUrl) });
220
+ const license = resp.data.license;
221
+ if (!license) return null;
222
+ return `license = { text = "${license?.name}" }`;
223
+ })();
224
+
225
+ if (!updated)
226
+ DIE(
227
+ `Fail to get repo license from repo please contact author to create a license file\nMISSING_LICENSE_REPO: ${upstreamRepoUrl}`,
228
+ );
229
+
230
+ const replaced = raw.replace(licenseLine, () => updated);
231
+ if (replaced === raw) DIE(new Error("licenseLine not matched", { cause: { raw, licenseLine, updated } }));
232
+ await LicenseTasks.updateOne({ repository: upstreamRepoUrl }, { $set: { updateLine: updated } });
233
+ await Bun.write(tomlFile, replaced);
234
+ return { updated: true, license: replaced.match(/^license\s*=(.*)/i)?.[1]?.trim() };
235
+ }
236
+
237
+ /** use when template updated */
238
+ async function _pullTemplate() {
239
+ const referenceTemplate = "https://github.com/haohaocreates/ComfyUI-HH-Image-Selector/pull/3";
240
+ const template = await sflow(gh.pulls.get(parsePullUrl(referenceTemplate)))
241
+ .map((e) => e.data.body!)
242
+ .text();
243
+ await Bun.write("./templates/update-toml-license.md", template);
244
+ console.log(template);
245
+ }
@@ -7,49 +7,36 @@ import { readTemplateTitle } from "./readTemplateTitle";
7
7
  export type RelatedPullsWithComments = Awaited<ReturnType<typeof fetchRelatedPullWithComments>>;
8
8
  export type RelatedPull = Awaited<ReturnType<typeof matchRelatedPulls>>[number];
9
9
  export async function matchRelatedPulls(pulls: GithubPullParsed[]): Promise<
10
- (
11
- | {
12
- type: "pyproject";
13
- pull: {
14
- title: string;
15
- number: number;
16
- url: string;
17
- html_url: string;
18
- user: { login: string; html_url: string };
19
- body: string | null;
20
- prState: "closed" | "open" | "merged";
21
- updatedAt: Date;
22
- createdAt: Date;
23
- updated_at: Date;
24
- created_at: Date;
25
- };
26
- }
27
- | {
28
- type: "publishcr";
29
- pull: {
30
- title: string;
31
- number: number;
32
- url: string;
33
- html_url: string;
34
- user: { login: string; html_url: string };
35
- body: string | null;
36
- prState: "closed" | "open" | "merged";
37
- updatedAt: Date;
38
- createdAt: Date;
39
- updated_at: Date;
40
- created_at: Date;
41
- };
42
- }
43
- )[]
10
+ {
11
+ type: "pyproject" | "publishcr" | "licence-update";
12
+ pull: {
13
+ title: string;
14
+ number: number;
15
+ url: string;
16
+ html_url: string;
17
+ user: { login: string; html_url: string };
18
+ body: string | null;
19
+ prState: "closed" | "open" | "merged";
20
+ updatedAt: Date;
21
+ createdAt: Date;
22
+ updated_at: Date;
23
+ created_at: Date;
24
+ };
25
+ }[]
44
26
  > {
45
27
  const pyproject = await readTemplateTitle("add-toml.md");
46
28
  const publishcr = await readTemplateTitle("add-action.md");
29
+ const licenseUpdate = await readTemplateTitle("update-toml-license.md");
47
30
  const relatedPulls = await pMap(pulls, async (pull) =>
48
31
  match(pull)
49
32
  .with({ title: pyproject }, (pull) => ({
50
33
  type: "pyproject" as const,
51
34
  pull,
52
35
  }))
36
+ .with({ title: licenseUpdate }, (pull) => ({
37
+ type: "licence-update" as const,
38
+ pull,
39
+ }))
53
40
  .with({ title: publishcr }, (pull) => ({
54
41
  type: "publishcr" as const,
55
42
  pull,
@@ -7,6 +7,6 @@ export const zPullStatusFollow = z.array(
7
7
  url: z.string(),
8
8
  head: z.string(),
9
9
  comments: z.number(),
10
- lastwords: z.string(),
10
+ lastcomment: z.string(),
11
11
  }),
12
12
  );
@@ -32,7 +32,7 @@ export async function sendEmailAction({
32
32
  .map((e) =>
33
33
  // replace {{var}} s
34
34
  e.replace(
35
- /{{\$([_A-Za-z0-9]+)}}/,
35
+ /{{\$([_A-Za-z0-9]+)}}/g,
36
36
  (_, key: string) =>
37
37
  (payload as any)[key] || DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
38
38
  ),
package/src/sendGmail.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import DIE from "@snomiao/die";
2
- import type { Credentials, OAuth2Client } from "google-auth-library";
2
+ // import type { Credentials } from "google-auth-library";
3
+ import type { Credentials } from "google-auth-library";
3
4
  import { GoogleApis } from "googleapis";
5
+ import type { OAuth2Client } from "googleapis-common";
4
6
  import { createMimeMessage } from "mail-mime-builder";
5
7
  import markdownIt from "markdown-it";
6
8
  import {
@@ -8,12 +10,11 @@ import {
8
10
  getGCloudOAuth2Client,
9
11
  handleGCloudOAuth2Callback,
10
12
  } from "./gcloud/GCloudOAuth2Credentials";
11
-
12
13
  if (import.meta.main) {
13
14
  // send test email
14
15
  const name = "snomiao";
15
16
  const from = "snomiao@gmail.com";
16
- const to = "snomiao@gmail.com";
17
+ const to = ".snomiao@gmail.com";
17
18
  const subject = "Hello! snomiao";
18
19
  const markdown = `
19
20
  # hello from sno
@@ -1,5 +1,5 @@
1
- import pMap from "p-map";
2
1
  import { dissoc } from "rambda";
2
+ import sflow from "sflow";
3
3
  import { match } from "ts-pattern";
4
4
  import { $OK, TaskError, TaskOK } from "../packages/mongodb-pipeline-ts/Task";
5
5
  import { CNRepos } from "./CNRepos";
@@ -16,8 +16,9 @@ if (import.meta.main) {
16
16
 
17
17
  export async function updateCNReposInfo() {
18
18
  await CNRepos.createIndex($filaten({ info: { mtime: 1 } }));
19
- return await pMap(
20
- CNRepos.find($filaten({ info: { mtime: $stale("1d") } })),
19
+ return await sflow(
20
+ CNRepos.find($filaten({ info: { mtime: $stale("1d") } }))
21
+ ).pMap(
21
22
  async (repo) => {
22
23
  const { repository } = repo;
23
24
  console.log("[INFO] Fetching meta info from " + repository);
@@ -53,5 +54,5 @@ export async function updateCNReposInfo() {
53
54
  return await CNRepos.updateOne({ repository: url }, { $set: { info } }, { upsert: true });
54
55
  },
55
56
  { concurrency: 2 },
56
- );
57
+ ).toArray();
57
58
  }