Skip to content

Commit 962ebb5

Browse files
authored
Merge pull request #49 from Comfy-Org/sno-slack-release-msg
feat: enhance Slack message handling for GitHub release notifications
2 parents 7ce8094 + 00935db commit 962ebb5

File tree

3 files changed

+151
-127
lines changed

3 files changed

+151
-127
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { GithubReleaseNotificationTask } from ".";
2+
3+
if (import.meta.main) {
4+
console.log(await GithubReleaseNotificationTask.find().toArray());
5+
}
Lines changed: 121 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { db } from "@/src/db"
2-
import { gh } from "@/src/gh"
3-
import { parseUrlRepoOwner } from "@/src/parseOwnerRepo"
4-
import DIE from "@snomiao/die"
5-
import sflow from "sflow"
6-
import parseGithubUrl from "parse-github-url"
7-
import { slack } from "@/src/slack"
8-
import { getSlackChannel } from "@/src/slack/channels"
9-
import { slackMessageUrlParse, slackMessageUrlStringify } from "../gh-design/gh-design"
10-
import isCI from "is-ci"
1+
import { db } from "@/src/db";
2+
import { gh } from "@/src/gh";
3+
import { parseUrlRepoOwner } from "@/src/parseOwnerRepo";
4+
import { getSlackChannel } from "@/src/slack/channels";
5+
import DIE from "@snomiao/die";
6+
import isCI from "is-ci";
7+
import parseGithubUrl from "parse-github-url";
8+
import sflow from "sflow";
9+
import { upsertSlackMessage } from "./upsertSlackMessage";
1110
// workflow
1211
/**
1312
* 1. fetch repos latest releases
@@ -16,134 +15,129 @@ import isCI from "is-ci"
1615
* 4. if it's a pre-release, do nothing
1716
*/
1817
const config = {
19-
repos: [
20-
'https://github.com/comfyanonymous/ComfyUI',
21-
'https://github.com/Comfy-Org/desktop',
22-
],
23-
slackChannel: 'desktop',
24-
slackMessage: '🔮 {repo} <{url}|Release {version}> is stable! ',
25-
sendSince: new Date('2025-08-02T00:00:00Z').toISOString(), // only send notifications for releases after this date (UTC)
26-
}
18+
repos: ["https://github.com/comfyanonymous/ComfyUI", "https://github.com/Comfy-Org/desktop"],
19+
slackChannel: "desktop",
20+
slackMessage: "🔮 {repo} <{url}|Release {version}> is {status}!",
21+
sendSince: new Date("2025-08-02T00:00:00Z").toISOString(), // only send notifications for releases after this date (UTC)
22+
};
2723

28-
type GithubReleaseNotificationTask = {
29-
url: string, // github release url
30-
version?: string, // released version, e.g. v1.0.0, v2.0.0-beta.1
31-
releasedAt?: Date,
32-
isStable?: boolean, // true if the release is stable, false if it's a pre-release
33-
slackMessage?: {
34-
text: string
35-
channel: string
36-
url?: string // set after sent
37-
}
38-
}
24+
const coreVersionPattern = /Update ComfyUI core to (v\S+)/;
25+
export type GithubReleaseNotificationTask = {
26+
url: string; // github release url
27+
version?: string; // released version, e.g. v1.0.0, v2.0.0-beta.1
28+
coreVersion?: string; // for desktop repo, match /Update ComfyUI core to (v\S+)/
29+
createdAt: Date;
30+
releasedAt?: Date;
31+
isStable?: boolean; // true if the release is stable, false if it's a pre-release
32+
status: "draft" | "prerelease" | "stable";
33+
34+
// when it's drafting/pre-release
35+
slackMessageDrafting?: {
36+
text: string;
37+
channel: string;
38+
url?: string; // set after sent
39+
};
40+
41+
// send when it's stable, will reply the drafting url if there are one
42+
slackMessage?: {
43+
text: string;
44+
channel: string;
45+
url?: string; // set after sent
46+
};
47+
};
3948

