detached cc09a5a

Enhance CLI functionality and improve update process

- Add CLI version command
- Improve logging and error handling in deploy and push commands
- Refactor update command with user prompts and permission checks
- Optimize version checking logic
- Less verbose timestamps to log outputs
- Display image size in MB for better readability
- Implement root user detection for update process
- Add configuration field check before update
commit
cc09a5a15577774ce61390f464bd31404a0f6525
author
bamen · · 2024-09-18 08:14 UTC
committer
bamen · · 2024-09-18 08:21 UTC
parent
f6c3de2
signature
unsigned
7 files · +108 -34
M cmd/cli/main.go +6 -2
@@ -8,6 +8,7 @@ import (
88 "github.com/bnema/gordon/internal/cli/cmd"
99 "github.com/bnema/gordon/internal/common"
1010 "github.com/bnema/gordon/internal/server"
11+ "github.com/charmbracelet/log"
1112 "github.com/spf13/cobra"
1213
1314 // env auto load
@@ -29,6 +30,7 @@ func InitializeCommands(client *cli.App, server *server.App) {
2930 rootCmd.AddCommand(cmd.NewDeployCommand(client))
3031 rootCmd.AddCommand(cmd.NewUpdateCommand(client))
3132 rootCmd.AddCommand(cmd.NewPushCommand(client))
33+ rootCmd.AddCommand(cmd.NewVersionCommand(client))
3234 }
3335
3436 func Execute(client *cli.App, server *server.App) {
@@ -59,8 +61,10 @@ func main() {
5961 // Check for new version
6062 go func() {
6163 msg, err := common.CheckVersionPeriodically(&s.Config)
62- if err != nil || msg != "" {
63- // log.Warnf("Error checking for new version: %s", err)
64+ if err != nil {
65+ log.Warnf("Error checking for new version: %s", err)
66+ } else if msg != "" {
67+ log.Info(msg)
6468 }
6569 }()
6670
@@ -8,6 +8,7 @@ import (
8 "github.com/bnema/gordon/internal/cli/cmd"8 "github.com/bnema/gordon/internal/cli/cmd"
9 "github.com/bnema/gordon/internal/common"9 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"10 "github.com/bnema/gordon/internal/server"
11+ "github.com/charmbracelet/log"
11 "github.com/spf13/cobra"12 "github.com/spf13/cobra"
12 13
13 // env auto load14 // env auto load
@@ -29,6 +30,7 @@ func InitializeCommands(client *cli.App, server *server.App) {
29 rootCmd.AddCommand(cmd.NewDeployCommand(client))30 rootCmd.AddCommand(cmd.NewDeployCommand(client))
30 rootCmd.AddCommand(cmd.NewUpdateCommand(client))31 rootCmd.AddCommand(cmd.NewUpdateCommand(client))
31 rootCmd.AddCommand(cmd.NewPushCommand(client))32 rootCmd.AddCommand(cmd.NewPushCommand(client))
33+ rootCmd.AddCommand(cmd.NewVersionCommand(client))
32 }34 }
33 35
34 func Execute(client *cli.App, server *server.App) {36 func Execute(client *cli.App, server *server.App) {
@@ -59,8 +61,10 @@ func main() {
59 // Check for new version61 // Check for new version
60 go func() {62 go func() {
61 msg, err := common.CheckVersionPeriodically(&s.Config)63 msg, err := common.CheckVersionPeriodically(&s.Config)
62- if err != nil || msg != "" {64+ if err != nil {
63- // log.Warnf("Error checking for new version: %s", err)65+ log.Warnf("Error checking for new version: %s", err)
66+ } else if msg != "" {
67+ log.Info(msg)
64 }68 }
65 }()69 }()
66 70
M internal/cli/cmd/deploy.go +8 -1
@@ -19,6 +19,11 @@ import (
1919 "github.com/spf13/cobra"
2020 )
2121
22+func init() {
23+ log.SetReportTimestamp(true)
24+ log.SetTimeFormat("15:04")
25+}
26+
2227 func NewDeployCommand(a *cli.App) *cobra.Command {
2328 var port string
2429 var targetDomain string
@@ -49,7 +54,9 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
4954 }
5055 defer reader.Close()
5156
52- log.Info("Image exported successfully", "image", imageName, "size", actualSize)
57+ sizeInMB := float64(actualSize) / 1024 / 1024
58+
59+ log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
5360
5461 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {
5562 if deployErr, ok := err.(*common.DeploymentError); ok {
@@ -19,6 +19,11 @@ import (
19 "github.com/spf13/cobra"19 "github.com/spf13/cobra"
20 )20 )
21 21
22+func init() {
23+ log.SetReportTimestamp(true)
24+ log.SetTimeFormat("15:04")
25+}
26+
22 func NewDeployCommand(a *cli.App) *cobra.Command {27 func NewDeployCommand(a *cli.App) *cobra.Command {
23 var port string28 var port string
24 var targetDomain string29 var targetDomain string
@@ -49,7 +54,9 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
49 }54 }
50 defer reader.Close()55 defer reader.Close()
51 56
52- log.Info("Image exported successfully", "image", imageName, "size", actualSize)57+ sizeInMB := float64(actualSize) / 1024 / 1024
58+
59+ log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
53 60
54 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {61 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {
55 if deployErr, ok := err.(*common.DeploymentError); ok {62 if deployErr, ok := err.(*common.DeploymentError); ok {
M internal/cli/cmd/push.go +8 -1
@@ -12,6 +12,11 @@ import (
1212 "github.com/spf13/cobra"
1313 )
1414
15+func init() {
16+ log.SetReportTimestamp(true)
17+ log.SetTimeFormat("15:04")
18+}
19+
1520 func NewPushCommand(a *cli.App) *cobra.Command {
1621 var port string
1722
@@ -54,7 +59,9 @@ func pushImage(a *cli.App, imageName string) error {
5459 }
5560 defer reader.Close()
5661
57- log.Info("Image exported successfully", "image", imageName, "size", actualSize)
62+ sizeInMB := float64(actualSize) / 1024 / 1024
63+
64+ log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
5865
5966 reqPayload := common.RequestPayload{
6067 Type: "push",
@@ -12,6 +12,11 @@ import (
12 "github.com/spf13/cobra"12 "github.com/spf13/cobra"
13 )13 )
14 14
15+func init() {
16+ log.SetReportTimestamp(true)
17+ log.SetTimeFormat("15:04")
18+}
19+
15 func NewPushCommand(a *cli.App) *cobra.Command {20 func NewPushCommand(a *cli.App) *cobra.Command {
16 var port string21 var port string
17 22
@@ -54,7 +59,9 @@ func pushImage(a *cli.App, imageName string) error {
54 }59 }
55 defer reader.Close()60 defer reader.Close()
56 61
57- log.Info("Image exported successfully", "image", imageName, "size", actualSize)62+ sizeInMB := float64(actualSize) / 1024 / 1024
63+
64+ log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
58 65
59 reqPayload := common.RequestPayload{66 reqPayload := common.RequestPayload{
60 Type: "push",67 Type: "push",
M internal/cli/cmd/update.go +53 -4
@@ -19,6 +19,7 @@ import (
1919
2020 "github.com/AlecAivazis/survey/v2"
2121 "github.com/bnema/gordon/internal/cli"
22+ "github.com/bnema/gordon/internal/cli/handler"
2223 "github.com/bnema/gordon/pkg/docker"
2324 "github.com/fatih/color"
2425 "github.com/pocketbase/pocketbase/tools/archive"
@@ -76,7 +77,7 @@ func NewUpdateCommand(a *cli.App) *cobra.Command {
7677 var needConfirm bool
7778 if docker.IsRunningInContainer() {
7879 needConfirm = true
79- color.Yellow("NB! It seems that you are in a Docker container.")
80+ color.Yellow("It seems that you are in a Docker container.")
8081 color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
8182 }
8283
@@ -92,6 +93,17 @@ func NewUpdateCommand(a *cli.App) *cobra.Command {
9293 }
9394 }
9495
96+ // Vérifier si l'utilisateur est root
97+ if isRoot() {
98+ color.Yellow("Running update as root. Skipping configuration check.")
99+ return pluginInstance.update()
100+ }
101+
102+ // Si non root, effectuer la vérification des champs
103+ if err := handler.FieldCheck(a); err != nil {
104+ return fmt.Errorf("field check failed: %w", err)
105+ }
106+
95107 return pluginInstance.update()
96108 },
97109 }
@@ -116,6 +128,17 @@ func (p *plugin) update() error {
116128 return nil
117129 }
118130
131+ // Prompt user for update
132+ confirm := false
133+ prompt := &survey.Confirm{
134+ Message: fmt.Sprintf("A new version (%s) is available. Do you want to update?", latest.Tag),
135+ }
136+ survey.AskOne(prompt, &confirm)
137+ if !confirm {
138+ fmt.Println("Update cancelled by user.")
139+ return nil
140+ }
141+
119142 suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
120143 if suffix == "" {
121144 return errors.New("unsupported platform")
@@ -156,18 +179,26 @@ func (p *plugin) update() error {
156179 renamedOldExec := oldExec + ".old"
157180 defer os.Remove(renamedOldExec)
158181
182+ // Check if the user has write permissions
183+ if err := checkWritePermission(oldExec); err != nil {
184+ color.Yellow("Administrator rights are required to perform the update.")
185+ color.Yellow("Please run the following command with sudo:")
186+ color.HiBlack("sudo gordon update")
187+ return nil
188+ }
189+
159190 newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
160191 if _, err := os.Stat(newExec); err != nil {
161192 // try again with an .exe extension
162193 newExec = newExec + ".exe"
163194 if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
164- return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
195+ return fmt.Errorf("the executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
165196 }
166197 }
167198
168199 // rename the current executable
169200 if err := os.Rename(oldExec, renamedOldExec); err != nil {
170- return fmt.Errorf("Failed to rename the current executable: %w", err)
201+ return fmt.Errorf("failed to rename the current executable: %w", err)
171202 }
172203
173204 tryToRevertExecChanges := func() {
@@ -179,7 +210,7 @@ func (p *plugin) update() error {
179210 // replace with the extracted binary
180211 if err := os.Rename(newExec, oldExec); err != nil {
181212 tryToRevertExecChanges()
182- return fmt.Errorf("Failed replacing the executable: %w", err)
213+ return fmt.Errorf("failed replacing the executable: %w", err)
183214 }
184215
185216 color.HiBlack("---")
@@ -355,3 +386,21 @@ func compareVersions(a, b string) int {
355386
356387 return 0 // equal
357388 }
389+
390+// New function to check write permissions
391+func checkWritePermission(path string) error {
392+ // Try to create a temporary file in the same directory
393+ dir := filepath.Dir(path)
394+ tmpFile := filepath.Join(dir, ".gordon_update_test")
395+ f, err := os.Create(tmpFile)
396+ if err != nil {
397+ return err
398+ }
399+ f.Close()
400+ os.Remove(tmpFile)
401+ return nil
402+}
403+
404+func isRoot() bool {
405+ return os.Geteuid() == 0
406+}
@@ -19,6 +19,7 @@ import (
19 19
20 "github.com/AlecAivazis/survey/v2"20 "github.com/AlecAivazis/survey/v2"
21 "github.com/bnema/gordon/internal/cli"21 "github.com/bnema/gordon/internal/cli"
22+ "github.com/bnema/gordon/internal/cli/handler"
22 "github.com/bnema/gordon/pkg/docker"23 "github.com/bnema/gordon/pkg/docker"
23 "github.com/fatih/color"24 "github.com/fatih/color"
24 "github.com/pocketbase/pocketbase/tools/archive"25 "github.com/pocketbase/pocketbase/tools/archive"
@@ -76,7 +77,7 @@ func NewUpdateCommand(a *cli.App) *cobra.Command {
76 var needConfirm bool77 var needConfirm bool
77 if docker.IsRunningInContainer() {78 if docker.IsRunningInContainer() {
78 needConfirm = true79 needConfirm = true
79- color.Yellow("NB! It seems that you are in a Docker container.")80+ color.Yellow("It seems that you are in a Docker container.")
80 color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")81 color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
81 }82 }
82 83
@@ -92,6 +93,17 @@ func NewUpdateCommand(a *cli.App) *cobra.Command {
92 }93 }
93 }94 }
94 95
96+ // Vérifier si l'utilisateur est root
97+ if isRoot() {
98+ color.Yellow("Running update as root. Skipping configuration check.")
99+ return pluginInstance.update()
100+ }
101+
102+ // Si non root, effectuer la vérification des champs
103+ if err := handler.FieldCheck(a); err != nil {
104+ return fmt.Errorf("field check failed: %w", err)
105+ }
106+
95 return pluginInstance.update()107 return pluginInstance.update()
96 },108 },
97 }109 }
@@ -116,6 +128,17 @@ func (p *plugin) update() error {
116 return nil128 return nil
117 }129 }
118 130
131+ // Prompt user for update
132+ confirm := false
133+ prompt := &survey.Confirm{
134+ Message: fmt.Sprintf("A new version (%s) is available. Do you want to update?", latest.Tag),
135+ }
136+ survey.AskOne(prompt, &confirm)
137+ if !confirm {
138+ fmt.Println("Update cancelled by user.")
139+ return nil
140+ }
141+
119 suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)142 suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
120 if suffix == "" {143 if suffix == "" {
121 return errors.New("unsupported platform")144 return errors.New("unsupported platform")
@@ -156,18 +179,26 @@ func (p *plugin) update() error {
156 renamedOldExec := oldExec + ".old"179 renamedOldExec := oldExec + ".old"
157 defer os.Remove(renamedOldExec)180 defer os.Remove(renamedOldExec)
158 181
182+ // Check if the user has write permissions
183+ if err := checkWritePermission(oldExec); err != nil {
184+ color.Yellow("Administrator rights are required to perform the update.")
185+ color.Yellow("Please run the following command with sudo:")
186+ color.HiBlack("sudo gordon update")
187+ return nil
188+ }
189+
159 newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)190 newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
160 if _, err := os.Stat(newExec); err != nil {191 if _, err := os.Stat(newExec); err != nil {
161 // try again with an .exe extension192 // try again with an .exe extension
162 newExec = newExec + ".exe"193 newExec = newExec + ".exe"
163 if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {194 if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
164- return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)195+ return fmt.Errorf("the executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
165 }196 }
166 }197 }
167 198
168 // rename the current executable199 // rename the current executable
169 if err := os.Rename(oldExec, renamedOldExec); err != nil {200 if err := os.Rename(oldExec, renamedOldExec); err != nil {
170- return fmt.Errorf("Failed to rename the current executable: %w", err)201+ return fmt.Errorf("failed to rename the current executable: %w", err)
171 }202 }
172 203
173 tryToRevertExecChanges := func() {204 tryToRevertExecChanges := func() {
@@ -179,7 +210,7 @@ func (p *plugin) update() error {
179 // replace with the extracted binary210 // replace with the extracted binary
180 if err := os.Rename(newExec, oldExec); err != nil {211 if err := os.Rename(newExec, oldExec); err != nil {
181 tryToRevertExecChanges()212 tryToRevertExecChanges()
182- return fmt.Errorf("Failed replacing the executable: %w", err)213+ return fmt.Errorf("failed replacing the executable: %w", err)
183 }214 }
184 215
185 color.HiBlack("---")216 color.HiBlack("---")
@@ -355,3 +386,21 @@ func compareVersions(a, b string) int {
355 386
356 return 0 // equal387 return 0 // equal
357 }388 }
389+
390+// New function to check write permissions
391+func checkWritePermission(path string) error {
392+ // Try to create a temporary file in the same directory
393+ dir := filepath.Dir(path)
394+ tmpFile := filepath.Join(dir, ".gordon_update_test")
395+ f, err := os.Create(tmpFile)
396+ if err != nil {
397+ return err
398+ }
399+ f.Close()
400+ os.Remove(tmpFile)
401+ return nil
402+}
403+
404+func isRoot() bool {
405+ return os.Geteuid() == 0
406+}
A internal/cli/cmd/version.go +23 -0
@@ -0,0 +1,23 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/spf13/cobra"
8+)
9+
10+func NewVersionCommand(a *cli.App) *cobra.Command {
11+ return &cobra.Command{
12+ Use: "version",
13+ Short: "Affiche la version de Gordon",
14+ Run: func(cmd *cobra.Command, args []string) {
15+ version := a.Config.GetVersion()
16+ if version == "" {
17+ fmt.Println("Version : devel")
18+ } else {
19+ fmt.Printf("Version : %s\n", version)
20+ }
21+ },
22+ }
23+}
@@ -0,0 +1,23 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/cli"
7+ "github.com/spf13/cobra"
8+)
9+
10+func NewVersionCommand(a *cli.App) *cobra.Command {
11+ return &cobra.Command{
12+ Use: "version",
13+ Short: "Affiche la version de Gordon",
14+ Run: func(cmd *cobra.Command, args []string) {
15+ version := a.Config.GetVersion()
16+ if version == "" {
17+ fmt.Println("Version : devel")
18+ } else {
19+ fmt.Printf("Version : %s\n", version)
20+ }
21+ },
22+ }
23+}
M internal/cli/newclient.go +1 -0
@@ -16,6 +16,7 @@ func NewClientApp(buildConfig *common.BuildConfig) (*App, error) {
1616 config := common.Config{
1717 Build: *buildConfig,
1818 }
19+
1920 _, err := config.LoadConfig()
2021 if err != nil {
2122 return nil, fmt.Errorf("failed to load configuration: %w", err)
@@ -16,6 +16,7 @@ func NewClientApp(buildConfig *common.BuildConfig) (*App, error) {
16 config := common.Config{16 config := common.Config{
17 Build: *buildConfig,17 Build: *buildConfig,
18 }18 }
19+
19 _, err := config.LoadConfig()20 _, err := config.LoadConfig()
20 if err != nil {21 if err != nil {
21 return nil, fmt.Errorf("failed to load configuration: %w", err)22 return nil, fmt.Errorf("failed to load configuration: %w", err)
M internal/common/version.go +9 -26
@@ -7,11 +7,9 @@ import (
77 "log"
88 "net/http"
99 "runtime"
10- "strings"
1110 "time"
1211
1312 "github.com/bnema/gordon/pkg/docker"
14- "github.com/fatih/color"
1513 )
1614
1715 type CurrentVersion struct {
@@ -28,9 +26,8 @@ type VersionInfo struct {
2826 }
2927
3028 type VersionsResponse struct {
31- Name string `json:"name"`
32- AMD64 VersionInfo `json:"amd64"`
33- ARM64 VersionInfo `json:"arm64"`
29+ AMD64 string `json:"amd64"`
30+ ARM64 string `json:"arm64"`
3431 }
3532
3633 func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {
@@ -61,35 +58,22 @@ func checkAndUpdateVersion(c *Config) (string, error) {
6158 var remoteVersion string
6259 switch getArch() {
6360 case "amd64":
64- remoteVersion = remoteVersions.AMD64.Name
61+ remoteVersion = remoteVersions.AMD64
6562 case "arm64":
66- remoteVersion = remoteVersions.ARM64.Name
63+ remoteVersion = remoteVersions.ARM64
6764 default:
68- remoteVersion = remoteVersions.Name
65+ return "", fmt.Errorf("unsupported architecture: %s", getArch())
6966 }
7067
71- // Check if remoteVersion is empty or doesn't contain the architecture suffix
72- if remoteVersion == "" || !strings.Contains(remoteVersion, getArch()) {
68+ // Check if remoteVersion is empty
69+ if remoteVersion == "" {
7370 return "", fmt.Errorf("remote version not available for architecture %s", getArch())
7471 }
7572
76- remoteVersion = remoteVersion[:len(remoteVersion)-len(getArch())-1]
7773 message := CheckForNewVersion(localVersion.Version, remoteVersion)
7874
79- // if local version is empty that means dev mode is on so we don't need to check for updates
80- if localVersion.Version == "" {
81- return "", nil
82- }
83-
84- if message != "" {
85- // Create a new color attribute
86- green := color.New(color.FgGreen).SprintFunc()
87- // Use the color attribute to format the string with ANSI codes
88- coloredMessage := green(fmt.Sprintf("New version %s is available", remoteVersion))
89- return coloredMessage, nil
90- }
91-
92- return "", nil
75+ // Return the message without an error, even if it's empty
76+ return message, nil
9377 }
9478
9579 func CheckVersionPeriodically(c *Config) (string, error) {
@@ -151,5 +135,4 @@ func getRemoteVersion(c *Config) (VersionsResponse, error) {
151135 }
152136
153137 return remoteVersionInfo, nil
154-
155138 }
@@ -7,11 +7,9 @@ import (
7 "log"7 "log"
8 "net/http"8 "net/http"
9 "runtime"9 "runtime"
10- "strings"
11 "time"10 "time"
12 11
13 "github.com/bnema/gordon/pkg/docker"12 "github.com/bnema/gordon/pkg/docker"
14- "github.com/fatih/color"
15 )13 )
16 14
17 type CurrentVersion struct {15 type CurrentVersion struct {
@@ -28,9 +26,8 @@ type VersionInfo struct {
28 }26 }
29 27
30 type VersionsResponse struct {28 type VersionsResponse struct {
31- Name string `json:"name"`29+ AMD64 string `json:"amd64"`
32- AMD64 VersionInfo `json:"amd64"`30+ ARM64 string `json:"arm64"`
33- ARM64 VersionInfo `json:"arm64"`
34 }31 }
35 32
36 func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {33 func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {
@@ -61,35 +58,22 @@ func checkAndUpdateVersion(c *Config) (string, error) {
61 var remoteVersion string58 var remoteVersion string
62 switch getArch() {59 switch getArch() {
63 case "amd64":60 case "amd64":
64- remoteVersion = remoteVersions.AMD64.Name61+ remoteVersion = remoteVersions.AMD64
65 case "arm64":62 case "arm64":
66- remoteVersion = remoteVersions.ARM64.Name63+ remoteVersion = remoteVersions.ARM64
67 default:64 default:
68- remoteVersion = remoteVersions.Name65+ return "", fmt.Errorf("unsupported architecture: %s", getArch())
69 }66 }
70 67
71- // Check if remoteVersion is empty or doesn't contain the architecture suffix68+ // Check if remoteVersion is empty
72- if remoteVersion == "" || !strings.Contains(remoteVersion, getArch()) {69+ if remoteVersion == "" {
73 return "", fmt.Errorf("remote version not available for architecture %s", getArch())70 return "", fmt.Errorf("remote version not available for architecture %s", getArch())
74 }71 }
75 72
76- remoteVersion = remoteVersion[:len(remoteVersion)-len(getArch())-1]
77 message := CheckForNewVersion(localVersion.Version, remoteVersion)73 message := CheckForNewVersion(localVersion.Version, remoteVersion)
78 74
79- // if local version is empty that means dev mode is on so we don't need to check for updates75+ // Return the message without an error, even if it's empty
80- if localVersion.Version == "" {76+ return message, nil
81- return "", nil
82- }
83-
84- if message != "" {
85- // Create a new color attribute
86- green := color.New(color.FgGreen).SprintFunc()
87- // Use the color attribute to format the string with ANSI codes
88- coloredMessage := green(fmt.Sprintf("New version %s is available", remoteVersion))
89- return coloredMessage, nil
90- }
91-
92- return "", nil
93 }77 }
94 78
95 func CheckVersionPeriodically(c *Config) (string, error) {79 func CheckVersionPeriodically(c *Config) (string, error) {
@@ -151,5 +135,4 @@ func getRemoteVersion(c *Config) (VersionsResponse, error) {
151 }135 }
152 136
153 return remoteVersionInfo, nil137 return remoteVersionInfo, nil
154-
155 }138 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close