Skip to content

Commit d72cd25

Browse files
committed
feat(upgrade): add interactive version selection using tap
1 parent 12fbefd commit d72cd25

File tree

6 files changed

+132
-13
lines changed

6 files changed

+132
-13
lines changed

cmds/testcmd/main.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/yarlson/tap"
9+
)
10+
11+
func main() {
12+
fmt.Println("Styled Select Example")
13+
fmt.Println("Use arrow keys (or hjkl) to navigate, Enter to select, Ctrl+C to cancel")
14+
fmt.Println()
15+
16+
// Example 1: Color selection with hints
17+
colors := []tap.SelectOption[string]{
18+
{Value: "red", Label: "Red", Hint: "The color of passion and energy"},
19+
{Value: "blue", Label: "Blue", Hint: "The color of calm and trust"},
20+
{Value: "green", Label: "Green", Hint: "The color of nature and growth"},
21+
{Value: "yellow", Label: "Yellow", Hint: "The color of happiness and optimism"},
22+
{Value: "purple", Label: "Purple", Hint: "The color of creativity and mystery"},
23+
}
24+
25+
result := tap.Select[string](context.Background(), tap.SelectOptions[string]{
26+
Message: "What's your favorite color?",
27+
Options: colors,
28+
})
29+
fmt.Printf("\nYou selected: %s\n", result)
30+
31+
// Example 2: Framework selection with initial value
32+
fmt.Println("\n" + strings.Repeat("─", 50))
33+
fmt.Println("Framework Selection Example:")
34+
35+
frameworks := []tap.SelectOption[string]{
36+
{Value: "react", Label: "React", Hint: "A JavaScript library for building user interfaces"},
37+
{Value: "vue", Label: "Vue.js", Hint: "The Progressive JavaScript Framework"},
38+
{Value: "angular", Label: "Angular", Hint: "Platform for building mobile and desktop web apps"},
39+
{Value: "svelte", Label: "Svelte", Hint: "Cybernetically enhanced web apps"},
40+
{Value: "solid", Label: "SolidJS", Hint: "Simple and performant reactivity"},
41+
}
42+
43+
initialValue := "react"
44+
result2 := tap.Select[string](context.Background(), tap.SelectOptions[string]{
45+
Message: "Which frontend framework do you prefer?",
46+
Options: frameworks,
47+
InitialValue: &initialValue,
48+
})
49+
fmt.Printf("\nYou chose: %s\n", result2)
50+
51+
// Example 3: Priority levels (numeric values)
52+
fmt.Println("\n" + strings.Repeat("─", 50))
53+
fmt.Println("Priority Selection Example:")
54+
55+
priorities := []tap.SelectOption[int]{
56+
{Value: 1, Label: "Low Priority", Hint: "Can be done when time permits"},
57+
{Value: 2, Label: "Medium Priority", Hint: "Should be completed this week"},
58+
{Value: 3, Label: "High Priority", Hint: "Needs attention today"},
59+
{Value: 4, Label: "Critical", Hint: "Drop everything and do this now"},
60+
}
61+
62+
result3 := tap.Select[int](context.Background(), tap.SelectOptions[int]{
63+
Message: "What's the priority level for this task?",
64+
Options: priorities,
65+
})
66+
fmt.Printf("\nSelected priority level: %d\n", result3)
67+
68+
// Example 4: Simple options without labels (uses values as labels)
69+
fmt.Println("\n" + strings.Repeat("─", 50))
70+
fmt.Println("Simple Options Example:")
71+
72+
environments := []tap.SelectOption[string]{
73+
{Value: "development"},
74+
{Value: "staging"},
75+
{Value: "production"},
76+
}
77+
78+
result4 := tap.Select[string](context.Background(), tap.SelectOptions[string]{
79+
Message: "Which environment to deploy to?",
80+
Options: environments,
81+
})
82+
fmt.Printf("\nDeploying to: %s\n", result4)
83+
fmt.Println("\nAll examples completed successfully! 🎉")
84+
}

cmds/upgradecmd/cmd.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ package upgradecmd
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"os/exec"
78
"path/filepath"
9+
"sort"
810

9-
tea "github.com/charmbracelet/bubbletea"
1011
"github.com/hashicorp/go-getter"
12+
"github.com/hashicorp/go-version"
1113
"github.com/olekukonko/tablewriter"
12-
"github.com/pubgo/fastcommit/utils/githubclient"
1314
"github.com/pubgo/funk/assert"
1415
"github.com/pubgo/funk/errors"
1516
"github.com/pubgo/funk/log"
1617
"github.com/pubgo/funk/recovery"
1718
"github.com/rs/zerolog"
1819
"github.com/samber/lo"
1920
"github.com/urfave/cli/v3"
21+
"github.com/yarlson/tap"
22+
23+
"github.com/pubgo/fastcommit/utils/githubclient"
2024
)
2125