40-
const GithubReleaseNotificationTask = db.collection<GithubReleaseNotificationTask>('GithubReleaseNotificationTask')
41-
await GithubReleaseNotificationTask.createIndex({ url: 1 }, { unique: true })
42-
const save = async (task: GithubReleaseNotificationTask) => await GithubReleaseNotificationTask.findOneAndUpdate(
49+
export const GithubReleaseNotificationTask = db.collection<GithubReleaseNotificationTask>(
50+
"GithubReleaseNotificationTask",
51+
);
52+
await GithubReleaseNotificationTask.createIndex({ url: 1 }, { unique: true });
53+
const save = async (task: { url: string } & Partial<GithubReleaseNotificationTask>) =>
54+
(await GithubReleaseNotificationTask.findOneAndUpdate(
4355
{ url: task.url },
4456
{ $set: task },
45-
{ upsert: true, returnDocument: 'after' }
46-
) || DIE('never')
57+
{ upsert: true, returnDocument: "after" },
58+
)) || DIE("never");
4759

4860
if (import.meta.main) {
49-
await runGithubDesktopReleaseNotificationTask()
50-
if (isCI) {
51-
await db.close()
52-
process.exit(0); // exit if running in CI
53-
}
61+
await runGithubDesktopReleaseNotificationTask();
62+
if (isCI) {
63+
await db.close();
64+
process.exit(0); // exit if running in CI
65+
}
5466
}
5567

