From 8ca44485b4c48ce05fbd2fd8c1fd862d7296aab1 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 22 Apr 2024 13:12:14 +0800 Subject: [PATCH] feat: add the `--all` flag to the `install` command (#216) * feat: Add the `--all` flag to the `install` command * update docs --- cmd/commands/install.go | 168 +++++++++++++++++++++++++++- docs/usage/core-commands.md | 4 + docs/zh-hans/usage/core-commands.md | 6 +- internal/manager.go | 8 +- internal/sdk.go | 14 +-- 5 files changed, 183 insertions(+), 17 deletions(-) diff --git a/cmd/commands/install.go b/cmd/commands/install.go index 2bf5f330..42498e16 100644 --- a/cmd/commands/install.go +++ b/cmd/commands/install.go @@ -17,21 +17,36 @@ package commands import ( - "strings" - + "errors" + "fmt" + "github.com/pterm/pterm" "github.com/urfave/cli/v2" "github.com/version-fox/vfox/internal" + "github.com/version-fox/vfox/internal/toolset" + "os" + "strings" ) var Install = &cli.Command{ - Name: "install", - Aliases: []string{"i"}, - Usage: "Install a version of the target SDK", + Name: "install", + Aliases: []string{"i"}, + Usage: "Install a version of the target SDK", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Aliases: []string{"a"}, + Usage: "Install all SDK versions recorded in .tool-versions", + }, + }, Action: installCmd, Category: CategorySDK, } func installCmd(ctx *cli.Context) error { + if ctx.Bool("all") { + return installAll() + } + sdkArg := ctx.Args().First() if sdkArg == "" { return cli.Exit("sdk name is required", 1) @@ -59,3 +74,146 @@ func installCmd(ctx *cli.Context) error { return source.Install(version) } } + +func installAll() error { + manager := internal.NewSdkManager() + defer manager.Close() + + plugins, sdks, err := notInstalled(manager) + if err != nil { + return err + } + if len(plugins) == 0 && len(sdks) == 0 { + fmt.Println("All plugins and SDKs are already installed") + return nil + } + + fmt.Println("Install the following plugins and SDKs:") + printPlugin(plugins, nil) + printSdk(sdks, nil) + if result, _ := pterm.DefaultInteractiveConfirm. + WithDefaultValue(true). + Show("Do you want to install these plugins and SDKs?"); !result { + return nil + } + + var ( + count = len(plugins) + len(sdks) + index = 0 + errorStr string + stdout = os.Stdout + stderr = os.Stderr + pluginsResult = make(map[string]bool) + sdksResult = make(map[string]bool) + ) + os.Stdout = nil + os.Stderr = nil + pterm.SetDefaultOutput(os.Stdout) + + spinnerInfo, _ := pterm.DefaultSpinner. + WithSequence([]string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "}...). + WithText("Installing..."). + WithWriter(stdout). + Start() + for _, plugin := range plugins { + index++ + spinnerInfo.UpdateText(fmt.Sprintf("[%v/%v] %s: %s installing...\033[K", index, count, "Plugin", plugin)) + pluginsResult[plugin] = false + if err := manager.Add(plugin, "", ""); err != nil { + if errors.Is(err, internal.ManifestNotFound) { + errorStr = fmt.Sprintf("%s\n[%s] not found in remote registry, please check the name", errorStr, plugin) + } else { + errorStr = fmt.Sprintf("%s\n%s", errorStr, err) + } + continue + } + pluginsResult[plugin] = true + } + for sdk, version := range sdks { + index++ + spinnerInfo.UpdateText(fmt.Sprintf("[%v/%v] %s: %s@%s installing...\033[K", index, count, "SDK", sdk, version)) + sdkVersion := fmt.Sprintf("%s@%s", sdk, version) + sdksResult[sdkVersion] = false + lookupSdk, err := manager.LookupSdk(sdk) + if err != nil { + errorStr = fmt.Sprintf("%s\n%s", errorStr, err) + continue + } + err = lookupSdk.Install(internal.Version(version)) + if err != nil { + errorStr = fmt.Sprintf("%s\n%s", errorStr, err) + continue + } + sdksResult[sdkVersion] = true + } + spinnerInfo.UpdateText(fmt.Sprintf("[%v/%v] Installation completed.\033[K", count, count)) + _ = spinnerInfo.Stop() + os.Stdout = stdout + os.Stderr = stderr + pterm.SetDefaultOutput(os.Stdout) + + fmt.Printf("%s indicates successful installation, while %s indicates installation failure.\n", pterm.Green("Green"), pterm.Red("red")) + printPlugin(plugins, pluginsResult) + printSdk(sdks, sdksResult) + + if len(errorStr) > 0 { + fmt.Println(errorStr) + } + return nil +} + +func notInstalled(manager *internal.Manager) (plugins []string, sdks map[string]string, err error) { + tvs, err := toolset.NewMultiToolVersions([]string{ + manager.PathMeta.WorkingDirectory, + manager.PathMeta.CurTmpPath, + manager.PathMeta.HomePath, + }) + if err != nil { + return + } + sdks = tvs.FilterTools(func(name, version string) bool { + lookupSdk, err := manager.LookupSdk(name) + if err != nil { + plugins = append(plugins, name) + return true + } + if !lookupSdk.CheckExists(internal.Version(version)) { + return true + } + return false + }) + return +} + +func printPlugin(plugins []string, result map[string]bool) { + if len(plugins) > 0 { + fmt.Println("Plugin:") + for _, plugin := range plugins { + if result != nil { + if result[plugin] { + plugin = pterm.Green(plugin) + } else { + plugin = pterm.Red(plugin) + } + } + + fmt.Printf(" %s\n", plugin) + } + } +} + +func printSdk(sdks map[string]string, result map[string]bool) { + fmt.Println("SDK:") + for sdk, version := range sdks { + sdkVersion := fmt.Sprintf("%s@%s", sdk, version) + if result != nil { + if result[sdkVersion] { + sdkVersion = pterm.Green(sdkVersion) + } else { + sdkVersion = pterm.Red(sdkVersion) + } + } + + fmt.Printf(" %s\n", sdkVersion) + } +} diff --git a/docs/usage/core-commands.md b/docs/usage/core-commands.md index b53ff219..ca4f3ca1 100644 --- a/docs/usage/core-commands.md +++ b/docs/usage/core-commands.md @@ -37,6 +37,10 @@ vfox i @ `version`: The version to install +**Options** + +- `-a, --all`: Install all SDK versions recorded in .tool-versions + ::: tip `search` command will retrieve the plugin from the remote repository and add it locally if it is not installed locally. ::: diff --git a/docs/zh-hans/usage/core-commands.md b/docs/zh-hans/usage/core-commands.md index 67a087e6..2d277f4e 100644 --- a/docs/zh-hans/usage/core-commands.md +++ b/docs/zh-hans/usage/core-commands.md @@ -37,6 +37,10 @@ vfox i @ `version`: 需要安装的版本号 +**选项** + +- `-a, --all`: 安装 .tool-versions 中记录的所有 SDK 版本 + ::: tip 自动安装 如果本地没有安装 SDK,`install`命令会从远端仓库检索插件并安装到本地。 ::: @@ -120,7 +124,7 @@ vfox c vfox cd [options] [] ``` -`sdk-name`: SDK 名称, 不传默认为 `VFOX_HOME`. +`sdk-name`: SDK 名称, 不传默认为 `VFOX_HOME`。 **选项** diff --git a/internal/manager.go b/internal/manager.go index 18be74d1..7b1f4de8 100644 --- a/internal/manager.go +++ b/internal/manager.go @@ -43,7 +43,7 @@ const ( ) var ( - manifestNotFound = errors.New("manifest not found") + ManifestNotFound = errors.New("manifest not found") ) type NotFoundError struct { @@ -131,7 +131,7 @@ func (m *Manager) LookupSdkWithInstall(name string) (*Sdk, error) { Show(); result { manifest, err := m.fetchPluginManifest(m.GetRegistryAddress(name + ".json")) - if errors.Is(err, manifestNotFound) { + if errors.Is(err, ManifestNotFound) { return nil, fmt.Errorf("[%s] not found in remote registry, please check the name", pterm.LightRed(name)) } @@ -236,7 +236,7 @@ func (m *Manager) Update(pluginName string) error { logger.Debugf("Fetching plugin %s from %s...\n", pluginName, address) registryManifest, err := m.fetchPluginManifest(address) if err != nil { - if errors.Is(err, manifestNotFound) { + if errors.Is(err, ManifestNotFound) { if sdk.Plugin.ManifestUrl != "" { logger.Debugf("Fetching plugin %s from %s...\n", pluginName, sdk.Plugin.ManifestUrl) du, err := m.fetchPluginManifest(sdk.Plugin.ManifestUrl) @@ -324,7 +324,7 @@ func (m *Manager) fetchPluginManifest(url string) (*RegistryPluginManifest, erro } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return nil, manifestNotFound + return nil, ManifestNotFound } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("fetch manifest error, status code: %d", resp.StatusCode) diff --git a/internal/sdk.go b/internal/sdk.go index 5329b34d..7922b9fc 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -50,7 +50,7 @@ type Sdk struct { func (b *Sdk) Install(version Version) error { label := b.label(version) - if b.checkExists(version) { + if b.CheckExists(version) { return fmt.Errorf("%s is already installed", label) } installInfo, err := b.Plugin.PreInstall(version) @@ -65,7 +65,7 @@ func (b *Sdk) Install(version Version) error { // A second check is required because the plug-in may change the version number, // for example, latest is resolved to a specific version number. label = b.label(mainSdk.Version) - if b.checkExists(mainSdk.Version) { + if b.CheckExists(mainSdk.Version) { return fmt.Errorf("%s is already installed", label) } success := false @@ -194,7 +194,7 @@ func (b *Sdk) preInstallSdk(info *Info, sdkDestPath string) (string, error) { func (b *Sdk) Uninstall(version Version) error { label := b.label(version) - if !b.checkExists(version) { + if !b.CheckExists(version) { return fmt.Errorf("%s is not installed", pterm.Red(label)) } if b.Current() == version { @@ -215,7 +215,7 @@ func (b *Sdk) Available(args []string) ([]*Package, error) { func (b *Sdk) EnvKeys(version Version) (*env.Envs, error) { label := b.label(version) - if !b.checkExists(version) { + if !b.CheckExists(version) { return nil, fmt.Errorf("%s is not installed", label) } sdkPackage, err := b.GetLocalSdkPackage(version) @@ -266,7 +266,7 @@ func (b *Sdk) Use(version Version, scope UseScope) error { } label := b.label(version) - if !b.checkExists(version) { + if !b.CheckExists(version) { return fmt.Errorf("%s is not installed", label) } @@ -411,7 +411,7 @@ func (b *Sdk) Current() Version { return "" } current := toolVersion.FilterTools(func(name, version string) bool { - return name == b.Plugin.SdkName && b.checkExists(Version(version)) + return name == b.Plugin.SdkName && b.CheckExists(Version(version)) }) if len(current) == 0 { return "" @@ -524,7 +524,7 @@ func (b *Sdk) GetLocalSdkPackage(version Version) (*Package, error) { }, nil } -func (b *Sdk) checkExists(version Version) bool { +func (b *Sdk) CheckExists(version Version) bool { return util.FileExists(b.VersionPath(version)) }