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.
- package/README.md +26 -4
- package/index.ts +6 -0
- package/package.json +43 -26
- package/src/Authors.ts +4 -10
- package/src/CNRepos.ts +6 -3
- package/src/EmailTasks.ts +4 -3
- package/src/addCommentAction.ts +1 -1
- package/src/analyzePullsStatus.ts +36 -10
- package/src/bypassRepos.spec.ts +10 -0
- package/src/bypassRepos.ts +8 -0
- package/src/checkPRsFailures.ts +18 -0
- package/src/clone_modify_push_Branches.ts +12 -12
- package/src/createComfyRegistryPullRequests.ts +19 -9
- package/src/createGithubForkForRepo.ts +25 -11
- package/src/createGithubPullRequest.ts +114 -51
- package/src/createIssueComment.ts +8 -3
- package/src/followRuleSchema.ts +2 -1
- package/src/ghUser.ts +3 -0
- package/src/index.ts +2 -0
- package/src/makeUpdateTomlLicenseBranch.ts +245 -0
- package/src/matchRelatedPulls.ts +21 -34
- package/src/pullStatusFollowSchema.ts +1 -1
- package/src/sendEmailAction.ts +1 -1
- package/src/sendGmail.ts +4 -3
- package/src/updateCNReposInfo.ts +5 -4
- package/src/updateCNReposPulls.ts +14 -11
- package/src/updateFollowRuleSet.ts +0 -1
- package/src/updateOutdatedPullsTemplates.ts +5 -0
- package/tailwind.config.ts +68 -13
- package/CHANGELOG.md +0 -385
|
@@ -1,22 +1,67 @@
|
|
|
1
|
-
import DIE from "@snomiao/die";
|
|
2
|
-
import
|
|
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
|
|
30
|
-
head: src.owner
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
72
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
title: e.title,
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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("
|
|
30
|
+
console.log("+ IssueComment " + comment.html_url);
|
|
26
31
|
return { comment, comments: [...comments, comment] };
|
|
27
32
|
})();
|
|
28
33
|
return result;
|
package/src/followRuleSchema.ts
CHANGED
|
@@ -50,11 +50,12 @@ const zFollowUpRule = z.object({
|
|
|
50
50
|
created_at: mDate,
|
|
51
51
|
comments_author: mString,
|
|
52
52
|
head: mString,
|
|
53
|
-
|
|
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
|
+
}
|
package/src/matchRelatedPulls.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|
package/src/sendEmailAction.ts
CHANGED
|
@@ -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
|
|
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
|
package/src/updateCNReposInfo.ts
CHANGED
|
@@ -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
|
|
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
|
}
|