@@ -2,7 +2,6 @@ package deploy
22
33import (
44 "fmt"
5- "regexp"
65 "strings"
76 "time"
87
@@ -20,13 +19,15 @@ const (
2019 prCommentRowMarkerFmt = "<!-- row:%s:%s -->"
2120)
2221
23- // rowPattern matches a full table row line that starts with a row marker.
24- var rowPattern = regexp .MustCompile (`(?m)^\| <!-- row:\S+ --> .+\|$` )
22+ // findResult bundles the comment ID and body for restate.Run serialisation.
23+ type findResult struct {
24+ ID int64
25+ Body string
26+ }
2527
2628// prCommentReporter creates and updates a shared PR comment with one row per
27- // deployment, similar to the Vercel deployment comment. Multiple deploy
28- // workflows running concurrently for the same PR each manage their own row.
29- // All GitHub API calls are fire-and-forget.
29+ // app/env combination. Multiple deploy workflows for the same PR each manage
30+ // their own row. All GitHub API calls are fire-and-forget.
3031type prCommentReporter struct {
3132 github githubclient.GitHubClient
3233 installationID int64
@@ -40,8 +41,8 @@ type prCommentReporter struct {
4041 logURL string
4142 environmentURL string
4243
43- prNumber int // resolved lazily
44- commentID int64 // set after Create
44+ prNumber int
45+ commentID int64
4546}
4647
4748type prCommentReporterConfig struct {
@@ -74,8 +75,6 @@ func newPRCommentReporter(cfg prCommentReporterConfig) *prCommentReporter {
7475 }
7576}
7677
77- // Create looks up the PR, finds or creates the shared comment, and adds this
78- // deployment's row.
7978func (r * prCommentReporter ) Create (ctx restate.ObjectSharedContext ) {
8079 if r .installationID == 0 || r .repo == "" || r .branch == "" {
8180 return
@@ -92,99 +91,79 @@ func (r *prCommentReporter) Create(ctx restate.ObjectSharedContext) {
9291 }
9392 r .prNumber = prNumber
9493
95- // Look for an existing deployment comment on this PR.
9694 existing , err := restate .Run (ctx , func (_ restate.RunContext ) (findResult , error ) {
97- id , body , err := r .github .FindBotComment (r .installationID , r .repo , r .prNumber , prCommentMainMarker )
98- return findResult {ID : id , Body : body }, err
95+ id , body , findErr := r .github .FindBotComment (r .installationID , r .repo , r .prNumber , prCommentMainMarker )
96+ return findResult {ID : id , Body : body }, findErr
9997 }, restate .WithName ("find existing deploy comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
10098 if err != nil {
10199 logger .Error ("failed to search for existing deploy comment" , "error" , err )
102100 }
103101
104- row := r .buildRow ("⏳ Queued" )
102+ row := r .buildRow ("Queued" )
105103
106104 if existing .ID != 0 {
107- // Add or replace our row in the existing comment.
108105 r .commentID = existing .ID
109106 body := r .upsertRow (existing .Body , row )
110-
111107 _ = restate .RunVoid (ctx , func (_ restate.RunContext ) error {
112108 return r .github .UpdateIssueComment (r .installationID , r .repo , r .commentID , body )
113- }, restate .WithName ("add row to existing deploy comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
109+ }, restate .WithName ("add row to deploy comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
114110 return
115111 }
116112
117- // No existing comment — create one.
118113 body := r .buildFullComment (row )
119- commentID , err := restate .Run (ctx , func (_ restate.RunContext ) (int64 , error ) {
114+ commentID , createErr := restate .Run (ctx , func (_ restate.RunContext ) (int64 , error ) {
120115 return r .github .CreateIssueComment (r .installationID , r .repo , r .prNumber , body )
121- }, restate .WithName ("create PR deployment comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
122- if err != nil {
123- logger .Error ("failed to create PR comment" , "error" , err , "pr" , r .prNumber )
116+ }, restate .WithName ("create deploy comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
117+ if createErr != nil {
118+ logger .Error ("failed to create PR comment" , "error" , createErr , "pr" , r .prNumber )
124119 return
125120 }
126121 r .commentID = commentID
127122}
128123
129- // findResult is a helper to return both ID and Body from restate.Run.
130- type findResult struct {
131- ID int64
132- Body string
133- }
134-
135- // Report updates this deployment's row in the shared PR comment.
136124func (r * prCommentReporter ) Report (ctx restate.ObjectSharedContext , state string , description string ) {
137125 if r .commentID == 0 {
138126 return
139127 }
140128
141- statusEmoji , statusLabel := r .stateToDisplay (state )
142- row := r .buildRow (statusEmoji + " " + statusLabel )
129+ row := r .buildRow (stateLabel (state ))
143130
144- // Re-read current comment body so we don't clobber other deployments ' rows.
131+ // Re-read current body so we don't clobber other apps ' rows.
145132 current , err := restate .Run (ctx , func (_ restate.RunContext ) (findResult , error ) {
146- id , body , err := r .github .FindBotComment (r .installationID , r .repo , r .prNumber , prCommentMainMarker )
147- return findResult {ID : id , Body : body }, err
148- }, restate .WithName ("read deploy comment for update " ), restate .WithMaxRetryDuration (30 * time .Second ))
133+ id , body , findErr := r .github .FindBotComment (r .installationID , r .repo , r .prNumber , prCommentMainMarker )
134+ return findResult {ID : id , Body : body }, findErr
135+ }, restate .WithName ("read deploy comment" ), restate .WithMaxRetryDuration (30 * time .Second ))
149136 if err != nil || current .ID == 0 {
150137 return
151138 }
152139
153140 body := r .upsertRow (current .Body , row )
154-
155141 _ = restate .RunVoid (ctx , func (_ restate.RunContext ) error {
156142 return r .github .UpdateIssueComment (r .installationID , r .repo , r .commentID , body )
157- }, restate .WithName (fmt .Sprintf ("update PR comment row : %s" , state )), restate .WithMaxRetryDuration (30 * time .Second ))
143+ }, restate .WithName (fmt .Sprintf ("update deploy comment: %s" , state )), restate .WithMaxRetryDuration (30 * time .Second ))
158144}
159145
160- // rowMarker returns the unique key for this app/env combination.
161146func (r * prCommentReporter ) rowMarker () string {
162147 return fmt .Sprintf (prCommentRowMarkerFmt , r .appSlug , r .envSlug )
163148}
164149
165- // buildRow produces a single markdown table row with this deployment's info.
166150func (r * prCommentReporter ) buildRow (status string ) string {
167- rowMarker := r .rowMarker ()
168-
169151 nameLabel := r .projectSlug
170152 if r .appSlug != "default" {
171153 nameLabel += " / " + r .appSlug
172154 }
173155
174- now := time .Now ().UTC ().Format ("Jan 2, 2006 3:04pm" )
175-
176156 preview := "—"
177157 if r .environmentURL != "" {
178158 preview = fmt .Sprintf ("[Visit Preview](%s)" , r .environmentURL )
179159 }
180160
181- inspect := fmt .Sprintf ("[Inspect](%s)" , r . logURL )
182-
183- return fmt . Sprintf ( "| %s **%s** (%s) | %s | %s | %s | %s |" ,
184- rowMarker , nameLabel , r . envSlug , status , preview , inspect , now )
161+ return fmt .Sprintf ("| %s **%s** (%s) | %s | %s | [Inspect](%s) | %s |" ,
162+ r . rowMarker (), nameLabel , r . envSlug , status ,
163+ preview , r . logURL ,
164+ time . Now (). UTC (). Format ( "Jan 2, 2006 3:04pm" ) )
185165}
186166
187- // buildFullComment wraps the header, table, and a single row into a new comment.
188167func (r * prCommentReporter ) buildFullComment (firstRow string ) string {
189168 var b strings.Builder
190169 b .WriteString (prCommentMainMarker )
@@ -198,55 +177,48 @@ func (r *prCommentReporter) buildFullComment(firstRow string) string {
198177}
199178
200179// upsertRow replaces an existing row for this app/env or appends a new one.
201- func (r * prCommentReporter ) upsertRow (existingBody string , newRow string ) string {
202- rowMarker := r .rowMarker ()
180+ func (r * prCommentReporter ) upsertRow (body string , newRow string ) string {
181+ marker := r .rowMarker ()
182+ lines := strings .Split (body , "\n " )
203183
204- if strings .Contains (existingBody , rowMarker ) {
205- // Replace the existing row (the whole line containing our marker).
206- lines := strings .Split (existingBody , "\n " )
184+ if strings .Contains (body , marker ) {
207185 for i , line := range lines {
208- if strings .Contains (line , rowMarker ) {
186+ if strings .Contains (line , marker ) {
209187 lines [i ] = newRow
210- break
188+ return strings . Join ( lines , " \n " )
211189 }
212190 }
213- return strings .Join (lines , "\n " )
214191 }
215192
216- // Append new row after the last table row.
217- // Find the last line that starts with "| " (table row).
218- lines := strings .Split (existingBody , "\n " )
193+ // Append after the last table row (any line starting with "|" that isn't the separator).
219194 lastRowIdx := - 1
220195 for i , line := range lines {
221- if strings .HasPrefix (line , "|" ) && ! strings .Contains (line , ":--" ) && i > 0 {
196+ if i > 0 && strings .HasPrefix (line , "|" ) && ! strings .Contains (line , ":--" ) {
222197 lastRowIdx = i
223198 }
224199 }
225-
226200 if lastRowIdx >= 0 {
227- // Insert after the last row.
228201 result := make ([]string , 0 , len (lines )+ 1 )
229202 result = append (result , lines [:lastRowIdx + 1 ]... )
230203 result = append (result , newRow )
231204 result = append (result , lines [lastRowIdx + 1 :]... )
232205 return strings .Join (result , "\n " )
233206 }
234207
235- // Fallback: just append.
236- return existingBody + newRow + "\n "
208+ return body + newRow + "\n "
237209}
238210
239- func ( r * prCommentReporter ) stateToDisplay ( state string ) ( string , string ) {
211+ func stateLabel ( state string ) string {
240212 switch state {
241213 case "pending" :
242- return "⏳" , " Queued"
214+ return "Queued"
243215 case "in_progress" :
244- return "🔨" , " Building"
216+ return "Building"
245217 case "success" :
246- return "✅" , " Ready"
218+ return "Ready"
247219 case "failure" , "error" :
248- return "❌" , " Failed"
220+ return "Failed"
249221 default :
250- return "⏳" , " In Progress"
222+ return "In Progress"
251223 }
252224}
0 commit comments