Skip to content

Commit cafeb2e

Browse files
committed
Poll for release
1 parent ca9d867 commit cafeb2e

File tree

2 files changed

+134
-14
lines changed

2 files changed

+134
-14
lines changed

cmd/tag-release-tui/README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ go build -o bin/tag-release-tui ./cmd/tag-release-tui
3333
- **Flexible Branch Support**: Can be configured to work with any branch (currently set to `tag-release-charmbracelet`)
3434
- **Confirmation Screen**: Displays a summary and asks for confirmation before proceeding
3535
- **Live Execution**: Shows progress as the release is being created
36+
- **Automatic Release Detection**: Automatically polls GitHub releases page and provides URL when available
3637
- **Post-Release Instructions**: Provides next steps after successful release creation
3738
- **Safe Testing**: Perfect for testing against your fork without affecting upstream
3839

3940
## Flow
4041

4142
1. **Validation Phase**:
4243
- Checks tag format (semantic versioning)
43-
- Verifies you're on the main branch
44+
- Verifies you're on the correct branch
4445
- Fetches latest changes
4546
- Checks working directory is clean
4647
- Validates branch is up-to-date
@@ -53,12 +54,18 @@ go build -o bin/tag-release-tui ./cmd/tag-release-tui
5354

5455
3. **Execution Phase**:
5556
- Creates the release tag
56-
- Pushes tag to origin
57+
- Pushes tag to specified remote
5758
- Updates latest-release tag
5859
- Pushes latest-release tag
5960

60-
4. **Completion Phase**:
61-
- Shows success message
61+
4. **Release Detection Phase** (production mode only):
62+
- Automatically polls GitHub releases page every 10 seconds
63+
- Shows progress with animated dots
64+
- Displays release URL when found (up to 5 minutes)
65+
- Gracefully times out if release workflow takes longer
66+
67+
5. **Completion Phase**:
68+
- Shows success message with release URL (if found)
6269
- Provides post-release instructions
6370
- Shows relevant links
6471

