diff --git a/src/archiveClient/handler_findFilesByRules_test.go b/src/archiveClient/handler_findFilesByRules_test.go index f66c29e9..b4e35915 100644 --- a/src/archiveClient/handler_findFilesByRules_test.go +++ b/src/archiveClient/handler_findFilesByRules_test.go @@ -1,4 +1,5 @@ //go:build exclude + package archiveClient import ( diff --git a/src/archiveClient/handler_findGitFiles_test.go b/src/archiveClient/handler_findGitFiles_test.go index eba2eca0..0ed05974 100644 --- a/src/archiveClient/handler_findGitFiles_test.go +++ b/src/archiveClient/handler_findGitFiles_test.go @@ -1,4 +1,5 @@ //go:build exclude + package archiveClient import ( diff --git a/src/cmd/root.go b/src/cmd/root.go index 0b8bb536..ab88433f 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -3,6 +3,8 @@ package cmd import ( "context" "fmt" + "os" + "os/exec" "github.com/zeropsio/zcli/src/cmd/scope" "github.com/zeropsio/zcli/src/cmdBuilder" @@ -34,7 +36,21 @@ func rootCmd() *cmdBuilder.Cmd { AddChildrenCmd(servicePushCmd()). AddChildrenCmd(envCmd()). AddChildrenCmd(supportCmd()). + AddChildrenCmd(updateCmd()). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + zcliPath := fmt.Sprintf("%s/.local/bin/zcli", homeDir) + cmd := exec.CommandContext(ctx, zcliPath, "update") + cmd.Stdout = cmdData.Stdout.GetWriter() + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to execute 'zcli update': %w", err) + } + cmdData.Stdout.PrintLines( i18n.T(i18n.GuestWelcome), printer.EmptyLine, diff --git a/src/cmd/update.go b/src/cmd/update.go new file mode 100644 index 00000000..18941a13 --- /dev/null +++ b/src/cmd/update.go @@ -0,0 +1,147 @@ +package cmd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "os" + "os/exec" + "runtime" + "time" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/i18n" +) + +// other necessary imports + +func updateCmd() *cmdBuilder.Cmd { + // check existing zcli version + return cmdBuilder.NewCmd(). + Use("update"). + Short(i18n.T(i18n.CmdDescUpdate)). + HelpFlag(i18n.T(i18n.CmdHelpUpdate)). + GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { + // print the current version of zcli + if version != "" { + latestVersion, err := getLatestGitHubRelease(ctx) + if err != nil { + return err + } + + if latestVersion.TagName != version { + fmt.Println("There is a new version available:", latestVersion.TagName) + fmt.Println("Do you want to update? (y/n)") + var input string + if _, err := fmt.Scanln(&input); err != nil { + fmt.Println("Failed to read input:", err) + return err + } + + if input == "y" { + target := determineTargetArchitecture() + if err := downloadAndInstallZCLI(ctx, target); err != nil { + return err + } + fmt.Println("zCLI was updated successfully to", latestVersion.TagName) + } else { + fmt.Println("Update canceled.") + } + } else { + fmt.Println("You are using the latest version of zcli") + } + } else { + fmt.Println("You are using the development environment of zcli") + } + return nil + }) +} + +type GitHubRelease struct { + TagName string `json:"tagName"` + Body string `json:"body"` +} + +func getLatestGitHubRelease(ctx context.Context) (GitHubRelease, error) { + // GitHub repository details + repoOwner := "zeropsio" + repoName := "zcli" + + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName) + + client := http.Client{ + Timeout: 4 * time.Second, + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return GitHubRelease{}, err + } + + resp, err := client.Do(req) + if err != nil { + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return GitHubRelease{}, nil + } + return GitHubRelease{}, err + } + defer func() { + if err := resp.Body.Close(); err != nil { + fmt.Println("erorr") + } + }() + + var release GitHubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return GitHubRelease{}, err + } + return release, nil +} + +func determineTargetArchitecture() string { + switch runtime.GOOS + " " + runtime.GOARCH { + case "darwin amd64": + return "darwin-amd64" + case "darwin arm64": + return "darwin-arm64" + case "linux 386": + return "linux-i386" + default: + return "linux-amd64" + } +} + +func downloadAndInstallZCLI(ctx context.Context, target string) error { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + binDir := fmt.Sprintf("%s/.local/bin", homeDir) + binPath := fmt.Sprintf("%s/zcli", binDir) + + if _, err := os.Stat(binDir); os.IsNotExist(err) { + if err := os.MkdirAll(binDir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", binDir, err) + } + } + + zcliURI := fmt.Sprintf("https://github.com/zeropsio/zcli/releases/latest/download/zcli-%s", target) + curlCmd := fmt.Sprintf("curl --fail --location --progress-bar --output %s %s", binPath, zcliURI) + cmd := exec.CommandContext(ctx, "sh", "-c", curlCmd) + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to download zcli: %w", err) + } + + if err := os.Chmod(binPath, 0755); err != nil { + return fmt.Errorf("failed to make zcli executable: %w", err) + } + + fmt.Printf("zCLI was installed successfully to %s\n", binPath) + return nil +} diff --git a/src/i18n/en.go b/src/i18n/en.go index 1053ae3e..19ebf2ea 100644 --- a/src/i18n/en.go +++ b/src/i18n/en.go @@ -183,6 +183,10 @@ and your %s.`, CmdDescStatusShowDebugLogs: "Shows zCLI debug logs", DebugLogsNotFound: "Debug logs not found", + // update + CmdHelpUpdate: "the update command", + CmdDescUpdate: "Updates zCLI to the latest version", + // version CmdHelpVersion: "the version command.", CmdDescVersion: "Shows the current zCLI version", diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index 55091c49..b933aaed 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -168,6 +168,10 @@ const ( CmdDescStatusShowDebugLogs = "CmdDescStatusShowDebugLogs" DebugLogsNotFound = "DebugLogsNotFound" + // update + CmdHelpUpdate = "CmdHelpUpdate" + CmdDescUpdate = "CmdDescUpdate" + // version CmdHelpVersion = "CmdHelpVersion" CmdDescVersion = "CmdDescVersion" diff --git a/src/printer/printer.go b/src/printer/printer.go index 8a0108fb..22480510 100644 --- a/src/printer/printer.go +++ b/src/printer/printer.go @@ -46,3 +46,7 @@ func Style(s lipgloss.Style, text string) string { } return s.Render(text) } + +func (p *Printer) GetWriter() io.Writer { + return p.out +}