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/matchRelatedPulls.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type RelatedPullsWithComments = Awaited<ReturnType<typeof fetchRelatedPul
|
|
|
8
8
|
export type RelatedPull = Awaited<ReturnType<typeof matchRelatedPulls>>[number];
|
|
9
9
|
export async function matchRelatedPulls(pulls: GithubPullParsed[]): Promise<
|
|
10
10
|
{
|
|
11
|
-
type: "pyproject" | "publishcr" | "licence-update";
|
|
11
|
+
type: "pyproject" | "publishcr" | "licence-update" | "gpl3-license";
|
|
12
12
|
pull: {
|
|
13
13
|
title: string;
|
|
14
14
|
number: number;
|
|
@@ -24,9 +24,10 @@ export async function matchRelatedPulls(pulls: GithubPullParsed[]): Promise<
|
|
|
24
24
|
};
|
|
25
25
|
}[]
|
|
26
26
|
> {
|
|
27
|
-
const pyproject = await readTemplateTitle("add-toml.md");
|
|
28
|
-
const publishcr = await readTemplateTitle("add-action.md");
|
|
29
|
-
const licenseUpdate = await readTemplateTitle("update-toml-license.md");
|
|
27
|
+
const pyproject = await readTemplateTitle("./templates/add-toml.md");
|
|
28
|
+
const publishcr = await readTemplateTitle("./templates/add-action.md");
|
|
29
|
+
const licenseUpdate = await readTemplateTitle("./templates/update-toml-license.md");
|
|
30
|
+
const gpl3License = await readTemplateTitle("./templates/add-gpl3-license.md");
|
|
30
31
|
const relatedPulls = await pMap(pulls, async (pull) =>
|
|
31
32
|
match(pull)
|
|
32
33
|
.with({ title: pyproject }, (pull) => ({
|
|
@@ -41,6 +42,10 @@ export async function matchRelatedPulls(pulls: GithubPullParsed[]): Promise<
|
|
|
41
42
|
type: "publishcr" as const,
|
|
42
43
|
pull,
|
|
43
44
|
}))
|
|
45
|
+
.with({ title: gpl3License }, (pull) => ({
|
|
46
|
+
type: "gpl3-license" as const,
|
|
47
|
+
pull,
|
|
48
|
+
}))
|
|
44
49
|
.otherwise(() => null),
|
|
45
50
|
);
|
|
46
51
|
return relatedPulls.flatMap((e) => (e ? [e] : []));
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
normalizeGithubUrl,
|
|
4
|
+
normalizeGithubUrls,
|
|
5
|
+
normalizeGithubUrlsInObject,
|
|
6
|
+
} from "./normalizeGithubUrl";
|
|
7
|
+
|
|
8
|
+
describe("normalizeGithubUrl", () => {
|
|
9
|
+
test("should normalize comfyanonymous to Comfy-Org", () => {
|
|
10
|
+
const url = "https://github.com/comfyanonymous/ComfyUI/issues/123";
|
|
11
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI/issues/123";
|
|
12
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("should handle already normalized URLs", () => {
|
|
16
|
+
const url = "https://github.com/Comfy-Org/ComfyUI/issues/123";
|
|
17
|
+
expect(normalizeGithubUrl(url)).toBe(url);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("should be case insensitive", () => {
|
|
21
|
+
const url = "https://github.com/ComfyAnonymous/ComfyUI/issues/123";
|
|
22
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI/issues/123";
|
|
23
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("should handle pull request URLs", () => {
|
|
27
|
+
const url = "https://github.com/comfyanonymous/ComfyUI/pull/456";
|
|
28
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI/pull/456";
|
|
29
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should handle comment URLs", () => {
|
|
33
|
+
const url = "https://github.com/comfyanonymous/ComfyUI/issues/123#issuecomment-456789";
|
|
34
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI/issues/123#issuecomment-456789";
|
|
35
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("should handle release URLs", () => {
|
|
39
|
+
const url = "https://github.com/comfyanonymous/ComfyUI/releases/tag/v1.0.0";
|
|
40
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI/releases/tag/v1.0.0";
|
|
41
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should not affect other GitHub repos (Comfy-Org)", () => {
|
|
45
|
+
const url = "https://github.com/Comfy-Org/ComfyUI_frontend/issues/123";
|
|
46
|
+
expect(normalizeGithubUrl(url)).toBe(url);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("should not affect other comfyanonymous repos", () => {
|
|
50
|
+
// Only ComfyUI was migrated — other hypothetical repos under comfyanonymous should be untouched
|
|
51
|
+
const url = "https://github.com/comfyanonymous/some-other-repo/issues/1";
|
|
52
|
+
expect(normalizeGithubUrl(url)).toBe(url);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should handle repo URL without trailing path", () => {
|
|
56
|
+
const url = "https://github.com/comfyanonymous/ComfyUI";
|
|
57
|
+
const expected = "https://github.com/Comfy-Org/ComfyUI";
|
|
58
|
+
expect(normalizeGithubUrl(url)).toBe(expected);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("should handle empty string", () => {
|
|
62
|
+
expect(normalizeGithubUrl("")).toBe("");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should handle non-GitHub URLs", () => {
|
|
66
|
+
const url = "https://example.com/path";
|
|
67
|
+
expect(normalizeGithubUrl(url)).toBe(url);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("normalizeGithubUrls", () => {
|
|
72
|
+
test("should normalize multiple URLs", () => {
|
|
73
|
+
const urls = [
|
|
74
|
+
"https://github.com/comfyanonymous/ComfyUI/issues/123",
|
|
75
|
+
"https://github.com/Comfy-Org/ComfyUI/issues/456",
|
|
76
|
+
"https://github.com/comfyanonymous/ComfyUI/pull/789",
|
|
77
|
+
];
|
|
78
|
+
const expected = [
|
|
79
|
+
"https://github.com/Comfy-Org/ComfyUI/issues/123",
|
|
80
|
+
"https://github.com/Comfy-Org/ComfyUI/issues/456",
|
|
81
|
+
"https://github.com/Comfy-Org/ComfyUI/pull/789",
|
|
82
|
+
];
|
|
83
|
+
expect(normalizeGithubUrls(urls)).toEqual(expected);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should handle empty array", () => {
|
|
87
|
+
expect(normalizeGithubUrls([])).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("normalizeGithubUrlsInObject", () => {
|
|
92
|
+
test("should normalize URL fields in object", () => {
|
|
93
|
+
const obj = {
|
|
94
|
+
sourceIssueUrl: "https://github.com/comfyanonymous/ComfyUI/issues/123",
|
|
95
|
+
targetIssueUrl: "https://github.com/Comfy-Org/ComfyUI_frontend/issues/456",
|
|
96
|
+
otherField: "not a url",
|
|
97
|
+
};
|
|
98
|
+
const result = normalizeGithubUrlsInObject(obj, ["sourceIssueUrl", "targetIssueUrl"]);
|
|
99
|
+
expect(result.sourceIssueUrl).toBe("https://github.com/Comfy-Org/ComfyUI/issues/123");
|
|
100
|
+
expect(result.targetIssueUrl).toBe("https://github.com/Comfy-Org/ComfyUI_frontend/issues/456");
|
|
101
|
+
expect(result.otherField).toBe("not a url");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("should not modify non-URL fields", () => {
|
|
105
|
+
const obj = {
|
|
106
|
+
url: "https://github.com/comfyanonymous/ComfyUI/issues/123",
|
|
107
|
+
count: 42,
|
|
108
|
+
flag: true,
|
|
109
|
+
};
|
|
110
|
+
const result = normalizeGithubUrlsInObject(obj, ["url"]);
|
|
111
|
+
expect(result.url).toBe("https://github.com/Comfy-Org/ComfyUI/issues/123");
|
|
112
|
+
expect(result.count).toBe(42);
|
|
113
|
+
expect(result.flag).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("should handle objects with no URL fields", () => {
|
|
117
|
+
const obj = { name: "test", value: 123 };
|
|
118
|
+
const result = normalizeGithubUrlsInObject(obj, []);
|
|
119
|
+
expect(result).toEqual(obj);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should create a new object (not mutate)", () => {
|
|
123
|
+
const obj = {
|
|
124
|
+
url: "https://github.com/comfyanonymous/ComfyUI/issues/123",
|
|
125
|
+
};
|
|
126
|
+
const result = normalizeGithubUrlsInObject(obj, ["url"]);
|
|
127
|
+
expect(result).not.toBe(obj);
|
|
128
|
+
expect(obj.url).toBe("https://github.com/comfyanonymous/ComfyUI/issues/123");
|
|
129
|
+
expect(result.url).toBe("https://github.com/Comfy-Org/ComfyUI/issues/123");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize GitHub URLs to use the canonical Comfy-Org organization name
|
|
3
|
+
*
|
|
4
|
+
* This ensures that URLs pointing to the same resource but with different
|
|
5
|
+
* organization names (comfyanonymous vs Comfy-Org) are treated as identical.
|
|
6
|
+
*
|
|
7
|
+
* @param url - GitHub URL to normalize
|
|
8
|
+
* @returns Normalized URL with comfyanonymous replaced by Comfy-Org
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* normalizeGithubUrl("https://github.com/comfyanonymous/ComfyUI/issues/123")
|
|
12
|
+
* // => "https://github.com/Comfy-Org/ComfyUI/issues/123"
|
|
13
|
+
*
|
|
14
|
+
* normalizeGithubUrl("https://github.com/Comfy-Org/ComfyUI/issues/123")
|
|
15
|
+
* // => "https://github.com/Comfy-Org/ComfyUI/issues/123"
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeGithubUrl(url: string): string {
|
|
18
|
+
if (!url) return url;
|
|
19
|
+
|
|
20
|
+
// Only ComfyUI was migrated from comfyanonymous to Comfy-Org
|
|
21
|
+
return url.replace(
|
|
22
|
+
/github\.com\/comfyanonymous\/ComfyUI([/?#]|$)/gi,
|
|
23
|
+
"github.com/Comfy-Org/ComfyUI$1",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalize multiple GitHub URLs at once
|
|
29
|
+
*
|
|
30
|
+
* @param urls - Array of GitHub URLs to normalize
|
|
31
|
+
* @returns Array of normalized URLs
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeGithubUrls(urls: string[]): string[] {
|
|
34
|
+
return urls.map(normalizeGithubUrl);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalize GitHub URLs in an object's properties
|
|
39
|
+
*
|
|
40
|
+
* @param obj - Object containing URL properties
|
|
41
|
+
* @param urlFields - Array of field names that contain URLs
|
|
42
|
+
* @returns New object with normalized URLs
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* normalizeGithubUrlsInObject(
|
|
46
|
+
* { sourceIssueUrl: "https://github.com/comfyanonymous/ComfyUI/issues/123" },
|
|
47
|
+
* ["sourceIssueUrl"]
|
|
48
|
+
* )
|
|
49
|
+
* // => { sourceIssueUrl: "https://github.com/Comfy-Org/ComfyUI/issues/123" }
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeGithubUrlsInObject<T extends Record<string, unknown>>(
|
|
52
|
+
obj: T,
|
|
53
|
+
urlFields: (keyof T)[],
|
|
54
|
+
): T {
|
|
55
|
+
const result = { ...obj };
|
|
56
|
+
|
|
57
|
+
for (const field of urlFields) {
|
|
58
|
+
if (typeof result[field] === "string") {
|
|
59
|
+
result[field] = normalizeGithubUrl(result[field] as string) as T[keyof T];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { parseIssueUrl } from "./parseIssueUrl";
|
|
2
|
+
|
|
3
|
+
describe("parseIssueUrl", () => {
|
|
4
|
+
it("should parse a valid pull request URL", () => {
|
|
5
|
+
const url = "https://github.com/owner/repo/pull/123";
|
|
6
|
+
const result = parseIssueUrl(url);
|
|
7
|
+
|
|
8
|
+
expect(result).toEqual({
|
|
9
|
+
owner: "owner",
|
|
10
|
+
repo: "repo",
|
|
11
|
+
issue_number: 123,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should parse a valid issue URL", () => {
|
|
16
|
+
const url = "https://github.com/owner/repo/issues/456";
|
|
17
|
+
const result = parseIssueUrl(url);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
owner: "owner",
|
|
21
|
+
repo: "repo",
|
|
22
|
+
issue_number: 456,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should handle owner and repo names with hyphens", () => {
|
|
27
|
+
const url = "https://github.com/my-org/my-repo/pull/789";
|
|
28
|
+
const result = parseIssueUrl(url);
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
owner: "my-org",
|
|
32
|
+
repo: "my-repo",
|
|
33
|
+
issue_number: 789,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should handle numeric issue numbers correctly", () => {
|
|
38
|
+
const url = "https://github.com/test/test/issues/1";
|
|
39
|
+
const result = parseIssueUrl(url);
|
|
40
|
+
|
|
41
|
+
expect(result).toEqual({
|
|
42
|
+
owner: "test",
|
|
43
|
+
repo: "test",
|
|
44
|
+
issue_number: 1,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle large issue numbers", () => {
|
|
49
|
+
const url = "https://github.com/test/test/pull/999999";
|
|
50
|
+
const result = parseIssueUrl(url);
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
owner: "test",
|
|
54
|
+
repo: "test",
|
|
55
|
+
issue_number: 999999,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should throw an error for invalid URL format", () => {
|
|
60
|
+
const invalidUrl = "https://github.com/owner/repo/invalid/123";
|
|
61
|
+
|
|
62
|
+
expect(() => parseIssueUrl(invalidUrl)).toThrow();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should throw an error for non-GitHub URLs", () => {
|
|
66
|
+
const invalidUrl = "https://gitlab.com/owner/repo/pull/123";
|
|
67
|
+
|
|
68
|
+
expect(() => parseIssueUrl(invalidUrl)).toThrow();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should throw an error for URLs without issue/pull number", () => {
|
|
72
|
+
const invalidUrl = "https://github.com/owner/repo/pull/";
|
|
73
|
+
|
|
74
|
+
expect(() => parseIssueUrl(invalidUrl)).toThrow();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should throw an error for URLs with non-numeric issue numbers", () => {
|
|
78
|
+
const invalidUrl = "https://github.com/owner/repo/pull/abc";
|
|
79
|
+
|
|
80
|
+
expect(() => parseIssueUrl(invalidUrl)).toThrow();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should throw an error for incomplete URLs", () => {
|
|
84
|
+
const invalidUrl = "https://github.com/owner";
|
|
85
|
+
|
|
86
|
+
expect(() => parseIssueUrl(invalidUrl)).toThrow();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should handle URLs with trailing slashes", () => {
|
|
90
|
+
// Note: The current regex doesn't handle trailing slashes, so this should fail
|
|
91
|
+
const url = "https://github.com/owner/repo/pull/123/";
|
|
92
|
+
|
|
93
|
+
expect(() => parseIssueUrl(url)).toThrow();
|
|
94
|
+
});
|
|
95
|
+
});
|
package/src/parseIssueUrl.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import DIE from "@snomiao/die";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse pull or issue url, get issue_number
|
|
5
|
+
*/
|
|
1
6
|
export function parseIssueUrl(issueUrl: string) {
|
|
2
|
-
const
|
|
3
|
-
.match(
|
|
4
|
-
|
|
7
|
+
const m =
|
|
8
|
+
issueUrl.match(
|
|
9
|
+
/^https:\/\/github\.com\/([\w-]+)\/([\w-]+)\/(?:pull|issues)\/(\d+)(?:\/files)?(?:#.*)?$/,
|
|
10
|
+
) || DIE(`Invalid issue URL: ${issueUrl}`);
|
|
11
|
+
const [owner, repo, strNumber] = m.slice(1);
|
|
12
|
+
if (!owner || !repo || !strNumber) {
|
|
13
|
+
throw new Error(`Invalid issue URL: ${issueUrl}`);
|
|
14
|
+
}
|
|
5
15
|
return { owner, repo, issue_number: Number(strNumber) };
|
|
6
16
|
}
|
|
17
|
+
|
|
18
|
+
export function stringifyIssueUrl({ owner, repo, issue_number }: ReturnType<typeof parseIssueUrl>) {
|
|
19
|
+
return `https://github.com/${owner}/${repo}/issues/${issue_number}`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseGithubRepoUrl,
|
|
3
|
+
stringifyGithubOrigin,
|
|
4
|
+
stringifyGithubRepoUrl,
|
|
5
|
+
stringifyOwnerRepo,
|
|
6
|
+
} from "./parseOwnerRepo";
|
|
7
|
+
|
|
8
|
+
describe("parseOwnerRepo", () => {
|
|
9
|
+
describe("parseGithubRepoUrl", () => {
|
|
10
|
+
it("should parse SSH GitHub URL", () => {
|
|
11
|
+
const result = parseGithubRepoUrl("git@github.com:owner/repo");
|
|
12
|
+
expect(result).toEqual({
|
|
13
|
+
owner: "owner",
|
|
14
|
+
repo: "repo",
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should parse SSH GitHub URL with .git extension", () => {
|
|
19
|
+
const result = parseGithubRepoUrl("git@github.com:owner/repo.git");
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
owner: "owner",
|
|
22
|
+
repo: "repo",
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should parse HTTPS GitHub URL", () => {
|
|
27
|
+
const result = parseGithubRepoUrl("https://github.com/owner/repo");
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
owner: "owner",
|
|
30
|
+
repo: "repo",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should parse HTTPS GitHub URL with .git extension", () => {
|
|
35
|
+
const result = parseGithubRepoUrl("https://github.com/owner/repo.git");
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
owner: "owner",
|
|
38
|
+
repo: "repo",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle owner/repo with hyphens and underscores", () => {
|
|
43
|
+
const result = parseGithubRepoUrl("git@github.com:my-org_name/my-repo_name");
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
owner: "my-org_name",
|
|
46
|
+
repo: "my-repo_name",
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should handle owner/repo with numbers", () => {
|
|
51
|
+
const result = parseGithubRepoUrl("https://github.com/user123/repo456");
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
owner: "user123",
|
|
54
|
+
repo: "repo456",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should handle real-world example URLs", () => {
|
|
59
|
+
const result1 = parseGithubRepoUrl("git@github.com:Comfy-Org/ComfyUI-Registry");
|
|
60
|
+
expect(result1).toEqual({
|
|
61
|
+
owner: "Comfy-Org",
|
|
62
|
+
repo: "ComfyUI-Registry",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result2 = parseGithubRepoUrl("https://github.com/snomiao/ComfyUI-FD-Tagger.git");
|
|
66
|
+
expect(result2).toEqual({
|
|
67
|
+
owner: "snomiao",
|
|
68
|
+
repo: "ComfyUI-FD-Tagger",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("stringifyOwnerRepo", () => {
|
|
74
|
+
it("should stringify owner and repo", () => {
|
|
75
|
+
const result = stringifyOwnerRepo({ owner: "owner", repo: "repo" });
|
|
76
|
+
expect(result).toBe("owner/repo");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should handle special characters", () => {
|
|
80
|
+
const result = stringifyOwnerRepo({ owner: "my-org_name", repo: "my-repo_name" });
|
|
81
|
+
expect(result).toBe("my-org_name/my-repo_name");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should handle numbers", () => {
|
|
85
|
+
const result = stringifyOwnerRepo({ owner: "user123", repo: "repo456" });
|
|
86
|
+
expect(result).toBe("user123/repo456");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("stringifyGithubRepoUrl", () => {
|
|
91
|
+
it("should create HTTPS GitHub URL", () => {
|
|
92
|
+
const result = stringifyGithubRepoUrl({ owner: "owner", repo: "repo" });
|
|
93
|
+
expect(result).toBe("https://github.com/owner/repo");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should handle special characters", () => {
|
|
97
|
+
const result = stringifyGithubRepoUrl({ owner: "my-org_name", repo: "my-repo_name" });
|
|
98
|
+
expect(result).toBe("https://github.com/my-org_name/my-repo_name");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle real-world examples", () => {
|
|
102
|
+
const result = stringifyGithubRepoUrl({ owner: "Comfy-Org", repo: "ComfyUI-Registry" });
|
|
103
|
+
expect(result).toBe("https://github.com/Comfy-Org/ComfyUI-Registry");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("stringifyGithubOrigin", () => {
|
|
108
|
+
it("should return SSH URL regardless of token presence", async () => {
|
|
109
|
+
// The function always returns SSH URL according to the current implementation
|
|
110
|
+
const result = await stringifyGithubOrigin({ owner: "owner", repo: "repo" });
|
|
111
|
+
expect(result).toBe("git@github.com:owner/repo");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should handle special characters in owner/repo", async () => {
|
|
115
|
+
const result = await stringifyGithubOrigin({ owner: "my-org_name", repo: "my-repo_name" });
|
|
116
|
+
expect(result).toBe("git@github.com:my-org_name/my-repo_name");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle real-world examples", async () => {
|
|
120
|
+
const result = await stringifyGithubOrigin({ owner: "Comfy-Org", repo: "ComfyUI-Registry" });
|
|
121
|
+
expect(result).toBe("git@github.com:Comfy-Org/ComfyUI-Registry");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("integration tests", () => {
|
|
126
|
+
it("should work end-to-end with parseGithubRepoUrl and stringifyOwnerRepo", () => {
|
|
127
|
+
const originalUrl = "git@github.com:Comfy-Org/ComfyUI-Registry.git";
|
|
128
|
+
const parsed = parseGithubRepoUrl(originalUrl);
|
|
129
|
+
const stringified = stringifyOwnerRepo(parsed);
|
|
130
|
+
expect(stringified).toBe("Comfy-Org/ComfyUI-Registry");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should work end-to-end with parseGithubRepoUrl and stringifyGithubRepoUrl", () => {
|
|
134
|
+
const originalUrl = "git@github.com:snomiao/ComfyUI-FD-Tagger";
|
|
135
|
+
const parsed = parseGithubRepoUrl(originalUrl);
|
|
136
|
+
const httpsUrl = stringifyGithubRepoUrl(parsed);
|
|
137
|
+
expect(httpsUrl).toBe("https://github.com/snomiao/ComfyUI-FD-Tagger");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should work end-to-end with parseGithubRepoUrl and stringifyGithubOrigin", async () => {
|
|
141
|
+
const originalUrl = "https://github.com/owner/repo.git";
|
|
142
|
+
const parsed = parseGithubRepoUrl(originalUrl);
|
|
143
|
+
const sshUrl = await stringifyGithubOrigin(parsed);
|
|
144
|
+
expect(sshUrl).toBe("git@github.com:owner/repo");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
package/src/parseOwnerRepo.ts
CHANGED
|
@@ -2,21 +2,24 @@ import { basename, dirname } from "path";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse owner and repo obj
|
|
5
|
-
* @param gitUrl git@github.
|
|
5
|
+
* @param gitUrl git@github.com:owner/repo or https://github.com/owner/repo
|
|
6
6
|
*/
|
|
7
|
-
export function
|
|
7
|
+
export function parseGithubRepoUrl(gitUrl: string) {
|
|
8
8
|
return {
|
|
9
9
|
owner: basename(dirname(gitUrl.replace(/:/, "/"))),
|
|
10
10
|
repo: basename(gitUrl.replace(/:/, "/")).replace(/\.git$/, ""),
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
export function stringifyOwnerRepo({ owner, repo }: ReturnType<typeof
|
|
13
|
+
export function stringifyOwnerRepo({ owner, repo }: ReturnType<typeof parseGithubRepoUrl>) {
|
|
14
14
|
return owner + "/" + repo;
|
|
15
15
|
}
|
|
16
|
-
export function stringifyGithubRepoUrl({ owner, repo }: ReturnType<typeof
|
|
16
|
+
export function stringifyGithubRepoUrl({ owner, repo }: ReturnType<typeof parseGithubRepoUrl>) {
|
|
17
17
|
return "https://github.com/" + owner + "/" + repo;
|
|
18
18
|
}
|
|
19
|
-
export async function stringifyGithubOrigin({
|
|
19
|
+
export async function stringifyGithubOrigin({
|
|
20
|
+
owner,
|
|
21
|
+
repo,
|
|
22
|
+
}: ReturnType<typeof parseGithubRepoUrl>) {
|
|
20
23
|
const PR_TOKEN = process.env.GH_TOKEN_COMFY_PR;
|
|
21
24
|
if (PR_TOKEN) {
|
|
22
25
|
// fails: maybe permission issue
|
package/src/parsePullsState.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { GithubPull } from "
|
|
2
|
-
import { parsePull } from "
|
|
1
|
+
import type { GithubPull } from "@/lib/github/GithubPull";
|
|
2
|
+
import { parsePull } from "@/lib/github/parsePull";
|
|
3
3
|
export type GithubPullParsed = ReturnType<typeof parsePulls>[number];
|
|
4
4
|
export function parsePulls(data: GithubPull[]) {
|
|
5
5
|
return data.map((e) => parsePull(e));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DIE from "@snomiao/die";
|
|
2
2
|
|
|
3
3
|
export function parseTitleBodyOfMarkdown(tmpl: string) {
|
|
4
|
-
tmpl.startsWith("# ")
|
|
4
|
+
if (!tmpl.startsWith("# ")) DIE("Unrecognized template format:" + tmpl);
|
|
5
5
|
const title = tmpl.split("\n")[0].slice(1).trim();
|
|
6
6
|
const body = tmpl.split("\n").slice(1).join("\n").trim();
|
|
7
7
|
return { title, body };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { gh } from "@/lib/github";
|
|
2
|
+
|
|
3
|
+
type GithubRepo = Awaited<ReturnType<typeof gh.repos.get>>["data"];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pick only the fields we need from a GitHub repository response.
|
|
7
|
+
* This reduces document size from ~5KB to ~300 bytes.
|
|
8
|
+
*
|
|
9
|
+
* Fields kept based on actual usage in codebase:
|
|
10
|
+
* - html_url: repository links
|
|
11
|
+
* - archived: statistics and filtering
|
|
12
|
+
* - default_branch: branch operations
|
|
13
|
+
* - private: access control
|
|
14
|
+
* - owner.login: author tracking
|
|
15
|
+
* - license: license checks
|
|
16
|
+
* - updated_at: activity tracking
|
|
17
|
+
*/
|
|
18
|
+
export function pickRepoInfo(repo: GithubRepo) {
|
|
19
|
+
return {
|
|
20
|
+
html_url: repo.html_url,
|
|
21
|
+
archived: repo.archived,
|
|
22
|
+
default_branch: repo.default_branch,
|
|
23
|
+
private: repo.private,
|
|
24
|
+
updated_at: repo.updated_at,
|
|
25
|
+
owner: {
|
|
26
|
+
login: repo.owner.login,
|
|
27
|
+
},
|
|
28
|
+
license: repo.license
|
|
29
|
+
? {
|
|
30
|
+
spdx_id: repo.license.spdx_id,
|
|
31
|
+
name: repo.license.name,
|
|
32
|
+
}
|
|
33
|
+
: null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type PickedRepoInfo = ReturnType<typeof pickRepoInfo>;
|
package/src/postSlackMessage.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import DIE from "@snomiao/die";
|
|
2
|
-
import {
|
|
2
|
+
import { getSlack } from "@/lib/slack";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated use upsertSlackMessage from gh-desktop-release-notification/upsertSlackMessage.ts
|
|
6
|
+
*/
|
|
4
7
|
export async function postSlackMessage(text: string) {
|
|
5
8
|
const channel = process.env.SLACK_BOT_CHANNEL || DIE(new Error("missing env.SLACK_BOT_CHANNEL"));
|
|
9
|
+
const slack = getSlack();
|
|
6
10
|
// this api will auto retry if failed
|
|
7
11
|
const response = await slack.chat.postMessage({
|
|
8
12
|
channel,
|
package/src/preload.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
void (async () => {
|
|
2
|
+
await Bun.plugin({
|
|
3
|
+
name: "YAML",
|
|
4
|
+
async setup(build) {
|
|
5
|
+
const { load } = await import("js-yaml");
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// when a .yaml file is imported...
|
|
8
|
+
build.onLoad({ filter: /\.(yaml|yml)$/ }, async (args) => {
|
|
9
|
+
// read and parse the file
|
|
10
|
+
const text = await Bun.file(args.path).text();
|
|
11
|
+
const exports = load(text) as Record<string, unknown>;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
13
|
+
// and returns it as a module
|
|
14
|
+
return {
|
|
15
|
+
exports,
|
|
16
|
+
loader: "object", // special loader for JS objects
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
})();
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
23
|
+
// 2025-08-15 seems unnecessary
|
|
24
|
+
// Bun.plugin({
|
|
25
|
+
// name: "preload-plugin",
|
|
26
|
+
// setup(builder) {
|
|
27
|
+
// builder.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
28
|
+
// const text = await Bun.file(args.path).text();
|
|
29
|
+
// // console.log("text", text);
|
|
30
|
+
// return { contents: text, loader: args.loader };
|
|
31
|
+
// });
|
|
32
|
+
// },
|
|
33
|
+
// });
|
package/src/readTemplateTitle.ts
CHANGED
|
@@ -5,7 +5,5 @@ export async function readTemplateTitle(filename: string) {
|
|
|
5
5
|
return await readTemplate(filename).then((e) => e.title);
|
|
6
6
|
}
|
|
7
7
|
export async function readTemplate(filename: string) {
|
|
8
|
-
return readFile(
|
|
9
|
-
parseTitleBodyOfMarkdown,
|
|
10
|
-
);
|
|
8
|
+
return readFile(filename, "utf8").then(parseTitleBodyOfMarkdown);
|
|
11
9
|
}
|