Skip to content

Commit a7afb14

Browse files
authored
feat: replace CLI output with styled Charm-based UI
Migrate all CLI output to the Charm ecosystem (Bubble Tea, lipgloss, Huh) for a polished terminal experience with proper headless/CI mode fallback. - Add Info, Success, ErrorMsg, Warn methods to UI with lipgloss styling - Style spinner final/error messages with checkmark/cross prefixes - Fix "No servers found" to exit 0 instead of 1 (not an error) - Replace all RawMessage/PrintMixedGreen calls with typed UI methods - Add interactive forms via charmbracelet/huh replacing survey - Add styled tables via lipgloss replacing tablewriter - Log spinner errors at error level in headless mode - Update README libraries section to reflect new dependencies
1 parent 87637d7 commit a7afb14

23 files changed

Lines changed: 1276 additions & 448 deletions

.golangci.yaml

Lines changed: 112 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,121 @@
1-
linters:
1+
version: "2"
2+
3+
formatters:
24
enable:
3-
- thelper
45
- gofumpt
5-
- tparallel
6-
- unconvert
7-
- unparam
8-
- wastedassign
9-
- revive
6+
- goimports
7+
8+
linters:
9+
enable:
10+
# bugs
11+
- bodyclose
12+
- durationcheck
13+
- errcheck
14+
- errorlint
15+
- gosec
16+
- govet
17+
- loggercheck
18+
- nilerr
19+
- noctx
20+
- recvcheck
21+
- sqlclosecheck
22+
23+
# performance
24+
- copyloopvar
25+
- intrange
26+
- mirror
27+
- perfsprint
28+
- prealloc
29+
- usestdlibvars
30+
31+
# style & conventions
32+
- asciicheck
33+
- bidichk
34+
- dupword
35+
- errname
1036
- forbidigo
11-
- tagliatelle
12-
- typecheck
1337
- goconst
1438
- gocritic
1539
- gocyclo
16-
- govet
17-
- errcheck
18-
- gosec
19-
- goimports
40+
- goprintffuncname
41+
- misspell
42+
- modernize
2043
- nolintlint
44+
- nosprintfhostport
45+
- predeclared
46+
- revive
47+
- tagliatelle
48+
- unconvert
49+
- unparam
50+
- wastedassign
51+
- whitespace
52+
53+
# testing
54+
- thelper
55+
- tparallel
56+
- usetesting
57+
58+
# unused / dead code
59+
- fatcontext
60+
- reassign
2161

22-
linters-settings:
23-
forbidigo:
24-
forbid:
25-
- 'ioutil\.*'
26-
tagliatelle:
27-
case:
28-
use-field-name: false
62+
settings:
63+
forbidigo:
64+
forbid:
65+
- pattern: 'ioutil\.*'
66+
msg: "use os and io packages instead of ioutil"
67+
exclude-godoc-examples: true
68+
analyze-types: true
69+
tagliatelle:
70+
case:
71+
use-field-name: false
72+
rules:
73+
yaml: goCamel
74+
json: snake
75+
errorlint:
76+
asserts: true
77+
comparison: true
78+
errorf: true
79+
gocritic:
80+
enabled-tags:
81+
- diagnostic
82+
- style
83+
- performance
84+
disabled-checks:
85+
- hugeParam
86+
- importShadow
87+
revive:
2988
rules:
30-
yaml: goCamel
31-
json: snake
89+
- name: blank-imports
90+
- name: context-as-argument
91+
- name: dot-imports
92+
- name: error-naming
93+
- name: error-return
94+
- name: error-strings
95+
- name: exported
96+
disabled: true
97+
- name: increment-decrement
98+
- name: indent-error-flow
99+
- name: package-comments
100+
disabled: true
101+
- name: range
102+
- name: receiver-naming
103+
- name: redefines-builtin-id
104+
- name: superfluous-else
105+
- name: unreachable-code
106+
- name: var-naming
107+
misspell:
108+
locale: US
109+
ignore-rules:
110+
- organisation
111+
gocyclo:
112+
min-complexity: 15
113+
perfsprint:
114+
err-error: true
115+
errorf: true
116+
sprintf1: true
117+
gosec:
118+
excludes:
119+
- G104
120+
- G204
121+
- G304

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ Feel free to join! See our [contribution guidelines](CONTRIBUTING.md).
193193
<details>
194194
<summary>Click to expand</summary>
195195