2226
func New() *cli.Command {
@@ -63,10 +67,30 @@ func New() *cli.Command {
6367
client := githubclient.NewPublicRelease("pubgo", "fastcommit")
6468
r := lo.Must(client.List(ctx))
6569

66-
var p = tea.NewProgram(initialModel(githubclient.GetAssetList(r)))
67-
mm := assert.Must1(p.Run()).(*model)
70+
assets := githubclient.GetAssetList(r)
71+
assets = lo.Filter(assets, func(item githubclient.Asset, index int) bool { return !item.IsChecksumFile() })
72+
sort.Slice(assets, func(i, j int) bool {
73+
return lo.Must(version.NewSemver(assets[i].Name)).GreaterThan(lo.Must(version.NewSemver(assets[j].Name)))
74+
})
75+
76+
if len(assets) > 20 {
77+
assets = assets[:20]
78+
}
79+
80+
result2 := tap.Select[string](context.Background(), tap.SelectOptions[string]{
81+
Message: "Which frontend framework do you prefer?",
82+
Options: lo.Map(assets, func(item githubclient.Asset, index int) tap.SelectOption[string] {
83+
return tap.SelectOption[string]{
84+
Value: item.Name,
85+
Label: fmt.Sprintf("%s %s %s", item.Name, item.OS, item.Arch),
86+
}
87+
}),
88+
})
89+
fmt.Printf("\nYou chose: %s\n", result2)
6890

69-
var downloadURL = mm.selected.URL
91+
asset, ok := lo.Find(assets, func(item githubclient.Asset) bool { return item.Name == result2 })
92+
assert.If(!ok, "%s not found", result2)
93+
var downloadURL = asset.URL
7094

7195
downloadDir := filepath.Join(os.TempDir(), "fastcommit")
7296
pwd := lo.Must(os.Getwd())

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ require (
3333
github.com/sashabaranov/go-openai v1.40.5
3434
github.com/stretchr/testify v1.10.0
3535
github.com/urfave/cli/v3 v3.4.1
36+
github.com/yarlson/tap v0.9.0
3637
gopkg.in/yaml.v3 v3.0.1
3738
)
3839

@@ -61,6 +62,7 @@ require (
6162
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
6263
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
6364
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
65+
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect
6466
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
6567
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
6668
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
@@ -126,8 +128,8 @@ require (
126128
golang.org/x/net v0.42.0 // indirect
127129
golang.org/x/oauth2 v0.30.0 // indirect
128130
golang.org/x/sync v0.16.0 // indirect
129-
golang.org/x/sys v0.34.0 // indirect
130-
golang.org/x/term v0.33.0 // indirect
131+
golang.org/x/sys v0.35.0 // indirect
132+
golang.org/x/term v0.34.0 // indirect
131133
golang.org/x/text v0.27.0 // indirect
132134
golang.org/x/time v0.12.0 // indirect
133135
google.golang.org/api v0.241.0 // indirect

go.sum

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
480480
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
481481
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
482482
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
483+
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
484+
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
483485
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
484486
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
485487
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -936,6 +938,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
936938
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
937939
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
938940
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
941+
github.com/yarlson/tap v0.9.0 h1:gzYFE9OS+0AqQL7gP7ny+Aa5oNlwE1IRBtxHfAjUk+w=
942+
github.com/yarlson/tap v0.9.0/go.mod h1:sWR0uxQqkoj24f/8dusCuZTFoQkTiv4fM0zBty+i7XA=
939943
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
940944
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
941945
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1435,8 +1439,8 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
14351439
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
14361440
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
14371441
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
1438-
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
1439-
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
1442+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
1443+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
14401444
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
14411445
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
14421446
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1471,8 +1475,8 @@ golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
14711475
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
14721476
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
14731477
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
1474-
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
1475-
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
1478+
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
1479+
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
14761480
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
14771481
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
14781482
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

utils/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func GitPushTag(ver string) string {
8585

8686
log.Info().Msg("git push tag " + ver)
8787
assert.Must(RunShell("git", "tag", ver))
88-
return assert.Exit1(RunOutput("git", "push", "origin", ver))
88+
return assert.Must1(RunOutput("git", "push", "origin", ver))
8989
}
9090

9191
func GitFetchAll() {

utils/util.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,12 @@ func RunShell(args ...string) error {
166166
func RunOutput(args ...string) (string, error) {
167167
var shell = strings.Join(args, " ")
168168
log.Info().Msg("shell: " + strings.TrimSpace(shell))
169-
return script.Exec(shell).String()
169+
output, err := script.Exec(shell).String()
170+
if err != nil {
171+
log.Err(err).Msg("shell exec error")
172+
return "", err
173+
}
174+
return output, nil
170175
}
171176

172177
func IsRemoteTagExist(err string) bool {

0 commit comments

Comments
 (0)