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" ;
11
10
// workflow
12
11
/**
13
12
* 1. fetch repos latest releases
@@ -16,134 +15,102 @@ import isCI from "is-ci"
16
15
* 4. if it's a pre-release, do nothing
17
16
*/
18
17
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
+ } ;
27
23
28
24
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
+ } ;
39
39
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 (
43
44
{ url : task . url } ,
44
45
{ $set : task } ,
45
- { upsert : true , returnDocument : ' after' }
46
- ) || DIE ( ' never' )
46
+ { upsert : true , returnDocument : " after" } ,
47
+ ) ) || DIE ( " never" ) ;
47
48
48
49
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
+ }
54
55
}
55
56
56
57
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 : / h t t p s : \/ \/ c o m f y - o r g a n i z a t i o n \. s l a c k \. c o m \/ .* / ,
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
+ ) ;
93
61
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 ,
144
70
} )
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 ( ) ;
147
114
}
148
115
149
116
export default runGithubDesktopReleaseNotificationTask ;
0 commit comments