196-
- https://github.com/fatih/color
196+
- https://github.com/charmbracelet/bubbletea
197+
- https://github.com/charmbracelet/lipgloss
198+
- https://github.com/charmbracelet/huh
199+
- https://github.com/charmbracelet/bubbles
197200
- https://github.com/melbahja/goph
198201
- https://github.com/spf13/cobra
199202
- https://github.com/goreleaser
@@ -202,7 +205,6 @@ Feel free to join! See our [contribution guidelines](CONTRIBUTING.md).
202205
- https://github.com/scaleway/scaleway-sdk-go
203206
- https://github.com/linode/linodego
204207
- https://github.com/hetznercloud/hcloud-go
205-
- https://github.com/olekukonko/tablewriter
206208
- https://github.com/sethvargo/go-password
207209
- https://github.com/ovh/go-ovh
208210
- https://github.com/dirien/ovh-go-sdk
@@ -217,8 +219,7 @@ Feel free to join! See our [contribution guidelines](CONTRIBUTING.md).
217219
- https://github.com/tcnksm/go-latest
218220
- https://github.com/uber-go/zap
219221
- https://github.com/oracle/oci-go-sdk
220-
- https://github.com/AlecAivazis/survey
221-
- https://github.com/aws/aws-sdk-go
222+
- https://github.com/aws/aws-sdk-go-v2
222223
- https://github.com/gophercloud/gophercloud
223224
- https://github.com/exoscale/egoscale
224225

cmd/minectl/create.go

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ package minectl
22

33
import (
44
"fmt"
5-
"os"
65

76
"github.com/dirien/minectl/internal/provisioner"
8-
"github.com/olekukonko/tablewriter"
7+
"github.com/dirien/minectl/internal/ui"
98

109
"github.com/pkg/errors"
1110
"github.com/spf13/cobra"
1211
)
1312

1413
func init() {
1514
createCmd.Flags().StringP("filename", "f", "", "Location of the manifest file")
16-
createCmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, []string{"yaml"}) //nolint:errcheck
15+
_ = createCmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, []string{"yaml"})
1716
createCmd.Flags().BoolP("wait", "w", true, "Wait for Minecraft Server is started")
1817
}
1918

@@ -29,15 +28,15 @@ var createCmd = &cobra.Command{
2928

3029
func runCreate(cmd *cobra.Command, _ []string) error {
3130
filename, err := cmd.Flags().GetString("filename")
32-
if len(filename) == 0 {
31+
if filename == "" {
3332
return errors.New("Please provide a valid manifest file via -f|--filename flag")
3433
}
3534
if err != nil {
3635
return errors.Wrap(err, "Please provide a valid manifest file")
3736
}
3837
p, err := provisioner.NewProvisioner(&provisioner.MinectlProvisionerOpts{
3938
ManifestPath: filename,
40-
}, minectlLog)
39+
}, minectlUI)
4140
if err != nil {
4241
return err
4342
}
@@ -50,20 +49,17 @@ func runCreate(cmd *cobra.Command, _ []string) error {
5049
return err
5150
}
5251
if !headless {
53-
table := tablewriter.NewTable(os.Stdout,
54-
tablewriter.WithHeader([]string{"ID", "NAME", "REGION", "TAGS", "IP"}),
55-
)
52+
table := ui.NewTable(minectlUI, "ID", "NAME", "REGION", "TAGS", "IP")
5653
table.Append([]string{res.ID, res.Name, res.Region, res.Tags, res.PublicIP})
5754

5855
fmt.Println("")
5956
table.Render()
6057

61-
minectlLog.PrintMixedGreen("\n🪓 To delete the server type:\n\n %s", fmt.Sprintf("minectl delete -f %s --id %s\n", filename, res.ID))
62-
minectlLog.PrintMixedGreen("\n🆙 To update the server type:\n\n %s", fmt.Sprintf("minectl update -f %s --id %s\n", filename, res.ID))
63-
minectlLog.PrintMixedGreen("\n🔌 Connected to RCON type:\n\n %s", fmt.Sprintf("minectl rcon -f %s --id %s\n", filename, res.ID))
64-
minectlLog.RawMessage("🚧 Beta features:")
65-
minectlLog.PrintMixedGreen("⤴️ To upload a plugin type:\n\n %s",
66-
fmt.Sprintf("minectl plugins -f %s --id %s --plugin <folder>/x.jar --destination /minecraft/plugins\n", filename, res.ID))
58+
minectlUI.Info(fmt.Sprintf("To delete the server:\n\n minectl delete -f %s --id %s", filename, res.ID))
59+
minectlUI.Info(fmt.Sprintf("To update the server:\n\n minectl update -f %s --id %s", filename, res.ID))
60+
minectlUI.Info(fmt.Sprintf("To connect via RCON:\n\n minectl rcon -f %s --id %s", filename, res.ID))
61+
minectlUI.Warn("Beta features:")
62+
minectlUI.Info(fmt.Sprintf("To upload a plugin:\n\n minectl plugins -f %s --id %s --plugin <folder>/x.jar --destination /minecraft/plugins", filename, res.ID))
6763
}
6864
return nil
6965
}

cmd/minectl/delete.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
package minectl
22