5668
async function runGithubDesktopReleaseNotificationTask() {
57-
const pSlackChannelId = getSlackChannel(config.slackChannel).then(e => e.id || DIE(`unable to get slack channel ${config.slackChannel}`))
58-
// patch
59-
await GithubReleaseNotificationTask.deleteMany({
60-
url: /https:\/\/comfy-organization\.slack\.com\/.*/,
61-
})
62-
await GithubReleaseNotificationTask.findOneAndUpdate({
63-
version: "v0.4.60"
64-
}, {
65-
$set: {
66-
// channel: "C07H3GLKDPF",
67-
slackMessage: {
68-
url: "https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1754217152324349",
69-
channel: await pSlackChannelId,
70-
text: 'TODO',
71-
}
72-
}
73-
})
74-
75-
await sflow(config.repos)
76-
.map(parseUrlRepoOwner)
77-
.flatMap(({ owner, repo }) => gh.repos.listReleases({
78-
owner,
79-
repo,
80-
per_page: 3,
81-
}).then(e => e.data))
82-
.map(async (release) => {
83-
const url = release.html_url
84-
// create task
85-
let task = await save({
86-
url,
87-
isStable: !release.prerelease,
88-
version: release.tag_name,
89-
releasedAt: new Date(release.published_at || DIE('no published_at in release')),
90-
})
91-
if (!task.isStable) return task // not a stable release, skip
92-
if (+task.releasedAt! < +new Date(config.sendSince)) return task // skip releases before the sendSince date
69+
const pSlackChannelId = getSlackChannel(config.slackChannel).then(
70+
(e) => e.id || DIE(`unable to get slack channel ${config.slackChannel}`),
71+
);
9372

94-
const draftSlackMessage = {
95-
channel: config.slackChannel,
96-
text: config.slackMessage
97-
.replace('{url}', task.url)
98-
.replace('{repo}', parseGithubUrl(task.url)?.repo || DIE(`unable parse REPO from URL ${task.url}`))
99-
.replace('{version}', task.version || DIE(`unable to parse version from task ${JSON.stringify(task)}`)),
100-
}
101-
console.log(task)
102-
if (task.slackMessage?.url) {
103-
// already notified, check if we need to update the text
104-
if (task.slackMessage.text !== draftSlackMessage.text) {
105-
// update message content
106-
const ts = slackMessageUrlParse(task.slackMessage.url).ts
107-
console.log(draftSlackMessage)
108-
// console.log('slack.chat.update: ')
109-
const msg = await slack.chat.update({
110-
channel: task.slackMessage.channel,
111-
ts,
112-
text: draftSlackMessage.text,
113-
})
114-
// save updated message
115-
task = await save({
116-
url,
117-
slackMessage: {
118-
...task.slackMessage!,
119-
url: slackMessageUrlStringify({ channel: task.slackMessage!.channel, ts: msg.ts || DIE('missing ts in edited slack message') }),
120-
text: draftSlackMessage.text!, // update text
121-
}
122-
}) // save the task with updated slack message
123-
} else {
124-
// no need to update, pass
125-
}
126-
} else {
127-
// not yet notified, send a new message
128-
const channel = await pSlackChannelId
129-
// notify the slack channel
130-
const msg = await slack.chat.postMessage({
131-
text: draftSlackMessage.text,
132-
channel,
133-
})
134-
const slackUrl = slackMessageUrlStringify({ channel, ts: msg.ts! })
135-
task = await save({
136-
url,
137-
slackMessage: {
138-
...draftSlackMessage,
139-
url: slackUrl, // set the url after sent
140-
}
141-
}) // save the task with slack message
142-
}
143-
return task
73+
await sflow(config.repos)
74+
.map(parseUrlRepoOwner)
75+
.flatMap(({ owner, repo }) =>
76+
gh.repos
77+
.listReleases({
78+
owner,
79+
repo,
80+
per_page: 3,
14481
})
145-
.log()
146-
.run()
82+
.then((e) => e.data),
83+
)
84+
.map(async (release) => {
85+
const url = release.html_url;
86+
const status = release.draft ? "draft" : release.prerelease ? "prerelease" : "stable";
87+
88+
// create task
89+
let task = await save({
90+
url,
91+
status: status,
92+
isStable: status == "stable",
93+
version: release.tag_name,
94+
coreVersion: (release.body || release.body_text)?.match(coreVersionPattern)?.[1],
95+
createdAt: new Date(release.created_at || DIE("no created_at in release, " + JSON.stringify(release))),
96+
releasedAt: !release.published_at ? undefined : new Date(release.published_at),
97+
});
98+
const coreTask = !task.coreVersion
99+
? undefined
100+
: await GithubReleaseNotificationTask.findOne({ version: task.coreVersion });
101+
102+
if (+task.createdAt! < +new Date(config.sendSince)) return task; // skip releases before the sendSince date
103+
104+
const newSlackMessage = {
105+
channel: await pSlackChannelId,
106+
text: config.slackMessage
107+
.replace("{url}", task.url)
108+
.replace("{repo}", parseGithubUrl(task.url)?.repo || DIE(`unable parse REPO from URL ${task.url}`))
109+
.replace("{version}", task.version || DIE(`unable to parse version from task ${JSON.stringify(task)}`))
110+
.replace("{status}", task.status),
111+
};
112+
113+
// upsert drafting message if new/changed
114+
const shouldSendDraftingMessage = !task.isStable || task.slackMessageDrafting?.url;
115+
if (shouldSendDraftingMessage && task.slackMessage?.text?.trim() !== newSlackMessage.text.trim()) {
116+
task = await save({
117+
url,
118+
slackMessage: await upsertSlackMessage({
119+
...newSlackMessage,
120+
replyUrl: coreTask?.slackMessageDrafting?.url,
121+
}),
122+
});
123+
}
124+
125+
// upsert stable message if new/changed
126+
const shouldSendMessage = task.isStable || task.slackMessage?.url;
127+
if (shouldSendMessage && task.slackMessage?.text?.trim() !== newSlackMessage.text.trim()) {
128+
task = await save({
129+
url,
130+
slackMessage: await upsertSlackMessage({
131+
...newSlackMessage,
132+
replyUrl:
133+
coreTask?.slackMessageDrafting?.url || coreTask?.slackMessage?.url || task.slackMessageDrafting?.url,
134+
}),
135+
});
136+
}
137+
return task;
138+
})
139+
.log()
140+
.run();
147141
}
148142

149143
export default runGithubDesktopReleaseNotificationTask;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { slack } from "@/src/slack";
2+
import { slackMessageUrlParse, slackMessageUrlStringify } from "../gh-design/gh-design";
3+
4+
export async function upsertSlackMessage({
5+
text,
6+
channel,
7+
url,
8+
replyUrl,
9+
}: {
10+
text: string;
11+
channel: string;
12+
url?: string;
13+
replyUrl?: string;
14+
}) {
15+
if (process.env.DRY_RUN) throw new Error("sending slack message: " + JSON.stringify({ text, channel, url }));
16+
if (!url) {
17+
const thread_ts = !replyUrl ? undefined : slackMessageUrlParse(replyUrl).ts;
18+
const msg = await slack.chat.postMessage({ text, channel, thread_ts });
19+
const url = slackMessageUrlStringify({ channel, ts: msg.ts! });
20+
return { ...msg, url, text, channel };
21+
}
22+
const ts = slackMessageUrlParse(url).ts;
23+
const msg = await slack.chat.update({ text, channel, ts });
24+
return { ...msg, url, text, channel };
25+
}

0 commit comments

Comments
 (0)