@@ -124,13 +131,16 @@ This TUI version provides the same functionality as the original `script/tag-rel
124131
- Improved error presentation
125132
- Built-in test mode (no need for `--dry-run` flag)
126133
- Support for different branches during development
134+
- **Automatic release detection and URL provision**
127135
- Enhanced user experience with modern terminal UI
136+
- **No more manual checking of releases page**
128137

129138
## Configuration
130139

131140
Currently configured for:
132141
- **Allowed Branch**: `tag-release-charmbracelet` (for development/testing)
133142
- **Default Remote**: `origin` (can be overridden with `--remote` flag)
143+
- **Polling**: Checks GitHub releases every 10 seconds for up to 5 minutes
134144
- **Target Branch**: Can be modified in the source code for production use
135145

136146
To use with the main branch in production, change the `allowedBranch` parameter in the `performValidation` call.

cmd/tag-release-tui/main.go

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package main
22

33
import (
44
"fmt"
5+
"net/http"
56
"os"
67
"os/exec"
78
"regexp"
89
"strings"
10+
"time"
911

1012
tea "github.com/charmbracelet/bubbletea"
1113
"github.com/charmbracelet/lipgloss"
@@ -20,6 +22,7 @@ const (
2022
stateConfirming
2123
stateExecuting
2224
stateComplete
25+
statePollingRelease
2326
stateError
2427
)
2528

@@ -37,6 +40,8 @@ type model struct {
3740
executed bool
3841
repoSlug string
3942
testMode bool
43+
releaseURL string
44+
pollingAttempts int
4045
width int
4146
height int
4247
}
@@ -57,6 +62,14 @@ type executionCompleteMsg struct {
5762
errors []string
5863
}
5964

65+
type releaseFoundMsg struct {
66+
url string
67+
}
68+
69+
type releasePollingMsg struct {
70+
attempt int
71+
}
72+
6073
// Styles
6174
var (
6275
titleStyle = lipgloss.NewStyle().
@@ -169,13 +182,54 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
169182

170183
case executionCompleteMsg:
171184
if msg.success {
172-
m.state = stateComplete
173-
m.executed = true
185+
if m.testMode {
186+
m.state = stateComplete
187+
m.executed = true
188+
} else {
189+
m.state = statePollingRelease
190+
return m, pollForRelease(m.repoSlug, m.tag)
191+
}
174192
} else {
175193
m.state = stateError
176194
m.errors = msg.errors
177195
}
178196
return m, nil
197+
198+
case releasePollingMsg:
199+
m.pollingAttempts = msg.attempt
200+
return m, nil
201+
202+
case releaseFoundMsg:
203+
m.releaseURL = msg.url
204+
m.state = stateComplete
205+
m.executed = true
206+
return m, nil
207+
208+
case pollAttemptMsg:
209+
if msg.attempt > 30 {
210+
// Timeout after 30 attempts (5 minutes)
211+
m.state = stateComplete
212+
m.executed = true
213+
return m, nil
214+
}
215+
216+
m.pollingAttempts = msg.attempt
217+
218+
// Check if release is available
219+
releaseURL := fmt.Sprintf("https://github.com/%s/releases/tag/%s", msg.repoSlug, msg.tag)
220+
resp, err := http.Get(releaseURL)
221+
if err == nil {
222+
resp.Body.Close()
223+
if resp.StatusCode == 200 {
224+
m.releaseURL = releaseURL
225+
m.state = stateComplete
226+
m.executed = true
227+
return m, nil
228+
}
229+
}
230+
231+
// Continue polling
232+
return m, startPollingTicker(msg.repoSlug, msg.tag, msg.attempt)
179233
}
180234

181235
return m, nil
@@ -191,6 +245,8 @@ func (m model) View() string {
191245
return m.renderConfirming()
192246
case stateExecuting:
193247
return m.renderExecuting()
248+
case statePollingRelease:
249+
return m.renderPollingRelease()
194250
case stateComplete:
195251
return m.renderComplete()
196252
case stateError:
@@ -312,6 +368,23 @@ func (m model) renderExecuting() string {
312368
return content
313369
}
314370

371+
func (m model) renderPollingRelease() string {
372+
content := titleStyle.Render("🏷️ GitHub MCP Server - Tag Release") + "\n\n"
373+
content += successStyle.Render("✅ Successfully tagged and pushed release "+m.tag) + "\n"
374+
content += successStyle.Render("✅ 'latest-release' tag has been updated") + "\n\n"
375+
376+
content += subtitleStyle.Render("🔍 Polling for GitHub release...") + "\n\n"
377+
378+
dots := strings.Repeat(".", (m.pollingAttempts % 3) + 1)
379+
content += warningStyle.Render(fmt.Sprintf("⋯ Checking GitHub releases page%s", dots)) + "\n"
380+
content += fmt.Sprintf(" Attempt %d/30 (checking every 10 seconds)\n", m.pollingAttempts+1)
381+
content += "\n"
382+
content += subtitleStyle.Render("Once the release workflow completes, the draft release URL will appear here.") + "\n"
383+
content += subtitleStyle.Render("Press Ctrl+C to exit early if needed.")
384+
385+
return content
386+
}
387+
315388
func (m model) renderComplete() string {
316389
content := titleStyle.Render("🏷️ GitHub MCP Server - Tag Release")
317390
if m.testMode {
@@ -330,13 +403,21 @@ func (m model) renderComplete() string {
330403
content += subtitleStyle.Render("To perform the actual release, run without --test flag") + "\n\n"
331404
} else {
332405
content += successStyle.Render("✅ Successfully tagged and pushed release "+m.tag) + "\n"
333-
content += successStyle.Render("✅ 'latest-release' tag has been updated") + "\n\n"
406+
content += successStyle.Render("✅ 'latest-release' tag has been updated") + "\n"
407+
408+
if m.releaseURL != "" {
409+
content += successStyle.Render("✅ Draft release is now available!") + "\n\n"
410+
content += subtitleStyle.Render("🎉 Release "+m.tag+" has been created!") + "\n\n"
411+
content += highlightStyle.Render("📦 Draft Release URL:") + "\n"
412+
content += " " + m.releaseURL + "\n\n"
413+
} else {
414+
content += "\n"
415+
content += subtitleStyle.Render("🎉 Release "+m.tag+" has been initiated!") + "\n\n"
416+
}
334417

335418
// Post-release instructions
336-
content += subtitleStyle.Render("🎉 Release "+m.tag+" has been initiated!") + "\n\n"
337419
content += subtitleStyle.Render("Next steps:") + "\n"
338420
steps := []string{
339-
fmt.Sprintf("📋 Check https://github.com/%s/releases and wait for the draft release", m.repoSlug),
340421
"✏️ Edit the new release, delete existing notes and click auto-generate button",
341422
"✨ Add a section at the top calling out the main features",
342423
"🚀 Publish the release",
@@ -346,8 +427,7 @@ func (m model) renderComplete() string {
346427
for _, step := range steps {
347428
content += " " + step + "\n"
348429
}
349-
350-
content += "\n" + subtitleStyle.Render(fmt.Sprintf("📦 Draft Release: https://github.com/%s/releases/tag/%s", m.repoSlug, m.tag)) + "\n\n"
430+
content += "\n"
351431
}
352432

353433
content += subtitleStyle.Render("Press Enter to exit")
@@ -535,6 +615,36 @@ func performExecution(tag, remote string, testMode bool) tea.Cmd {
535615
})
536616
}
537617

618+
// pollForRelease polls the GitHub releases page to check if a release is available
619+
func pollForRelease(repoSlug, tag string) tea.Cmd {
620+
return tea.Cmd(func() tea.Msg {
621+
// Check immediately first
622+
releaseURL := fmt.Sprintf("https://github.com/%s/releases/tag/%s", repoSlug, tag)
623+
resp, err := http.Get(releaseURL)
624+
if err == nil {
625+
resp.Body.Close()
626+
if resp.StatusCode == 200 {
627+
return releaseFoundMsg{url: releaseURL}
628+
}
629+
}
630+
631+
// Start polling with ticker
632+
return startPollingTicker(repoSlug, tag, 0)
633+
})
634+
}
635+
636+
func startPollingTicker(repoSlug, tag string, attempt int) tea.Cmd {
637+
return tea.Tick(time.Second*10, func(t time.Time) tea.Msg {
638+
return pollAttemptMsg{repoSlug: repoSlug, tag: tag, attempt: attempt + 1}
639+
})
640+
}
641+
642+
type pollAttemptMsg struct {
643+
repoSlug string
644+
tag string
645+
attempt int
646+
}
647+
538648
func main() {
539649
if len(os.Args) < 2 {
540650
fmt.Println("Error: No tag specified.")
@@ -547,7 +657,7 @@ func main() {
547657
tag := os.Args[1]
548658
testMode := false
549659
remote := "origin" // default remote
550-
660+
551661
// Parse flags
552662
for i := 2; i < len(os.Args); i++ {
553663
switch os.Args[i] {
@@ -563,13 +673,13 @@ func main() {
563673
}
564674
}
565675
}
566-
676+
567677
if testMode {
568678
fmt.Printf("🧪 Running in TEST MODE - no actual changes will be made (remote: %s)\n", remote)
569679
} else {
570680
fmt.Printf("🚀 Running release process (remote: %s)\n", remote)
571681
}
572-
682+
573683
p := tea.NewProgram(
574684
initialModel(tag, remote, testMode),
575685
tea.WithAltScreen(),

0 commit comments

Comments
 (0)