comfy-pr 0.2.26 → 1.0.1
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 +258 -157
- package/bot/IdleWaiter.ts +31 -0
- package/bot/RestartManager.spec.ts +163 -0
- package/bot/RestartManager.ts +190 -0
- package/bot/WorkingTasksManager.spec.ts +154 -0
- package/bot/cli.ts +1171 -0
- package/bot/codesearch-ai-wip.ts +351 -0
- package/bot/error-collector.ts +133 -0
- package/bot/index.ts +14 -0
- package/bot/restart-example.ts +99 -0
- package/bot/slack-bolt.ts +688 -0
- package/bot/slack-bot.ts +1575 -0
- package/bot/state.ts +19 -0
- package/bot/templateLoader.test.ts +84 -0
- package/bot/templateLoader.ts +92 -0
- package/next.config.ts +26 -0
- package/package.json +161 -41
- package/post-summary.ts +44 -0
- package/src/Authors.ts +2 -2
- package/src/CMNodes.ts +3 -3
- package/src/CNRepos.ts +30 -7
- package/src/CRNodes.ts +11 -9
- package/src/CRPulls.ts +3 -0
- package/src/EmailTasks.ts +27 -9
- package/src/FORK_OWNER.ts +3 -1
- package/src/FollowRules.ts +3 -3
- package/src/GithubIssueComments.ts +1 -1
- package/src/Totals.ts +9 -8
- package/src/WorkerInstances.ts +31 -8
- package/src/addCommentAction.ts +19 -11
- package/src/analyzePullsStatus.ts +39 -17
- package/src/analyzeTotals.ts +19 -11
- package/src/bypassRepos.ts +6 -5
- package/src/checkPRsFailures.ts +13 -8
- package/src/cli.test.ts +2 -0
- package/src/cli.ts +1 -1
- package/src/constants.ts +2 -0
- package/src/createComfyRegistryPRsFromCandidates.ts +20 -12
- package/src/createComfyRegistryPullRequests.ts +35 -10
- package/src/createGithubForkForRepo.ts +36 -11
- package/src/createGithubPullRequest.ts +83 -33
- package/src/createIssueComment.ts +4 -4
- package/src/fetchComfyRegistryNodes.ts +34 -4
- package/src/fetchCurrentGeoInfo.ts +3 -1
- package/src/fetchRelatedPullWithComments.ts +1 -1
- package/src/fetchRepoDescriptionMap.ts +1 -4
- package/src/followRuleSchema.ts +10 -4
- package/src/getBranchWorkingDir.ts +3 -7
- package/src/getRepoWorkingDir.ts +2 -3
- package/src/gh.spec.ts +571 -0
- package/src/ghData.ts +13 -0
- package/src/ghPageFlow.ts +33 -0
- package/src/ghSubscriber.ts +76 -0
- package/src/ghUser.ts +28 -4
- package/src/index.ts +13 -4
- package/src/initializeFollowRules.ts +11 -4
- package/src/logger.spec.ts +95 -0
- package/src/logger.ts +46 -0
- package/src/makeGpl3LicenseBranch.ts +66 -0
- package/src/makePublishBranch.ts +21 -15
- package/src/makeTomlBranch.ts +48 -14
- package/src/makeUpdateTomlLicenseBranch.ts +40 -44
- package/src/matchRelatedPulls.ts +9 -4
- package/src/normalizeGithubUrl.spec.ts +131 -0
- package/src/normalizeGithubUrl.ts +64 -0
- package/src/parseIssueUrl.spec.ts +95 -0
- package/src/parseIssueUrl.ts +17 -3
- package/src/parseOwnerRepo.spec.ts +147 -0
- package/src/parseOwnerRepo.ts +8 -5
- package/src/parsePullsState.ts +2 -2
- package/src/parseTitleBodyOfMarkdown.ts +1 -1
- package/src/pickRepoInfo.ts +37 -0
- package/src/postSlackMessage.ts +5 -1
- package/src/preload.ts +30 -27
- package/src/readTemplateTitle.ts +1 -3
- package/src/sendEmailAction.ts +14 -10
- package/src/sendGmail.ts +3 -3
- package/src/updateAuthorsForGithub.ts +53 -35
- package/src/updateAuthorsFromCNRepo.ts +12 -5
- package/src/updateCMNodesDuplicationWarnings.ts +14 -9
- package/src/updateCMRepos.ts +1 -1
- package/src/updateCNRepos.ts +14 -14
- package/src/updateCNReposCRPullsComments.ts +7 -5
- package/src/updateCNReposInfo.ts +49 -40
- package/src/updateCNReposPRCandidate.ts +3 -3
- package/src/updateCNReposPulls.ts +9 -5
- package/src/updateCNReposPullsDashboard.ts +11 -9
- package/src/updateCNReposRelatedPulls.ts +3 -3
- package/src/updateCRNodes.ts +24 -5
- package/src/updateCRRepos.ts +1 -1
- package/src/updateComfyTotals.ts +7 -5
- package/src/updateFollowRuleSet.ts +17 -9
- package/src/updateOutdatedPullsTemplates.ts +61 -48
- package/src/updateSlackMessages.ts +8 -4
- package/tailwind.config.ts +7 -1
- package/next-env.d.ts +0 -5
- package/src/GIT_USEREMAIL.ts +0 -5
- package/src/GIT_USERNAME.ts +0 -9
- package/src/clone_modify_push_Branches.ts +0 -21
- package/src/tomlFillDescription.ts +0 -17
package/src/CNRepos.ts
CHANGED
|
@@ -4,15 +4,14 @@ bun index.ts
|
|
|
4
4
|
*/
|
|
5
5
|
import type { ObjectId, WithId } from "mongodb";
|
|
6
6
|
import "react-hook-form";
|
|
7
|
-
import { nil } from "sflow";
|
|
8
7
|
import { type Task } from "../packages/mongodb-pipeline-ts/Task";
|
|
9
8
|
import { type CMNode } from "./CMNodes";
|
|
10
9
|
import { type CRNode } from "./CRNodes";
|
|
11
10
|
import { db } from "./db";
|
|
12
|
-
import { gh } from "
|
|
11
|
+
import { gh } from "@/lib/github";
|
|
13
12
|
import { type RelatedPull } from "./matchRelatedPulls";
|
|
14
13
|
import type { GithubPullParsed } from "./parsePullsState";
|
|
15
|
-
import { type SlackMsg } from "
|
|
14
|
+
import { type SlackMsg } from "@/lib/slack/SlackMsgs";
|
|
16
15
|
|
|
17
16
|
type Email = {
|
|
18
17
|
from?: string;
|
|
@@ -21,7 +20,7 @@ type Email = {
|
|
|
21
20
|
to: string;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
type
|
|
23
|
+
type _Sent = {
|
|
25
24
|
slack?: SlackMsg;
|
|
26
25
|
emails?: Email[];
|
|
27
26
|
};
|
|
@@ -39,7 +38,9 @@ export type CRPull = RelatedPull & {
|
|
|
39
38
|
};
|
|
40
39
|
// TODO: refactor into interface to improve performance
|
|
41
40
|
export type CustomNodeRepo = {
|
|
41
|
+
// github url
|
|
42
42
|
repository: string;
|
|
43
|
+
|
|
43
44
|
info?: Task<Pick<GithubRepo, "html_url" | "archived" | "default_branch" | "private">>;
|
|
44
45
|
|
|
45
46
|
// author?: Author;
|
|
@@ -48,7 +49,9 @@ export type CustomNodeRepo = {
|
|
|
48
49
|
cr?: Pick<WithId<CRNode>, "_id" | "id" | "name">;
|
|
49
50
|
|
|
50
51
|
cr_ids?: ObjectId[];
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
on_registry?: Task<boolean>; // check if cr_ids is not empty, exclude publisher.id === ADMIN
|
|
54
|
+
on_registry_all?: Task<boolean>; // check if cr_ids is not empty, including publisher.id === ADMIN
|
|
52
55
|
|
|
53
56
|
/** @deprecated use cm_ids */
|
|
54
57
|
cm?: Pick<WithId<CMNode>, "_id" | "id" | "title">;
|
|
@@ -66,13 +69,33 @@ export type CustomNodeRepo = {
|
|
|
66
69
|
// createFork?: Task<GithubRepo>;
|
|
67
70
|
// createBranches?: Task<{ type: CRType; assigned: Worker } & PushedBranch>[];
|
|
68
71
|
|
|
69
|
-
/** @deprecated use CRPulls.pull */
|
|
72
|
+
/** TODO: @deprecated use CRPulls.pull */
|
|
70
73
|
createdPulls?: Task<GithubPullParsed[]>;
|
|
71
74
|
};
|
|
72
75
|
export type CNRepo = CustomNodeRepo;
|
|
73
76
|
export const CNRepos = db.collection<CNRepo>("CNRepos");
|
|
74
77
|
|
|
75
|
-
CNRepos.createIndex({ repository: 1 }, { unique: true }).catch(
|
|
78
|
+
CNRepos.createIndex({ repository: 1 }, { unique: true }).catch(() => {});
|
|
79
|
+
|
|
80
|
+
// Performance optimization: compound index for complex state + mtime queries
|
|
81
|
+
// This index improves queries filtering by multiple state and mtime fields
|
|
82
|
+
// Target query: aggregate with $match on crPulls.state, info.state, and various mtime fields
|
|
83
|
+
CNRepos.createIndex(
|
|
84
|
+
{
|
|
85
|
+
"crPulls.state": 1,
|
|
86
|
+
"info.state": 1,
|
|
87
|
+
"crPulls.mtime": 1,
|
|
88
|
+
"info.mtime": 1,
|
|
89
|
+
"candidate.mtime": 1,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "idx_states_mtimes",
|
|
93
|
+
background: true,
|
|
94
|
+
},
|
|
95
|
+
).catch(() => {});
|
|
96
|
+
|
|
97
|
+
// Note: pulls.mtime index is created by setup-performance-indexes.ts script
|
|
98
|
+
// Run: bun run db:setup-indexes
|
|
76
99
|
|
|
77
100
|
// fix cr null, it should be not exists
|
|
78
101
|
// await CNRepos.updateMany({ cr: null as unknown as WithId<CRNode> }, { $unset: { cr: 1 } });
|
package/src/CRNodes.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
|
|
2
2
|
import type { ObjectId } from "mongodb";
|
|
3
|
-
import pMap from "p-map";
|
|
4
3
|
import { peekYaml } from "peek-log";
|
|
4
|
+
import { sf } from "sflow";
|
|
5
5
|
import { CNRepos } from "./CNRepos";
|
|
6
6
|
import { db } from "./db";
|
|
7
7
|
import { fetchCRNodes } from "./fetchComfyRegistryNodes";
|
|
8
|
-
import { type SlackMsg } from "
|
|
8
|
+
import { type SlackMsg } from "@/lib/slack/SlackMsgs";
|
|
9
9
|
|
|
10
10
|
export type CRNode = Awaited<ReturnType<typeof fetchCRNodes>>[number] & {
|
|
11
11
|
sent?: { slack?: SlackMsg };
|
|
@@ -16,12 +16,14 @@ await CRNodes.createIndex({ id: 1 }, { unique: true });
|
|
|
16
16
|
await CRNodes.createIndex({ repository: 1 }, { unique: false }); // WARN: duplicate is allowed
|
|
17
17
|
|
|
18
18
|
if (import.meta.main) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
// peek cr nodes
|
|
20
|
+
const r = await sf
|
|
21
|
+
.sflow(
|
|
22
|
+
$pipeline(CNRepos)
|
|
23
|
+
.match({ cr: { $exists: true } })
|
|
24
|
+
.replaceRoot({ newRoot: "$cr" })
|
|
25
|
+
.aggregate(),
|
|
26
|
+
)
|
|
27
|
+
.toArray();
|
|
26
28
|
peekYaml({ r, len: r.length });
|
|
27
29
|
}
|
package/src/CRPulls.ts
CHANGED
package/src/EmailTasks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DIE from "@snomiao/die";
|
|
2
2
|
import markdownIt from "markdown-it";
|
|
3
3
|
import type { WithId } from "mongodb";
|
|
4
|
-
import { sf } from "
|
|
4
|
+
import { sf } from "sflow";
|
|
5
5
|
import type { z } from "zod";
|
|
6
6
|
import { $stale, db } from "./db";
|
|
7
7
|
import type { zSendEmailAction } from "./followRuleSchema";
|
|
@@ -25,10 +25,10 @@ export const EmailTasks = db.collection<EmailTask>("EmailTasks");
|
|
|
25
25
|
await EmailTasks.createIndex({ from: 1, to: 1, subject: 1 });
|
|
26
26
|
|
|
27
27
|
if (import.meta.main) {
|
|
28
|
-
sf(EmailTasks.find({}))
|
|
28
|
+
sf.sflow(EmailTasks.find({}))
|
|
29
29
|
.map((e) => e.state)
|
|
30
30
|
.toLog();
|
|
31
|
-
// sf(EmailTasks.watch([], { fullDocument: "whenAvailable" }))
|
|
31
|
+
// sf.sflow(EmailTasks.watch([], { fullDocument: "whenAvailable" }))
|
|
32
32
|
// // .filter((c) => c.operationType === "modify")
|
|
33
33
|
// .toLog()
|
|
34
34
|
// .then(() => console.log("all done"));
|
|
@@ -54,7 +54,7 @@ if (import.meta.main) {
|
|
|
54
54
|
* deduplicated by [from, to, subject].join(' ')
|
|
55
55
|
*/
|
|
56
56
|
export async function enqueueEmailTask(task: z.infer<typeof zSendEmailAction>) {
|
|
57
|
-
const { name, from, to, subject, body, provider } = task;
|
|
57
|
+
const { name: _name, from, to, subject, body: _body, provider: _provider } = task;
|
|
58
58
|
return (
|
|
59
59
|
(await EmailTasks.findOneAndUpdate(
|
|
60
60
|
{ from, to, subject },
|
|
@@ -64,8 +64,9 @@ export async function enqueueEmailTask(task: z.infer<typeof zSendEmailAction>) {
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
export async function updateEmailTasks() {
|
|
67
|
-
const count = await sf
|
|
68
|
-
.
|
|
67
|
+
const count = await sf
|
|
68
|
+
.sflow(EmailTasks.find({ state: "waiting" }))
|
|
69
|
+
.merge(sf.sflow(EmailTasks.find({ state: "sending", mtime: $stale("1m") })))
|
|
69
70
|
.map(async (e) => {
|
|
70
71
|
const { _id, state } = e;
|
|
71
72
|
if (state === "waiting") {
|
|
@@ -73,21 +74,38 @@ export async function updateEmailTasks() {
|
|
|
73
74
|
return await sendEmailTask(e)
|
|
74
75
|
.then(() => EmailTasks.updateOne({ _id }, { $set: { mtime: new Date(), state: "sent" } }))
|
|
75
76
|
.catch((err) =>
|
|
76
|
-
EmailTasks.updateOne(
|
|
77
|
+
EmailTasks.updateOne(
|
|
78
|
+
{ _id },
|
|
79
|
+
{ $set: { mtime: new Date(), state: "error", error: String(err) } },
|
|
80
|
+
),
|
|
77
81
|
);
|
|
78
82
|
}
|
|
79
83
|
})
|
|
80
84
|
.toCount();
|
|
81
85
|
console.log(count, "email tasks processed");
|
|
82
86
|
}
|
|
83
|
-
export async function sendEmailTask({
|
|
87
|
+
export async function sendEmailTask({
|
|
88
|
+
_id,
|
|
89
|
+
state: _state,
|
|
90
|
+
name,
|
|
91
|
+
from,
|
|
92
|
+
to,
|
|
93
|
+
subject,
|
|
94
|
+
body,
|
|
95
|
+
provider,
|
|
96
|
+
}: WithId<EmailTask>) {
|
|
84
97
|
// prerequisites
|
|
85
98
|
if (provider !== "google") DIE("providers other than google is implemented yet");
|
|
86
99
|
const auth = await getGCloudOAuth2Client({
|
|
87
100
|
email: from,
|
|
88
101
|
scope: ["https://www.googleapis.com/auth/gmail.compose"],
|
|
89
102
|
authorize: async (url) =>
|
|
90
|
-
DIE(
|
|
103
|
+
DIE(
|
|
104
|
+
"ERROR: Trying send email from " +
|
|
105
|
+
from +
|
|
106
|
+
" but it's not authorized, plz grant permission in " +
|
|
107
|
+
url,
|
|
108
|
+
),
|
|
91
109
|
});
|
|
92
110
|
// do send
|
|
93
111
|
return sendGmail({
|
package/src/FORK_OWNER.ts
CHANGED
|
@@ -2,4 +2,6 @@ import DIE from "@snomiao/die";
|
|
|
2
2
|
import { ghUser } from "./ghUser";
|
|
3
3
|
|
|
4
4
|
export const FORK_OWNER =
|
|
5
|
-
process.env.FORK_OWNER?.replace(/"/g, "")?.trim() ||
|
|
5
|
+
process.env.FORK_OWNER?.replace(/"/g, "")?.trim() ||
|
|
6
|
+
(await ghUser()).login ||
|
|
7
|
+
DIE("Missing env.FORK_OWNER");
|
package/src/FollowRules.ts
CHANGED
|
@@ -15,11 +15,11 @@ export type FollowRuleSet = {
|
|
|
15
15
|
matched?: Task<PullsStatus>;
|
|
16
16
|
action_results?: {
|
|
17
17
|
name: string;
|
|
18
|
-
action:
|
|
19
|
-
result: Task<
|
|
18
|
+
action: unknown;
|
|
19
|
+
result: Task<unknown>;
|
|
20
20
|
}[];
|
|
21
21
|
enabled?: boolean;
|
|
22
22
|
yamlWhenEnabled?: string;
|
|
23
23
|
};
|
|
24
24
|
export const FollowRuleSets = db.collection<FollowRuleSet>("FollowRuleSets");
|
|
25
|
-
|
|
25
|
+
FollowRuleSets.createIndex("name", { unique: true }).catch(() => null);
|
package/src/Totals.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
|
|
2
2
|
import { flatten } from "flat";
|
|
3
|
-
import { sf } from "
|
|
3
|
+
import { sf } from "sflow";
|
|
4
4
|
import { type Task } from "../packages/mongodb-pipeline-ts/Task";
|
|
5
5
|
import { analyzeTotals } from "./analyzeTotals";
|
|
6
6
|
import { db } from "./db";
|
|
@@ -12,13 +12,14 @@ export const Totals = db.collection<{
|
|
|
12
12
|
totals?: Task<Totals>;
|
|
13
13
|
}>("Totals");
|
|
14
14
|
if (import.meta.main) {
|
|
15
|
-
sf
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
await sf
|
|
16
|
+
.sflow(
|
|
17
|
+
$pipeline(Totals)
|
|
18
|
+
.match({ "totals.data": { $exists: true } })
|
|
19
|
+
.set({ "totals.data.date": "$totals.mtime" })
|
|
20
|
+
.replaceRoot({ newRoot: "$totals.data" })
|
|
21
|
+
.aggregate(),
|
|
22
|
+
)
|
|
22
23
|
.map((e) => flatten(e) as { date: string } & Record<string, number>)
|
|
23
24
|
.toLog();
|
|
24
25
|
}
|
package/src/WorkerInstances.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { getMAC } from "@ctrl/mac-address";
|
|
|
2
2
|
import { defer } from "lodash-es";
|
|
3
3
|
import md5 from "md5";
|
|
4
4
|
import type { WithId } from "mongodb";
|
|
5
|
-
import
|
|
5
|
+
import sflow from "sflow";
|
|
6
|
+
import { $fresh, db } from "./db";
|
|
6
7
|
import { fetchCurrentGeoInfo } from "./fetchCurrentGeoInfo";
|
|
7
8
|
import { createInstanceId } from "./utils/createInstanceId";
|
|
9
|
+
import { yaml } from "./utils/yaml";
|
|
10
|
+
|
|
8
11
|
export type GeoInfo = Awaited<ReturnType<typeof fetchCurrentGeoInfo>>;
|
|
9
12
|
export type WorkerInstance = {
|
|
10
13
|
/** id: rand */
|
|
@@ -14,26 +17,43 @@ export type WorkerInstance = {
|
|
|
14
17
|
geo: GeoInfo;
|
|
15
18
|
workerId: string;
|
|
16
19
|
task?: string;
|
|
20
|
+
tasks?: string[];
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
const k = "COMFY_PR_WorkerInstanceKey";
|
|
20
|
-
|
|
24
|
+
const g = globalThis as typeof globalThis & { [k]: string };
|
|
21
25
|
function getWorkerInstanceId() {
|
|
22
26
|
// ensure only one instance
|
|
23
|
-
if (!
|
|
27
|
+
if (!g[k])
|
|
24
28
|
defer(async function () {
|
|
25
29
|
await Promise.all([postWorkerHeartBeatLoop(), watchWorkerInstancesLoop()]);
|
|
26
30
|
});
|
|
27
|
-
const instanceId = (
|
|
31
|
+
const instanceId = (g[k] ??= createInstanceId());
|
|
28
32
|
return instanceId;
|
|
29
33
|
}
|
|
30
34
|
export const WorkerInstances = db.collection<WorkerInstance>("WorkerInstances");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
let _geoPromise: Promise<GeoInfo> | undefined;
|
|
36
|
+
function getGeoPromise() {
|
|
37
|
+
if (!_geoPromise) {
|
|
38
|
+
_geoPromise = fetchCurrentGeoInfo();
|
|
39
|
+
}
|
|
40
|
+
return _geoPromise;
|
|
41
|
+
}
|
|
34
42
|
|
|
35
43
|
if (import.meta.main) {
|
|
44
|
+
await WorkerInstances.createIndex({ id: 1 }, { unique: true });
|
|
45
|
+
await WorkerInstances.createIndex({ ip: 1 });
|
|
36
46
|
console.log(await getWorkerInstance());
|
|
47
|
+
console.log(
|
|
48
|
+
await sflow(
|
|
49
|
+
WorkerInstances.watch([{ $match: { up: $fresh("5min") } }], {
|
|
50
|
+
fullDocument: "whenAvailable",
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
.map((e) => yaml.stringify(e))
|
|
54
|
+
.log()
|
|
55
|
+
.run(),
|
|
56
|
+
);
|
|
37
57
|
}
|
|
38
58
|
|
|
39
59
|
async function postWorkerHeartBeatLoop() {
|
|
@@ -75,9 +95,12 @@ export async function getWorkerInstance(task?: string) {
|
|
|
75
95
|
id,
|
|
76
96
|
active: new Date(),
|
|
77
97
|
workerId: getWorkerId(),
|
|
78
|
-
geo: await
|
|
98
|
+
geo: await getGeoPromise(),
|
|
79
99
|
...(task && { task }),
|
|
80
100
|
},
|
|
101
|
+
$addToSet: {
|
|
102
|
+
...(task && { tasks: task }),
|
|
103
|
+
},
|
|
81
104
|
$setOnInsert: { up: new Date() },
|
|
82
105
|
},
|
|
83
106
|
{ upsert: true, returnDocument: "after" },
|
package/src/addCommentAction.ts
CHANGED
|
@@ -8,11 +8,11 @@ import type { z } from "zod";
|
|
|
8
8
|
import type { PullStatusShown } from "./analyzePullsStatus";
|
|
9
9
|
import { CNRepos, type CRPull } from "./CNRepos";
|
|
10
10
|
import { createIssueComment } from "./createIssueComment";
|
|
11
|
-
import { $
|
|
11
|
+
import { $flatten } from "./db";
|
|
12
12
|
import type { zAddCommentAction } from "./followRuleSchema";
|
|
13
13
|
import { ghUser } from "./ghUser";
|
|
14
14
|
import type { GithubIssueComment } from "./GithubIssueComments";
|
|
15
|
-
import { notifySlackLinks } from "
|
|
15
|
+
import { notifySlackLinks } from "@/lib/slack/notifySlackLinks";
|
|
16
16
|
|
|
17
17
|
export async function addCommentAction({
|
|
18
18
|
matched,
|
|
@@ -32,14 +32,15 @@ export async function addCommentAction({
|
|
|
32
32
|
action: "add-comment",
|
|
33
33
|
url: payload.url,
|
|
34
34
|
by: action.by,
|
|
35
|
-
body: action.body.replace(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
body: action.body.replace(/{{\$([_A-Za-z0-9]+)}}/g, (_, key: string) =>
|
|
36
|
+
String(
|
|
37
|
+
(payload as Record<string, unknown>)[key] ??
|
|
38
|
+
DIE("Missing key: " + key + " in payload: " + JSON.stringify(payload)),
|
|
39
|
+
),
|
|
39
40
|
),
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
if (runAction && loadedAction.by === ghUser.login) {
|
|
43
|
+
if (runAction && loadedAction.by === (await ghUser()).login) {
|
|
43
44
|
const existedCommentsTask =
|
|
44
45
|
(await $pipeline(CNRepos)
|
|
45
46
|
.unwind("$crPulls.data")
|
|
@@ -48,7 +49,8 @@ export async function addCommentAction({
|
|
|
48
49
|
.replaceRoot({ newRoot: "$crPulls.data.comments" })
|
|
49
50
|
.as<Task<GithubIssueComment[]>>()
|
|
50
51
|
.aggregate()
|
|
51
|
-
.next()) ??
|
|
52
|
+
.next()) ??
|
|
53
|
+
DIE("comments is not fetched before, plz check " + loadedAction.url + " in CNRepos");
|
|
52
54
|
|
|
53
55
|
const existedComments =
|
|
54
56
|
TaskDataOrNull(existedCommentsTask) ??
|
|
@@ -56,13 +58,19 @@ export async function addCommentAction({
|
|
|
56
58
|
const existedComment = existedComments.find((e) => e.body === loadedAction.body);
|
|
57
59
|
|
|
58
60
|
if (!existedComment) {
|
|
59
|
-
const { comments, comment } = await createIssueComment(
|
|
61
|
+
const { comments, comment } = await createIssueComment(
|
|
62
|
+
loadedAction.url,
|
|
63
|
+
loadedAction.body,
|
|
64
|
+
loadedAction.by,
|
|
65
|
+
);
|
|
60
66
|
const updateResult = await CNRepos.updateOne(
|
|
61
|
-
$
|
|
67
|
+
$flatten({ crPulls: { data: $elemMatch({ pull: { html_url: loadedAction.url } }) } }),
|
|
62
68
|
{ $set: { "crPulls.data.$.comments": comments } },
|
|
63
69
|
);
|
|
64
70
|
if (!updateResult.matchedCount) DIE("created issue not matched");
|
|
65
|
-
await notifySlackLinks("A New issue comment are created from rule " + rule.name, [
|
|
71
|
+
await notifySlackLinks("A New issue comment are created from rule " + rule.name, [
|
|
72
|
+
comment.html_url,
|
|
73
|
+
]);
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $pipeline, type Pipeline } from "@/packages/mongodb-pipeline-ts/$pipeline";
|
|
2
2
|
import type { ObjectId } from "mongodb";
|
|
3
3
|
import prettyMs from "pretty-ms";
|
|
4
|
-
import {
|
|
4
|
+
import { sflow } from "sflow";
|
|
5
5
|
import type { z } from "zod";
|
|
6
6
|
import type { Task } from "../packages/mongodb-pipeline-ts/Task";
|
|
7
7
|
import type { Author } from "./Authors";
|
|
@@ -14,7 +14,7 @@ import type { zPullStatus } from "./zod/zPullsStatus";
|
|
|
14
14
|
// import { $pipeline } from "./db/$pipeline";
|
|
15
15
|
// in case of dump production in local environment:
|
|
16
16
|
// bun --env-file .env.production.local src/dump.ts > dump.csv
|
|
17
|
-
export const DashboardDetails = db.collection<
|
|
17
|
+
export const DashboardDetails = db.collection<Record<string, unknown>>("DashboardDetails");
|
|
18
18
|
if (import.meta.main) {
|
|
19
19
|
// const r = peekYaml(await analyzePullsStatus());
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ if (import.meta.main) {
|
|
|
26
26
|
// await writeFile("src/zPullsStatus.ts", jsonToZod(await analyzePullsStatus({ limit: 1 }), "zPullsStatus", true));
|
|
27
27
|
|
|
28
28
|
// analyzePullsStatusPipeline
|
|
29
|
-
await
|
|
29
|
+
await sflow(analyzePullsStatusPipeline().aggregate())
|
|
30
30
|
.filter((e) => e.email)
|
|
31
31
|
.limit(2)
|
|
32
32
|
.map((e) => yaml.stringify({ e }))
|
|
@@ -40,23 +40,37 @@ export type PullStatus = z.infer<typeof zPullStatus>;
|
|
|
40
40
|
export type PullsStatus = PullStatus[];
|
|
41
41
|
export type PullStatusShown = Awaited<ReturnType<typeof analyzePullsStatus>>[number];
|
|
42
42
|
|
|
43
|
-
export async function analyzePullsStatus({
|
|
43
|
+
export async function analyzePullsStatus({
|
|
44
|
+
skip = 0,
|
|
45
|
+
limit = 0,
|
|
46
|
+
pipeline = analyzePullsStatusPipeline(),
|
|
47
|
+
} = {}) {
|
|
44
48
|
"use server";
|
|
45
49
|
return await pipeline
|
|
46
50
|
.skip(skip)
|
|
47
51
|
.limit(limit || 2 ** 31 - 1)
|
|
48
52
|
.aggregate()
|
|
49
|
-
.map(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
.map(
|
|
54
|
+
({
|
|
55
|
+
updated_at,
|
|
56
|
+
created_at: _created_at,
|
|
57
|
+
actived_at,
|
|
58
|
+
on_registry_at: _on_registry_at,
|
|
59
|
+
...pull
|
|
60
|
+
}) => {
|
|
61
|
+
const pull_updated =
|
|
62
|
+
prettyMs(+new Date() - +new Date(updated_at), { compact: true }) + " ago";
|
|
63
|
+
const repo_updated =
|
|
64
|
+
prettyMs(+new Date() - +new Date(actived_at), { compact: true }) + " ago";
|
|
65
|
+
return {
|
|
66
|
+
updated: pull_updated, // @deprecated
|
|
67
|
+
pull_updated,
|
|
68
|
+
repo_updated,
|
|
69
|
+
...pull,
|
|
70
|
+
lastcomment: pull.lastcomment?.replace(/\s+/g, " ").replace(/\*\*\*.*/g, "..."),
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
)
|
|
60
74
|
.toArray();
|
|
61
75
|
}
|
|
62
76
|
export function baseCRPullStatusPipeline(): Pipeline<
|
|
@@ -71,7 +85,11 @@ export function baseCRPullStatusPipeline(): Pipeline<
|
|
|
71
85
|
return (
|
|
72
86
|
$pipeline(CNRepos)
|
|
73
87
|
// get latest pr comments time
|
|
74
|
-
.set({
|
|
88
|
+
.set({
|
|
89
|
+
"crPulls.data.pull.latest_comment_at": {
|
|
90
|
+
$max: { $max: "$crPulls.data.comments.data.updated_at" },
|
|
91
|
+
},
|
|
92
|
+
})
|
|
75
93
|
// unwind
|
|
76
94
|
.stage({ $unwind: "$crPulls.data" })
|
|
77
95
|
.match({ "crPulls.data.comments.data": { $exists: true } })
|
|
@@ -145,7 +163,11 @@ export function analyzePullsStatusPipeline(): Pipeline<{
|
|
|
145
163
|
.project({ authors: 0 })
|
|
146
164
|
|
|
147
165
|
.set({ lastcomment: { $arrayElemAt: ["$comments", -1] } })
|
|
148
|
-
.set({
|
|
166
|
+
.set({
|
|
167
|
+
lastcomment: {
|
|
168
|
+
$ifNull: [{ $concat: ["$lastcomment.user.login", ": ", "$lastcomment.body"] }, ""],
|
|
169
|
+
},
|
|
170
|
+
})
|
|
149
171
|
|
|
150
172
|
// calculate update_at max( )
|
|
151
173
|
// date format convert
|
package/src/analyzeTotals.ts
CHANGED
|
@@ -4,8 +4,9 @@ import promiseAllProperties from "promise-all-properties";
|
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
import { CMNodes } from "./CMNodes";
|
|
6
6
|
import { CNRepos } from "./CNRepos";
|
|
7
|
+
import { UNCLAIMED_ADMIN_PUBLISHER_ID } from "./constants";
|
|
7
8
|
import { CRNodes } from "./CRNodes";
|
|
8
|
-
import { $
|
|
9
|
+
import { $flatten } from "./db";
|
|
9
10
|
import { tLog } from "./utils/tLog";
|
|
10
11
|
|
|
11
12
|
if (import.meta.main) {
|
|
@@ -24,23 +25,30 @@ export async function analyzeTotals() {
|
|
|
24
25
|
"Total Nodes": promiseAllProperties({
|
|
25
26
|
"on ComfyUI Manager": CMNodes.estimatedDocumentCount(),
|
|
26
27
|
"on Registry": CRNodes.estimatedDocumentCount(),
|
|
28
|
+
"on Registry (exclude unclaimed)": CRNodes.countDocuments({
|
|
29
|
+
"publisher.id": { $ne: UNCLAIMED_ADMIN_PUBLISHER_ID },
|
|
30
|
+
} as Record<string, unknown>),
|
|
27
31
|
}),
|
|
28
32
|
"Total Repos": $pipeline(CNRepos)
|
|
29
33
|
.group({
|
|
30
34
|
_id: null,
|
|
31
|
-
"on Comfy Manager List": {
|
|
35
|
+
"on Comfy Manager List": {
|
|
36
|
+
$sum: { $cond: [{ $eq: [{ $type: "$cm" }, "missing"] }, 0, 1] },
|
|
37
|
+
},
|
|
32
38
|
"on Registry": { $sum: { $cond: [{ $eq: [{ $type: "$cr" }, "missing"] }, 0, 1] } },
|
|
33
39
|
Archived: { $sum: { $cond: ["$info.data.archived", 1, 0] } },
|
|
34
40
|
All: { $sum: 1 },
|
|
35
41
|
// Candidates: { $sum: { $cond: ["$candidate.data", 1, 0] } },
|
|
36
|
-
"Got ERROR on creating PR": {
|
|
42
|
+
"Got ERROR on creating PR": {
|
|
43
|
+
$sum: { $cond: [{ $eq: ["$createdPulls.state", "error"] }, 1, 0] },
|
|
44
|
+
},
|
|
37
45
|
})
|
|
38
46
|
.project({ _id: 0 })
|
|
39
47
|
.aggregate()
|
|
40
|
-
// .map((e:
|
|
48
|
+
// .map((e: unknown) => e.pairs)
|
|
41
49
|
.next(),
|
|
42
50
|
"Total Authors": $pipeline(CNRepos)
|
|
43
|
-
.match($
|
|
51
|
+
.match($flatten({ info: { data: { owner: { login: { $exists: true } } } } }))
|
|
44
52
|
.group({
|
|
45
53
|
_id: "$info.data.owner.login",
|
|
46
54
|
// author: "$info.data.owner.login",
|
|
@@ -57,7 +65,7 @@ export async function analyzeTotals() {
|
|
|
57
65
|
})
|
|
58
66
|
.project({ _id: 0 })
|
|
59
67
|
.aggregate()
|
|
60
|
-
// .map((e:
|
|
68
|
+
// .map((e: unknown) => e.pairs)
|
|
61
69
|
.next(),
|
|
62
70
|
"Total PRs Made": $pipeline(CNRepos)
|
|
63
71
|
.unwind("$crPulls.data")
|
|
@@ -66,7 +74,7 @@ export async function analyzeTotals() {
|
|
|
66
74
|
.set({ id_total: [["$_id", "$total"]] })
|
|
67
75
|
.group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
|
|
68
76
|
.aggregate()
|
|
69
|
-
.map((e:
|
|
77
|
+
.map((e: unknown) => (e as { pairs?: unknown }).pairs)
|
|
70
78
|
.next(),
|
|
71
79
|
"Total Open": $pipeline(CNRepos)
|
|
72
80
|
.unwind("$crPulls.data")
|
|
@@ -76,7 +84,7 @@ export async function analyzeTotals() {
|
|
|
76
84
|
.set({ id_total: [["$_id", "$total"]] })
|
|
77
85
|
.group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
|
|
78
86
|
.aggregate()
|
|
79
|
-
.map((e:
|
|
87
|
+
.map((e: unknown) => (e as { pairs?: unknown }).pairs)
|
|
80
88
|
.next(),
|
|
81
89
|
"Total Merged (on Registry)": $pipeline(CNRepos)
|
|
82
90
|
.unwind("$crPulls.data")
|
|
@@ -86,7 +94,7 @@ export async function analyzeTotals() {
|
|
|
86
94
|
.set({ id_total: [["$_id", "$total"]] })
|
|
87
95
|
.group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
|
|
88
96
|
.aggregate()
|
|
89
|
-
.map((e:
|
|
97
|
+
.map((e: unknown) => (e as { pairs?: unknown }).pairs)
|
|
90
98
|
.next(),
|
|
91
99
|
"Total Merged (not on Registry)": $pipeline(CNRepos)
|
|
92
100
|
.unwind("$crPulls.data")
|
|
@@ -96,7 +104,7 @@ export async function analyzeTotals() {
|
|
|
96
104
|
.set({ id_total: [["$_id", "$total"]] })
|
|
97
105
|
.group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
|
|
98
106
|
.aggregate()
|
|
99
|
-
.map((e:
|
|
107
|
+
.map((e: unknown) => (e as { pairs?: unknown }).pairs)
|
|
100
108
|
.next(),
|
|
101
109
|
"Total Closed": $pipeline(CNRepos)
|
|
102
110
|
.unwind("$crPulls.data")
|
|
@@ -106,7 +114,7 @@ export async function analyzeTotals() {
|
|
|
106
114
|
.set({ id_total: [["$_id", "$total"]] })
|
|
107
115
|
.group({ _id: null, pairs: { $mergeObjects: { $arrayToObject: "$id_total" } } })
|
|
108
116
|
.aggregate()
|
|
109
|
-
.map((e:
|
|
117
|
+
.map((e: unknown) => (e as { pairs?: unknown }).pairs)
|
|
110
118
|
.next(),
|
|
111
119
|
|
|
112
120
|
// // Follow Rules
|
package/src/bypassRepos.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { yaml } from "./utils/yaml";
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export const { ignore_repos } = z
|
|
5
|
+
.object({
|
|
6
|
+
ignore_repos: z.string().array(),
|
|
7
|
+
})
|
|
8
|
+
.parse(yaml.parse(await Bun.file("./comfypr-ignore.yaml").text()));
|
|
9
|
+
export const isRepoBypassed = (repo: string) => ignore_repos.some((reg) => repo.match(reg));
|