33
import (
4-
"bufio"
5-
"fmt"
6-
"os"
7-
"strings"
8-
94
"github.com/dirien/minectl/internal/provisioner"
5+
"github.com/dirien/minectl/internal/ui"
106
"github.com/pkg/errors"
117
"github.com/spf13/cobra"
128
)
139

1410
func init() {
1511
deleteCmd.Flags().StringP("filename", "f", "", "Location of the manifest file")
16-
deleteCmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, []string{"yaml"}) //nolint:errcheck
12+
_ = deleteCmd.Flags().SetAnnotation("filename", cobra.BashCompFilenameExt, []string{"yaml"})
1713
deleteCmd.Flags().String("id", "", "Contains the server id")
1814
deleteCmd.Flags().BoolP("yes", "y", false, "Automatically delete the server")
1915
}
@@ -35,20 +31,20 @@ func runDelete(cmd *cobra.Command, _ []string) error {
3531
if err != nil {
3632
return errors.Wrap(err, "failed to get 'filename' value")
3733
}
38-
if len(filename) == 0 {
34+
if filename == "" {
3935
return errors.New("Please provide a valid manifest file via -f|--filename flag")
4036
}
4137
id, err := cmd.Flags().GetString("id")
4238
if err != nil {
4339
return err
4440
}
45-
if len(id) == 0 {
41+
if id == "" {
4642
return errors.New("Please provide a valid id")
4743
}
4844
newProvisioner, err := provisioner.NewProvisioner(&provisioner.MinectlProvisionerOpts{
4945
ManifestPath: filename,
5046
ID: id,
51-
}, minectlLog)
47+
}, minectlUI)
5248
if err != nil {
5349
return err
5450
}
@@ -60,15 +56,17 @@ func runDelete(cmd *cobra.Command, _ []string) error {
6056
return err
6157
}
6258
} else {
63-
fmt.Print("🗑️Do you want to delete the Minecraft server? [y/N]: ")
64-
reader := bufio.NewReader(os.Stdin)
65-
text, _ := reader.ReadString('\n')
66-
text = strings.ReplaceAll(text, "\n", "")
67-
if strings.Compare("y", text) == 0 {
59+
confirmed, err := ui.Confirm("Do you want to delete the Minecraft server?")
60+
if err != nil {
61+
return err
62+
}
63+
if confirmed {
6864
err = newProvisioner.DeleteServer()
6965
if err != nil {
7066
return err
7167
}
68+
} else {
69+
minectlUI.Warn("Delete canceled.")
7270
}
7371
}
7472

cmd/minectl/list.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package minectl
22

33
import (
44
"fmt"
5-
"os"
65

76
"github.com/dirien/minectl/internal/provisioner"
8-
"github.com/olekukonko/tablewriter"
7+
"github.com/dirien/minectl/internal/ui"
98
"github.com/pkg/errors"
109
"github.com/spf13/cobra"
1110
)
@@ -31,7 +30,7 @@ func runList(cmd *cobra.Command, _ []string) error {
3130
if err != nil {
3231
return errors.Wrap(err, "failed to get 'provider' value")
3332
}
34-
if len(provider) == 0 {
33+
if provider == "" {
3534
return errors.New("Please provide a valid 'provider' value")
3635
}
3736
region, err := cmd.Flags().GetString("region")
@@ -42,7 +41,7 @@ func runList(cmd *cobra.Command, _ []string) error {
4241
newProvisioner, err := provisioner.ListProvisioner(&provisioner.MinectlProvisionerListOpts{
4342
Provider: provider,
4443
Region: region,
45-
}, minectlLog)
44+
}, minectlUI)
4645
if err != nil {
4746
return err
4847
}
@@ -51,19 +50,18 @@ func runList(cmd *cobra.Command, _ []string) error {
5150
return err
5251
}
5352

53+
if len(servers) == 0 {
54+
minectlUI.Info("No servers found")
55+
return nil
56+
}
57+
5458
if !headless {
55-
if len(servers) > 0 {
56-
fmt.Println("")
57-
table := tablewriter.NewTable(os.Stdout,
58-
tablewriter.WithHeader([]string{"ID", "NAME", "REGION", "TAGS", "IP"}),
59-
)
60-
for _, server := range servers {
61-
table.Append([]string{server.ID, server.Name, server.Region, server.Tags, server.PublicIP})
62-
}
63-
table.Render()
64-
} else {
65-
return errors.New("🤷 No server found")
59+
fmt.Println("")
60+
table := ui.NewTable(minectlUI, "ID", "NAME", "REGION", "TAGS", "IP")
61+
for _, server := range servers {
62+
table.Append([]string{server.ID, server.Name, server.Region, server.Tags, server.PublicIP})
6663
}
64+
table.Render()
6765
}
6866
return nil
6967
}

0 commit comments

Comments
 (0)