Skip to content

Commit 8c15c9d

Browse files
committed
feat(gh-desktop-release-notification): enhance Slack message handling for GitHub release notifications by adding status and upsert functionality
feat(upsertSlackMessage): create a new module to handle Slack message upsert operations for better code organization and reusability fix(gh-desktop-release-notification): update slackMessage format to include release status for clearer notifications
1 parent 7ce8094 commit 8c15c9d

File tree

2 files changed

+108
-126
lines changed

2 files changed

+108
-126
lines changed
Lines changed: 93 additions & 126 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,102 @@ 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

2824
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-
}
25+
url: string; // github release url
26+
version?: string; // released version, e.g. v1.0.0, v2.0.0-beta.1
27+
createdAt: Date;
28+
releasedAt?: Date;
29+
isStable?: boolean; // true if the release is stable, false if it's a pre-release
30+
status: "draft" | "prerelease" | "stable";
31+
32+
// drafted/pre-release message
33+
slackMessage?: {
34+
text: string;
35+
channel: string;
36+
url?: string; // set after sent
37+
};
38+
};
3939

40-
const GithubReleaseNotificationTask = db.collection<GithubReleaseNotificationTask>('GithubReleaseNotificationTask')
41-
await GithubReleaseNotificationTask.createIndex({ url: 1 }, { unique: true })
42-
const save = async (task: GithubReleaseNotificationTask) => await GithubReleaseNotificationTask.findOneAndUpdate(
40+
const GithubReleaseNotificationTask = db.collection<GithubReleaseNotificationTask>("GithubReleaseNotificationTask");
41+
await GithubReleaseNotificationTask.createIndex({ url: 1 }, { unique: true });
42+
const save = async (task: { url: string } & Partial<GithubReleaseNotificationTask>) =>
43+
(await GithubReleaseNotificationTask.findOneAndUpdate(
4344
{ url: task.url },
4445
{ $set: task },
45-
{ upsert: true, returnDocument: 'after' }
46-
) || DIE('never')
46+
{ upsert: true, returnDocument: "after" },
47+
)) || DIE("never");
4748

4849
if (import.meta.main) {
49-
await runGithubDesktopReleaseNotificationTask()
50-
if (isCI) {
51-
await db.close()
52-
process.exit(0); // exit if running in CI
53-
}
50+
await runGithubDesktopReleaseNotificationTask();
51+
if (isCI) {
52+
await db.close();
53+
process.exit(0); // exit if running in CI
54+
}
5455
}
5556

5657
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
58+
const pSlackChannelId = getSlackChannel(config.slackChannel).then(
59+
(e) => e.id || DIE(`unable to get slack channel ${config.slackChannel}`),
60+
);
9361

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
62+
await sflow(config.repos)
63+
.map(parseUrlRepoOwner)
64+
.flatMap(({ owner, repo }) =>
65+
gh.repos
66+
.listReleases({
67+
owner,
68+
repo,
69+
per_page: 3,
14470
})
145-
.log()
146-
.run()
71+
.then((e) => e.data),
72+
)
73+
.map(async (release) => {
74+
const url = release.html_url;
75+
const status = release.draft ? "draft" : release.prerelease ? "prerelease" : "stable";
76+
77+
// create task
78+
let task = await save({
79+
url,
80+
status: status,
81+
isStable: status == "stable",
82+
version: release.tag_name,
83+
createdAt: new Date(release.created_at || DIE("no created_at in release, " + JSON.stringify(release))),
84+
releasedAt: !release.published_at ? undefined : new Date(release.published_at),
85+
});
86+
87+
if (+task.createdAt! < +new Date(config.sendSince)) return task; // skip releases before the sendSince date
88+
89+
const newSlackMessage = {
90+
channel: await pSlackChannelId,
91+
text: config.slackMessage
92+
.replace("{url}", task.url)
93+
.replace("{repo}", parseGithubUrl(task.url)?.repo || DIE(`unable parse REPO from URL ${task.url}`))
94+
.replace("{version}", task.version || DIE(`unable to parse version from task ${JSON.stringify(task)}`))
95+
.replace("{status}", task.status),
96+
};
97+
98+
const anyExistedMsg = task.slackMessage;
99+
100+
// upsert message if new/changed
101+
const shouldSendMessage = task.isStable || anyExistedMsg?.url;
102+
if (shouldSendMessage && anyExistedMsg?.text?.trim() !== newSlackMessage.text.trim()) {
103+
console.log(
104+
anyExistedMsg?.text !== newSlackMessage.text,
105+
JSON.stringify(anyExistedMsg?.text),
106+
JSON.stringify(newSlackMessage.text),
107+
);
108+
task = await save({ url, slackMessage: await upsertSlackMessage(newSlackMessage) });
109+
}
110+
return task;
111+
})
112+
.log()
113+
.run();
147114
}
148115

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

0 commit comments

Comments
 (0)