From 22e9e0df29a3c6829965e442f5135255d381ae71 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 1 Mar 2024 10:43:23 +0800 Subject: [PATCH 01/24] docs: about Plugin and SDK --- docs/guides/faq.md | 28 +++++++++++++++++++++++++++- docs/guides/quick-start.md | 12 ++++++++++++ docs/zh-hans/guides/faq.md | 23 ++++++++++++++++++++++- docs/zh-hans/guides/quick-start.md | 10 ++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/guides/faq.md b/docs/guides/faq.md index 24b86c15..4e2af0e2 100644 --- a/docs/guides/faq.md +++ b/docs/guides/faq.md @@ -5,4 +5,30 @@ If your shell prompt `Warning: The current shell lacks hook support or configuration. It has switched to global scope automatically` that means you do not hook `vfox` into your shell, please hook it manually first. -Please refer to [Quick Start#_2-hook-vfox-to-your-shell](./quick-start.md#_2-hook-vfox-to-your-shell) to manually hook `vfox` into your shell. \ No newline at end of file +Please refer to [Quick Start#_2-hook-vfox-to-your-shell](./quick-start.md#_2-hook-vfox-to-your-shell) to manually hook `vfox` into your shell. + +## Why can't I delete the plugin? + +```text +I first added java/adoptium-jdk, then tried to install v21, because the download was slow, I exited halfway, and then tried +to remove the plugin with the remove command, and got the error message "java/adoptium-jdk not installed". + +So I want to switch to another source, when I execute "vfox add java/azul-jdk", I also get the error message "plugin java +already exists", now I can't move forward or backward. + +``` + +In the `vfox` concept, the plugin is the SDK, and the SDK is the plugin. You can think of the plugin as an extension of `vfox` +to manage different tools and runtime environments. + +Taking the `nodejs/npmmirror` plugin as an example, `nodejs` is the category, `npmmirror` is the plugin name, and the +**SDK name** marked by the `name` field inside the plugin. + +So, when deleting the plugin, you need to use the **SDK name** (here is `nodejs`) for deletion, not the plugin name +`nodejs/npmirror` or `npmmirror`. + +```bash +$ vfox remove nodejs +``` + +before deleting, you can use `vfox ls` to view the currently installed plugins (i.e. SDK names), and then delete them. \ No newline at end of file diff --git a/docs/guides/quick-start.md b/docs/guides/quick-start.md index bafb356d..0e64dd5a 100644 --- a/docs/guides/quick-start.md +++ b/docs/guides/quick-start.md @@ -116,6 +116,18 @@ If you don't know which plugin to add, you can use the `vfox available` command $ vfox add nodejs/nodejs ``` +::: tip About plugins and SDKs +In the `vfox` concept, the plugin is the SDK, and the SDK is the plugin. You can think of the plugin as an extension of `vfox` +to manage different tools and runtime environments. + +Taking the `nodejs/npmmirror` plugin as an example, `nodejs` is the category, `npmmirror` is the plugin name, and the +**SDK name** marked by the `name` field inside the plugin. + +So, when deleting the plugin, you need to use the **SDK name** (here is `nodejs`) for deletion, not the plugin name +`nodejs/npmirror` or `npmmirror`. + +::: + ## 4. Install a runtime After the plugin is successfully installed, you can install the corresponding version of Nodejs. diff --git a/docs/zh-hans/guides/faq.md b/docs/zh-hans/guides/faq.md index 5baa71f1..144b4185 100644 --- a/docs/zh-hans/guides/faq.md +++ b/docs/zh-hans/guides/faq.md @@ -6,4 +6,25 @@ 如果你看到提示`Warning: The current shell lacks hook support or configuration. It has switched to global scope automatically` 则说明你没有将`vfox`正确挂在到你的`Shell`上。 -请按照[快速入门#_2-挂载vfox到你的shell](./quick-start.md#_2-挂载vfox到你的shell)步骤进行手动挂载。 \ No newline at end of file +请按照[快速入门#_2-挂载vfox到你的shell](./quick-start.md#_2-挂载vfox到你的shell)步骤进行手动挂载。 + + +## 为什么我删除不了插件? + +```text +我先是 add 了 java/adoptium-jdk ,然后尝试安装 v21 ,因为下载慢就中途退出了,然后尝试 remove 命令去掉这个 plugin ,得到错误信息 "java/adoptium-jdk not installed"。 + +那么我想换另一个源,执行 "vfox add java/azul-jdk" 时,也得到错误信息 "plugin java already exists",现在是进退不能了。 +``` + +在`vfox`理念中, 插件即SDK、SDK即插件. 你可以将插件理解为`vfox`的一种扩展, 用于管理不同的工具和运行环境。 + +以`nodejs/npmmirror`插件为例, `nodejs`是分类, `npmmirror`是插件名, 插件内部`name`字段标注的叫**SDK名**。 + +所以, 在删除插件时, 需要使用**SDK名**(这里就是`nodejs`)进行删除, 而不是插件名`nodejs/npmirror`或`npmmirror`。 + +```bash +$ vfox remove nodejs +``` + +在删除之前, 你可以使用`vfox ls`查看当前已安装的插件(即SDK名称), 然后再进行删除。 \ No newline at end of file diff --git a/docs/zh-hans/guides/quick-start.md b/docs/zh-hans/guides/quick-start.md index 599c1a76..84b0fe8c 100644 --- a/docs/zh-hans/guides/quick-start.md +++ b/docs/zh-hans/guides/quick-start.md @@ -98,6 +98,16 @@ Invoke-Expression "$(vfox activate pwsh)" ```bash $ vfox add nodejs/npmmirror ``` +::: tip 关于插件和SDK的关系 +在`vfox`理念中, 插件即SDK、SDK即插件. 你可以将插件理解为`vfox`的一种扩展, 用于管理不同的工具和运行环境。 + +以`nodejs/npmmirror`插件为例, `nodejs`是分类, `npmmirror`是插件名, 插件内部`name`字段标注的叫**SDK名**。 + +所以, 在删除插件时, 需要使用**SDK名**进行删除, 而不是插件名`nodejs/npmirror`或`npmmirror`。 + +::: + + ## 4. 安装运行时 From 65838e86d0e20a52f443eeafea797b75e2d360b6 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 1 Mar 2024 15:19:13 +0800 Subject: [PATCH 02/24] bugfix: Prioritize displaying the versions of project, session, and global records when using `current` command. fix #61 --- cmd/commands/current.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/commands/current.go b/cmd/commands/current.go index 6d6807d9..2be57173 100644 --- a/cmd/commands/current.go +++ b/cmd/commands/current.go @@ -32,7 +32,7 @@ var Current = &cli.Command{ } func currentCmd(ctx *cli.Context) error { - manager := internal.NewSdkManager() + manager := internal.NewSdkManager(internal.GlobalRecordSource, internal.SessionRecordSource, internal.ProjectRecordSource) defer manager.Close() sdkName := ctx.Args().First() if sdkName == "" { From cdb5784c23fcd92be8ebed2edd98d0c558afca8e Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 1 Mar 2024 15:36:29 +0800 Subject: [PATCH 03/24] bugfix: handling interruptions during installation fix #63 --- internal/sdk.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/sdk.go b/internal/sdk.go index f99f08b6..437638b9 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -24,9 +24,11 @@ import ( "net/http" "net/url" "os" + "os/signal" "path/filepath" "sort" "strings" + "syscall" "github.com/schollz/progressbar/v3" "github.com/version-fox/vfox/internal/env" @@ -61,6 +63,19 @@ func (b *Sdk) Install(version Version) error { success := false newDirPath := b.VersionPath(mainSdk.Version) + sigs := make(chan os.Signal, 1) + + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + go func() { + _ = <-sigs + if !success { + _ = os.RemoveAll(newDirPath) + } + print("invoke la") + os.Exit(0) + }() + // Delete directory after failed installation defer func() { if !success { From 42954690a0bd6bc0cc2fd7a6561b8dfe45bf9ad1 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 1 Mar 2024 15:36:51 +0800 Subject: [PATCH 04/24] clean --- internal/sdk.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/sdk.go b/internal/sdk.go index 437638b9..15a0e62c 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -72,7 +72,6 @@ func (b *Sdk) Install(version Version) error { if !success { _ = os.RemoveAll(newDirPath) } - print("invoke la") os.Exit(0) }() From a58fe64bb59a749cb86273f34107c57fd9570192 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 1 Mar 2024 17:15:23 +0800 Subject: [PATCH 05/24] chore: update readme --- README.md | 2 +- README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a798a1b0..7b3fb687 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ See [vfox.lhan.me](https://vfox.lhan.me) for full documentation. > If you have installed `vfox`, you can view all available plugins with the `vfox available` command. -[![plugins](https://skillicons.dev/icons?i=java,kotlin,nodejs,flutter,dotnet,python,dart,golang,maven,zig,deno&theme=light)](https://github.com/version-fox/version-fox-plugins) +[![plugins](https://skillicons.dev/icons?i=java,kotlin,nodejs,flutter,dotnet,python,dart,golang,gradle,maven,zig,deno&theme=light)](https://github.com/version-fox/version-fox-plugins) For more details, see the [version-fox-plugins](https://github.com/version-fox/version-fox-plugins) diff --git a/README_CN.md b/README_CN.md index f558bd01..aa5ce72f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -73,7 +73,7 @@ $ node -v > 如果您已经安装了 `vfox`,您可以使用 `vfox available` 命令查看所有可用的插件。 -[![plugins](https://skillicons.dev/icons?i=java,kotlin,nodejs,flutter,dotnet,python,dart,golang,maven,zig,deno&theme=light)](https://github.com/version-fox/version-fox-plugins) +[![plugins](https://skillicons.dev/icons?i=java,kotlin,nodejs,flutter,dotnet,python,dart,golang,gradle,maven,zig,deno&theme=light)](https://github.com/version-fox/version-fox-plugins) 详细内容,请看 [version-fox-plugins](https://github.com/version-fox/version-fox-plugins) From bda08abad55e3c20653b033999961b8e8ff93224 Mon Sep 17 00:00:00 2001 From: Chance Date: Sat, 2 Mar 2024 09:21:29 +0800 Subject: [PATCH 06/24] bugfix: Prints the searched version according to the terminal height (#65) --- cmd/commands/search.go | 6 +++++- go.mod | 1 + go.sum | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/commands/search.go b/cmd/commands/search.go index c035ce25..6c8a2a4e 100644 --- a/cmd/commands/search.go +++ b/cmd/commands/search.go @@ -21,6 +21,9 @@ import ( "github.com/urfave/cli/v2" "github.com/version-fox/vfox/internal" "github.com/version-fox/vfox/internal/printer" + "golang.org/x/crypto/ssh/terminal" + "math" + "os" "strings" ) @@ -48,10 +51,11 @@ func searchCmd(ctx *cli.Context) error { if len(result) == 0 { return fmt.Errorf("no available version") } + _, height, _ := terminal.GetSize(int(os.Stdout.Fd())) kvSelect := printer.PageKVSelect{ TopText: "Please select a version of " + sdkName, Filter: true, - Size: 20, + Size: int(math.Min(math.Max(float64(height-3), 1), 20)), SourceFunc: func(page, size int) ([]*printer.KV, error) { start := page * size end := start + size diff --git a/go.mod b/go.mod index 8e7aa086..5e15ec2a 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/ulikunitz/xz v0.5.11 github.com/urfave/cli/v2 v2.26.0 github.com/yuin/gopher-lua v1.1.1 + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/sys v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index e52c0514..326da488 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= From b0e9b03bd79a156de0ba03c713724edb35e9fdd1 Mon Sep 17 00:00:00 2001 From: Alexis Kalabura <55039048+axdank@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:00:57 -0300 Subject: [PATCH 07/24] feat: allow json output for `env` subcommand (#64) Everyone can implement it in their own way, or if there is no supported shell --- cmd/commands/env.go | 52 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/cmd/commands/env.go b/cmd/commands/env.go index aa46cf34..877ca828 100644 --- a/cmd/commands/env.go +++ b/cmd/commands/env.go @@ -17,9 +17,11 @@ package commands import ( + "encoding/json" "fmt" "github.com/urfave/cli/v2" "github.com/version-fox/vfox/internal" + "github.com/version-fox/vfox/internal/env" "github.com/version-fox/vfox/internal/shell" ) @@ -37,12 +39,60 @@ var Env = &cli.Command{ Aliases: []string{"c"}, Usage: "cleanup temp file", }, + &cli.BoolFlag{ + Name: "json", + Aliases: []string{"j"}, + Usage: "get envs as json", + }, }, Action: envCmd, } func envCmd(ctx *cli.Context) error { - if ctx.IsSet("cleanup") { + if ctx.IsSet("json") { + type SDKs map[string]map[string]string + data := struct { + IsHookEnv bool `json:"is_hook_env"` + Paths []string `json:"paths"` + SDKs SDKs `json:"sdks"` + }{ + IsHookEnv: env.IsHookEnv(), + Paths: []string{}, + SDKs: make(SDKs), + } + manager := internal.NewSdkManager() + defer manager.Close() + allSdk, loadErr := manager.LoadAllSdk() + if loadErr != nil { + return loadErr + } + for name, s := range allSdk { + current := s.Current() + if current != "" { + envs, envErr := s.EnvKeys(current) + if envErr != nil { + return envErr + } + newEnv := make(map[string]string) + for k, v := range envs { + if k == "PATH" { + data.Paths = append(data.Paths, *v) + } else { + newEnv[k] = *v + } + } + if len(newEnv) > 0 { + data.SDKs[name] = newEnv + } + } + } + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + fmt.Println(string(jsonData)) + return nil + } else if ctx.IsSet("cleanup") { manager := internal.NewSdkManager() defer manager.Close() // Clean up the old temp files, before today. From 6aa76f9061eebafaf97ec71ea29a8967cc29a6a7 Mon Sep 17 00:00:00 2001 From: Han Li Date: Sat, 2 Mar 2024 19:52:25 +0800 Subject: [PATCH 08/24] Create FUNDING.yml --- .github/FUNDING.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..25e0d982 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: aooohan +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 753a6ac0035802cea3149ae0416be86471eda27e Mon Sep 17 00:00:00 2001 From: lihan Date: Sat, 2 Mar 2024 21:34:46 +0800 Subject: [PATCH 09/24] doc: faq --- docs/.vitepress/en.ts | 1 + docs/.vitepress/zh.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/.vitepress/en.ts b/docs/.vitepress/en.ts index d8e346d2..688f0f11 100644 --- a/docs/.vitepress/en.ts +++ b/docs/.vitepress/en.ts @@ -34,6 +34,7 @@ function nav(): DefaultTheme.NavItem[] { return [ {text: 'Home', link: '/'}, {text: 'Documentation', link: '/guides/intro'}, + {text: 'FAQ', link: '/guides/faq'}, {text: 'Plugins', link: 'https://github.com/version-fox/version-fox-plugins'} ] } diff --git a/docs/.vitepress/zh.ts b/docs/.vitepress/zh.ts index 6697eb57..ff652778 100644 --- a/docs/.vitepress/zh.ts +++ b/docs/.vitepress/zh.ts @@ -34,6 +34,7 @@ function nav(): DefaultTheme.NavItem[] { return [ {text: '首页', link: '/zh-hans/'}, {text: '文档', link: '/zh-hans/guides/intro'}, + {text: '常见问题', link: '/zh-hans/guides/faq'}, {text: '插件仓库', link: 'https://github.com/version-fox/version-fox-plugins'} ] } From 4834c237b11a819445b075f95aa5a65098fb50fe Mon Sep 17 00:00:00 2001 From: Alexis Kalabura <55039048+axdank@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:47:19 -0300 Subject: [PATCH 10/24] use Record.Export instead LoadAllSdk (#67) --- cmd/commands/env.go | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/cmd/commands/env.go b/cmd/commands/env.go index 877ca828..7d474cc4 100644 --- a/cmd/commands/env.go +++ b/cmd/commands/env.go @@ -60,29 +60,22 @@ func envCmd(ctx *cli.Context) error { Paths: []string{}, SDKs: make(SDKs), } - manager := internal.NewSdkManager() + manager := internal.NewSdkManagerWithSource(internal.GlobalRecordSource, internal.SessionRecordSource, internal.ProjectRecordSource) defer manager.Close() - allSdk, loadErr := manager.LoadAllSdk() - if loadErr != nil { - return loadErr - } - for name, s := range allSdk { - current := s.Current() - if current != "" { - envs, envErr := s.EnvKeys(current) - if envErr != nil { - return envErr - } - newEnv := make(map[string]string) - for k, v := range envs { - if k == "PATH" { - data.Paths = append(data.Paths, *v) - } else { - newEnv[k] = *v + for k, v := range manager.Record.Export() { + if lookupSdk, err := manager.LookupSdk(k); err == nil { + if keys, err := lookupSdk.EnvKeys(internal.Version(v)); err == nil { + newEnv := make(map[string]string) + for key, value := range keys { + if key == "PATH" { + data.Paths = append(data.Paths, *value) + } else { + newEnv[key] = *value + } + } + if len(newEnv) > 0 { + data.SDKs[lookupSdk.Plugin.Name] = newEnv } - } - if len(newEnv) > 0 { - data.SDKs[name] = newEnv } } } From 5d254bc36bc263b257ccb4f040829b3bc9203e97 Mon Sep 17 00:00:00 2001 From: Alexis Kalabura <55039048+axdank@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:50:11 -0300 Subject: [PATCH 11/24] bugfix: `Use` command in non-hook environments, do not use defer for save record. (#68) an early return is performed, and the function is not finished executing, therefore the defer is never called. --- internal/sdk.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/sdk.go b/internal/sdk.go index 15a0e62c..d2d36a47 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -263,7 +263,10 @@ func (b *Sdk) Use(version Version, scope UseScope) error { } } b.sdkManager.Record.Add(b.Plugin.Filename, string(version)) - defer b.sdkManager.Record.Save() + err := b.sdkManager.Record.Save() + if err != nil { + return err + } pterm.Printf("Now using %s.\n", pterm.LightGreen(label)) if !env.IsHookEnv() { return shell.GetProcess().Open(os.Getppid()) From 416dcad53c587dfb1635c65641dc536ec309f9b7 Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 15:16:34 +0800 Subject: [PATCH 12/24] bugfix: Duplicate PATH is generated when -g is global on Windows fix #60 --- internal/env/macos_env.go | 1 + internal/env/windows_env.go | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/internal/env/macos_env.go b/internal/env/macos_env.go index 592b04e8..a7f9e7c6 100644 --- a/internal/env/macos_env.go +++ b/internal/env/macos_env.go @@ -42,6 +42,7 @@ func (m *macosEnvManager) Close() error { } func (m *macosEnvManager) Load(key, value string) error { + // TODO check PATH which include many values m.store.Add(&KV{ Key: key, Value: value, diff --git a/internal/env/windows_env.go b/internal/env/windows_env.go index f6ae862d..58124b12 100644 --- a/internal/env/windows_env.go +++ b/internal/env/windows_env.go @@ -54,6 +54,9 @@ func (w *windowsEnvManager) loadPathValue() error { } s := strings.Split(val, ";") for _, path := range s { + if _, ok := w.pathMap[path]; ok { + continue + } w.paths = append(w.paths, path) w.pathMap[path] = struct{}{} } @@ -117,11 +120,15 @@ func (w *windowsEnvManager) Flush() (err error) { func (w *windowsEnvManager) Load(key, value string) error { if key == "PATH" { - _, ok := w.pathMap[value] - if !ok { - w.pathMap[value] = struct{}{} - w.paths = append(w.paths, value) + keys := strings.Split(value, ";") + for _, k := range keys { + _, ok := w.pathMap[k] + if !ok { + w.pathMap[k] = struct{}{} + w.paths = append(w.paths, k) + } } + } else { err := os.Setenv(key, value) if err != nil { @@ -147,11 +154,21 @@ func (w *windowsEnvManager) Remove(key string) error { if key == "PATH" { return fmt.Errorf("can not remove PATH variable") } - if _, ok := w.pathMap[key]; ok { - delete(w.pathMap, key) - w.deletedPathMap[key] = struct{}{} - } else { - _ = w.key.DeleteValue(key) + keys := strings.Split(key, ";") + for _, k := range keys { + if _, ok := w.pathMap[k]; ok { + delete(w.pathMap, k) + var newPaths []string + for _, v := range w.paths { + if v != k { + newPaths = append(newPaths, v) + } + } + w.paths = newPaths + w.deletedPathMap[k] = struct{}{} + } else { + _ = w.key.DeleteValue(k) + } } return nil } From c5c96f4126cf4f1b23b0c59a6fd57435e9c9ef79 Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 16:13:28 +0800 Subject: [PATCH 13/24] bugfix: The PATH environment variable contains multiple duplicate paths. fix #70 --- cmd/commands/activate.go | 11 ++++++++++- cmd/commands/env.go | 9 +++++++++ internal/env/macos_env.go | 2 -- internal/env/windows_env.go | 1 - internal/manager.go | 11 ++--------- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/cmd/commands/activate.go b/cmd/commands/activate.go index e732604d..e49159c9 100644 --- a/cmd/commands/activate.go +++ b/cmd/commands/activate.go @@ -19,6 +19,7 @@ package commands import ( "fmt" "github.com/version-fox/vfox/internal" + "os" "strings" "text/template" @@ -46,7 +47,15 @@ func activateCmd(ctx *cli.Context) error { return err } envKeys[env.HookFlag] = &name - envKeys[env.PathFlag] = envKeys["PATH"] + originPath := os.Getenv("PATH") + envKeys[env.PathFlag] = &originPath + + sdkPaths := envKeys["PATH"] + if sdkPaths != nil { + paths := manager.EnvManager.Paths([]string{*sdkPaths, originPath}) + envKeys["PATH"] = &paths + } + path := manager.PathMeta.ExecutablePath path = strings.Replace(path, "\\", "/", -1) s := shell.NewShell(name) diff --git a/cmd/commands/env.go b/cmd/commands/env.go index 7d474cc4..82b6650c 100644 --- a/cmd/commands/env.go +++ b/cmd/commands/env.go @@ -23,6 +23,7 @@ import ( "github.com/version-fox/vfox/internal" "github.com/version-fox/vfox/internal/env" "github.com/version-fox/vfox/internal/shell" + "os" ) var Env = &cli.Command{ @@ -106,6 +107,14 @@ func envCmd(ctx *cli.Context) error { if err != nil { return err } + + sdkPaths := envKeys["PATH"] + if sdkPaths != nil { + originPath := os.Getenv(env.PathFlag) + paths := manager.EnvManager.Paths([]string{*sdkPaths, originPath}) + envKeys["PATH"] = &paths + } + exportStr := s.Export(envKeys) fmt.Println(exportStr) return nil diff --git a/internal/env/macos_env.go b/internal/env/macos_env.go index a7f9e7c6..08e8f074 100644 --- a/internal/env/macos_env.go +++ b/internal/env/macos_env.go @@ -32,8 +32,6 @@ type macosEnvManager struct { } func (m *macosEnvManager) Paths(paths []string) string { - getenv := os.Getenv("PATH") - paths = append(paths, getenv) return strings.Join(paths, ":") } diff --git a/internal/env/windows_env.go b/internal/env/windows_env.go index 58124b12..63e7e2e4 100644 --- a/internal/env/windows_env.go +++ b/internal/env/windows_env.go @@ -198,7 +198,6 @@ func (w *windowsEnvManager) pathEnvValue() string { } func (w *windowsEnvManager) Paths(paths []string) string { - s := os.Getenv("PATH") paths = append(paths, s) return strings.Join(paths, ";") } diff --git a/internal/manager.go b/internal/manager.go index 31931c0d..7bb75b43 100644 --- a/internal/manager.go +++ b/internal/manager.go @@ -68,17 +68,10 @@ func (m *Manager) EnvKeys() (env.Envs, error) { } } } - if len(paths) == 0 { - var p string - if env.IsHookEnv() { - p = os.Getenv(env.PathFlag) - } else { - p = os.Getenv("PATH") - } - shellEnvs["PATH"] = &p - } else { + if len(paths) != 0 { pathStr := m.EnvManager.Paths(paths[:]) shellEnvs["PATH"] = &pathStr + } else { } return shellEnvs, nil } From 571fa662acf08aaa90a45b2ba62c4b452d1d5b7c Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 16:41:55 +0800 Subject: [PATCH 14/24] bugfix: simplify unix-like env manager to accept PATH value from plugin which include many paths. --- internal/env/env.go | 40 --------- internal/env/macos_env.go | 172 ++++++++++++-------------------------- 2 files changed, 52 insertions(+), 160 deletions(-) diff --git a/internal/env/env.go b/internal/env/env.go index 2ef62a37..ef84cc7c 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -30,43 +30,3 @@ type Manager interface { } type Envs map[string]*string - -type KV struct { - Key string - Value string -} - -type Store struct { - envMap map[string]string - deletedEnvMap map[string]struct{} - // $PATH - pathMap map[string]struct{} - deletedPathMap map[string]struct{} -} - -func (s *Store) Add(kv *KV) { - if kv.Key == "PATH" { - s.pathMap[kv.Value] = struct{}{} - } else { - s.envMap[kv.Key] = kv.Value - } -} - -func (s *Store) Remove(key string) { - if _, ok := s.pathMap[key]; ok { - delete(s.pathMap, key) - s.deletedPathMap[key] = struct{}{} - } else { - delete(s.envMap, key) - s.deletedEnvMap[key] = struct{}{} - } -} - -func NewStore() *Store { - return &Store{ - envMap: make(map[string]string), - pathMap: make(map[string]struct{}), - deletedPathMap: make(map[string]struct{}), - deletedEnvMap: make(map[string]struct{}), - } -} diff --git a/internal/env/macos_env.go b/internal/env/macos_env.go index 08e8f074..dfbff66f 100644 --- a/internal/env/macos_env.go +++ b/internal/env/macos_env.go @@ -19,16 +19,18 @@ package env import ( - "bufio" "fmt" "os" "strings" ) type macosEnvManager struct { - // ~/.version_fox/env.sh - vfEnvPath string - store *Store + envMap map[string]string + deletedEnvMap map[string]struct{} + // $PATH + paths []string + pathMap map[string]struct{} + deletedPathMap map[string]struct{} } func (m *macosEnvManager) Paths(paths []string) string { @@ -40,166 +42,96 @@ func (m *macosEnvManager) Close() error { } func (m *macosEnvManager) Load(key, value string) error { - // TODO check PATH which include many values - m.store.Add(&KV{ - Key: key, - Value: value, - }) + if key == "PATH" { + pathArray := strings.Split(value, ":") + for _, path := range pathArray { + if _, ok := m.pathMap[path]; ok { + continue + } + m.paths = append(m.paths, path) + m.pathMap[path] = struct{}{} + } + } else { + m.envMap[key] = value + } return nil } func (m *macosEnvManager) Remove(key string) error { if key == "PATH" { return fmt.Errorf("can not remove PATH variable") } - m.store.Remove(key) + array := strings.Split(key, ":") + for _, k := range array { + if _, ok := m.pathMap[k]; ok { + delete(m.pathMap, k) + var newPaths []string + for _, v := range m.paths { + if v != k { + newPaths = append(newPaths, v) + } + } + m.paths = newPaths + m.deletedPathMap[k] = struct{}{} + } else { + delete(m.envMap, key) + m.deletedEnvMap[key] = struct{}{} + } + } return nil } func (m *macosEnvManager) Flush() error { - for k, v := range m.store.envMap { + for k, _ := range m.deletedEnvMap { + if err := os.Unsetenv(k); err != nil { + return err + } + } + for k, v := range m.envMap { if err := os.Setenv(k, v); err != nil { return err } } var newPaths []string - for path := range m.store.pathMap { + for path := range m.pathMap { newPaths = append(newPaths, path) } oldPaths := strings.Split(os.Getenv("PATH"), ":") for _, path := range oldPaths { - if strings.Contains(path, ".version-fox") { + if _, ok := m.deletedPathMap[path]; ok { + continue + } + if _, ok := m.pathMap[path]; ok { continue } newPaths = append(newPaths, path) } return os.Setenv("PATH", strings.Join(newPaths, ":")) - //file, err := os.OpenFile(m.vfEnvPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - //if err != nil { - // fmt.Printf("Failed to open the file %s, err:%s\n", m.vfEnvPath, err.Error()) - // return err - //} - //defer file.Close() - //for k, v := range m.store.envMap { - // str := fmt.Sprintf("export %s=%s\n", k, v) - // if _, err := file.WriteString(str); err != nil { - // fmt.Printf("Failed to flush env variable to file,value: err:%s\n", err.Error()) - // return err - // } - //} - // - //pathValue := fmt.Sprintf("export PATH=%s\n", m.pathEnvValue()) - //if _, err := file.WriteString(pathValue); err != nil { - // fmt.Printf("Failed to flush PATH variable to file, err:%s\n", err.Error()) - // return err - //} - //return nil } func (m *macosEnvManager) Get(key string) (string, bool) { if key == "PATH" { return m.pathEnvValue(), true } else { - s, ok := m.store.envMap[key] + s, ok := m.envMap[key] return s, ok } } func (m *macosEnvManager) pathEnvValue() string { var pathValues []string - for k, _ := range m.store.pathMap { + for k, _ := range m.pathMap { pathValues = append(pathValues, k) } pathValues = append(pathValues, "$PATH") return strings.Join(pathValues, ":") } -func (m *macosEnvManager) loadEnvFile() error { - file, err := os.Open(m.vfEnvPath) - if err != nil { - return err - } - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "export") { - line = strings.TrimPrefix(line, "export") - line = strings.TrimSpace(line) - kv := strings.Split(line, "=") - if len(kv) == 2 { - key := kv[0] - value := kv[1] - if key == "PATH" { - pathArray := strings.Split(value, ":") - for _, path := range pathArray { - if path == "$PATH" { - continue - } - m.store.Add(&KV{ - Key: "PATH", - Value: path, - }) - } - } else { - m.store.Add(&KV{ - Key: key, - Value: value, - }) - } - } - } - } - if err := scanner.Err(); err != nil { - return err - } - return nil -} - func NewEnvManager(vfConfigPath string) (Manager, error) { - //envPath := filepath.Join(vfConfigPath, "env.sh") - //if !util.FileExists(envPath) { - // _, _ = os.Create(envPath) - //} manager := &macosEnvManager{ - //vfEnvPath: envPath, - store: NewStore(), + envMap: make(map[string]string), + pathMap: make(map[string]struct{}), + deletedPathMap: make(map[string]struct{}), + deletedEnvMap: make(map[string]struct{}), } - //err := manager.loadEnvFile() - //if err != nil { - // fmt.Printf("Failed to load env file: %s, err:%s\n", manager.vfEnvPath, err.Error()) - //} - //if err := appendEnvSourceIfNotExist(manager.shellInfo.ConfigPath, manager.vfEnvPath); err != nil { - // return nil, err - //} return manager, nil } - -func appendEnvSourceIfNotExist(parentEnvPath, childEnvPath string) error { - shellConfigFile, err := os.Open(parentEnvPath) - if err != nil { - return err - } - defer shellConfigFile.Close() - command := fmt.Sprintf("source %s", childEnvPath) - stat, _ := os.Stat(parentEnvPath) - if stat.Size() > 0 { - scanner := bufio.NewScanner(shellConfigFile) - for scanner.Scan() { - line := scanner.Text() - if strings.Contains(line, command) { - return nil - } - } - if err := scanner.Err(); err != nil { - return err - } - } - - file, err := os.OpenFile(parentEnvPath, os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer file.Close() - _, err = file.WriteString("\n" + command + "\n") - return err -} From fb08ec7a43ffaf1ceeea6ba69354c9781bdbbfc9 Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 17:02:59 +0800 Subject: [PATCH 15/24] chore: remove useless line --- cmd/commands/activate.go | 4 +++- internal/env/windows_env.go | 9 --------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/cmd/commands/activate.go b/cmd/commands/activate.go index e49159c9..51a5bea7 100644 --- a/cmd/commands/activate.go +++ b/cmd/commands/activate.go @@ -18,11 +18,12 @@ package commands import ( "fmt" - "github.com/version-fox/vfox/internal" "os" "strings" "text/template" + "github.com/version-fox/vfox/internal" + "github.com/urfave/cli/v2" "github.com/version-fox/vfox/internal/env" "github.com/version-fox/vfox/internal/shell" @@ -53,6 +54,7 @@ func activateCmd(ctx *cli.Context) error { sdkPaths := envKeys["PATH"] if sdkPaths != nil { paths := manager.EnvManager.Paths([]string{*sdkPaths, originPath}) + envKeys["PATH"] = &paths } diff --git a/internal/env/windows_env.go b/internal/env/windows_env.go index 63e7e2e4..f9a8cf6c 100644 --- a/internal/env/windows_env.go +++ b/internal/env/windows_env.go @@ -189,16 +189,7 @@ func (w *windowsEnvManager) broadcastEnvironment() error { return nil } -func (w *windowsEnvManager) pathEnvValue() string { - var paths []string - for path := range w.pathMap { - paths = append(paths, path) - } - return w.Paths(paths) -} - func (w *windowsEnvManager) Paths(paths []string) string { - paths = append(paths, s) return strings.Join(paths, ";") } From b828dcf04ba04056d78522b1aafd6226f463a8f1 Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 17:16:49 +0800 Subject: [PATCH 16/24] doc: PATH duplicate problem --- docs/guides/faq.md | 15 ++++++++++++++- docs/zh-hans/guides/faq.md | 15 ++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/guides/faq.md b/docs/guides/faq.md index 4e2af0e2..59ab97b5 100644 --- a/docs/guides/faq.md +++ b/docs/guides/faq.md @@ -31,4 +31,17 @@ So, when deleting the plugin, you need to use the **SDK name** (here is `nodejs` $ vfox remove nodejs ``` -before deleting, you can use `vfox ls` to view the currently installed plugins (i.e. SDK names), and then delete them. \ No newline at end of file +before deleting, you can use `vfox ls` to view the currently installed plugins (i.e. SDK names), and then delete them. + +## Why does the PATH environment variable value repeat on Windows? + +Only one situation will cause this, that is, you have used the SDK globally (`vfox use -g`), at this time, `vfox` will +operate the registry and write the SDK's `PATH` into the user environment variable (for the purpose of, **Shell that does not +support Hook function** can also use SDK, such as `CMD`). + +But because of the existence of the `.tool-versions` mechanism, the `PATH` becomes the sum of `.tool-verions` and the user +environment variable `PATH`. + +::: warning +The same SDK **can be repeated at most twice**, it will not be repeated indefinitely. If >2 times, please feedback to us. +::: diff --git a/docs/zh-hans/guides/faq.md b/docs/zh-hans/guides/faq.md index 144b4185..f39a06a0 100644 --- a/docs/zh-hans/guides/faq.md +++ b/docs/zh-hans/guides/faq.md @@ -1,6 +1,5 @@ # 常见问题 - ## 切换xxx不生效? `vfox use`命令不生效? 如果你看到提示`Warning: The current shell lacks hook support or configuration. It has switched to global scope automatically` @@ -8,7 +7,6 @@ 请按照[快速入门#_2-挂载vfox到你的shell](./quick-start.md#_2-挂载vfox到你的shell)步骤进行手动挂载。 - ## 为什么我删除不了插件? ```text @@ -27,4 +25,15 @@ $ vfox remove nodejs ``` -在删除之前, 你可以使用`vfox ls`查看当前已安装的插件(即SDK名称), 然后再进行删除。 \ No newline at end of file +在删除之前, 你可以使用`vfox ls`查看当前已安装的插件(即SDK名称), 然后再进行删除。 + +## Windows下PATH环境变量值重复? + +只有一种情况下会出现这种情况, 就是你全局(`vfox use -g`)使用过SDK, 这个时候`vfox`会操作注册表,将SDK的`PATH`写入用户环境变量当中(为的是, +**不支持Hook功能**的Shell也能使用SDK, 例如`CMD`)。 + +但是因为`.tool-versions`机制的存在, 所以`PATH`就变成了`.tool-verions` + 用户环境变量`PATH`两部分组成。 + +::: warning +同一个SDK**最多重复两条**, 不会无限重复。如果>2次, 请反馈给我们。 +::: \ No newline at end of file From f344606e563d604b95f6918cb676d29b66cb5613 Mon Sep 17 00:00:00 2001 From: lihan Date: Mon, 4 Mar 2024 17:19:40 +0800 Subject: [PATCH 17/24] =?UTF-8?q?release=20v0.2.4=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/version.go b/internal/version.go index 5ad14d64..aaa4bae9 100644 --- a/internal/version.go +++ b/internal/version.go @@ -16,4 +16,4 @@ package internal -const RuntimeVersion = "0.2.3" +const RuntimeVersion = "0.2.4" From 1da7b83eb31a5254ff0aaeabb9719328c178880c Mon Sep 17 00:00:00 2001 From: xxnuo <54252779+xxnuo@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:38:41 +0800 Subject: [PATCH 18/24] doc: Improve how to Hook for Powershell(#71) * Improve README for Powershell Since many beginners are using Powershell on the Windows system for development, it would be beneficial to enhance the usage instructions for beginners in Powershell. It's possible to elegantly accomplish tasks with a single command by modifying the source code or using scripts, but this could introduce new complexity. * powershell docs --- README.md | 8 +++++++- README_CN.md | 10 ++++++++-- docs/guides/quick-start.md | 10 +++++++++- docs/zh-hans/guides/quick-start.md | 11 ++++++++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7b3fb687..73653110 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,16 @@ echo 'eval "$(vfox activate bash)"' >> ~/.bashrc echo 'eval "$(vfox activate zsh)"' >> ~/.zshrc echo 'vfox activate fish | source' >> ~/.config/fish/config.fish -# For PowerShell, add the following line to your $PROFILE: +# For PowerShell: +# 1. Open PowerShell Profile: +New-Item -Type File -Path $PROFILE # Just ignore the 'file already exists' error. +Invoke-Item $PROFILE +# 2. Add the following line to the end of your $PROFILE and save: Invoke-Expression "$(vfox activate pwsh)" ``` +> Remember to restart your shell to apply the changes. + #### 3. Add an SDK plugin ```bash $ vfox add nodejs/nodejs diff --git a/README_CN.md b/README_CN.md index aa5ce72f..32a1b988 100644 --- a/README_CN.md +++ b/README_CN.md @@ -35,17 +35,23 @@ #### 1.选择一个适合你的[安装方式](https://vfox.lhan.me/zh-hans/guides/quick-start.html#_1-%E5%AE%89%E8%A3%85vfox)。 -#### 2. ⚠️ **挂载vfox到你的Shell (从下面选择一条适合你shell的命令)** ⚠️ +#### 2. ⚠️ **挂载vfox到你的 Shell (从下面选择一条适合你 shell 的命令)** ⚠️ ```bash echo 'eval "$(vfox activate bash)"' >> ~/.bashrc echo 'eval "$(vfox activate zsh)"' >> ~/.zshrc echo 'vfox activate fish | source' >> ~/.config/fish/config.fish -# PowerShell, 请将下面一行添加到你的$PROFILE文件中: +# PowerShell: +# 1. 打开 PowerShell 配置文件: +New-Item -Type File -Path $PROFILE # 无需在意 `文件已存在` 错误 +Invoke-Item $PROFILE +# 2. 将下面一行添加到你的 $PROFILE 文件末尾并保存: Invoke-Expression "$(vfox activate pwsh)" ``` +> 请记住重启你的 Shell 以应用更改。 + #### 3.添加插件 ```bash $ vfox add nodejs/nodejs diff --git a/docs/guides/quick-start.md b/docs/guides/quick-start.md index 0e64dd5a..869e150a 100644 --- a/docs/guides/quick-start.md +++ b/docs/guides/quick-start.md @@ -93,7 +93,15 @@ echo 'vfox activate fish | source' >> ~/.config/fish/config.fish ::: ::: details Powershell -add the following line to your `$PROFILE`: + +Open PowerShell Profile: + +```shell +New-Item -Type File -Path $PROFILE # Just ignore the 'file already exists' error. +Invoke-Item $PROFILE +``` + +Add the following line to the end of your profile and save: ```shell Invoke-Expression "$(vfox activate pwsh)" diff --git a/docs/zh-hans/guides/quick-start.md b/docs/zh-hans/guides/quick-start.md index 84b0fe8c..5f805de4 100644 --- a/docs/zh-hans/guides/quick-start.md +++ b/docs/zh-hans/guides/quick-start.md @@ -76,10 +76,19 @@ echo 'vfox activate fish | source' >> ~/.config/fish/config.fish ::: details Powershell -请将下面一行添加到你的`$PROFILE`文件中: +打开 PowerShell 配置文件: + +```shell +New-Item -Type File -Path $PROFILE # 无需在意 `文件已存在` 错误 +Invoke-Item $PROFILE +``` + +将下面一行添加到你的 $PROFILE 文件末尾并保存: + ```shell Invoke-Expression "$(vfox activate pwsh)" ``` + ::: ## 3. 添加插件 From 4f368aaa023bbd265f06738374c63b7618e5151b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:56:00 +0800 Subject: [PATCH 19/24] chore(deps): bump golang.org/x/net from 0.7.0 to 0.17.0 (#77) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 5e15ec2a..9220b21e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/ulikunitz/xz v0.5.11 github.com/urfave/cli/v2 v2.26.0 github.com/yuin/gopher-lua v1.1.1 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/crypto v0.14.0 golang.org/x/sys v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -29,7 +29,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/term v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 326da488..9407a48c 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,9 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -101,8 +102,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 2af1303e1b69e955ec0aa5ece3db5e4c9af14a13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:56:50 +0800 Subject: [PATCH 20/24] chore(deps): bump golang.org/x/crypto (#76) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20210921155107-089bfa567519 to 0.17.0. - [Commits](https://github.com/golang/crypto/commits/v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 9220b21e..07809f00 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/ulikunitz/xz v0.5.11 github.com/urfave/cli/v2 v2.26.0 github.com/yuin/gopher-lua v1.1.1 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/sys v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +30,6 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/term v0.14.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 9407a48c..6203f88b 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -128,16 +128,17 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From c816d36a27395deed1a8eded6a1f983c2894aaa9 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 5 Mar 2024 12:58:05 +0800 Subject: [PATCH 21/24] bugfix: Fixed the bug of selecting version for searching (#75) --- internal/printer/select.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/printer/select.go b/internal/printer/select.go index f02c9d88..cb765fcb 100644 --- a/internal/printer/select.go +++ b/internal/printer/select.go @@ -178,7 +178,7 @@ func (s *PageKVSelect) Show() (*KV, error) { area.Update(s.renderSelect()) } case keys.Enter: - s.result = s.options[s.index] + s.result = s.fuzzySearchMatches[s.index] return true, nil default: return false, nil From c432e3a181fdbd7a890d343859a7c14b7c35cd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=8E=E5=A3=B0?= Date: Tue, 5 Mar 2024 16:34:23 +0800 Subject: [PATCH 22/24] feat: support PreUse hook (#58) * feat: support PreUse hooks * test: add plugin test case * chore: update code style * feat: add preload lua script * fix: move select printer into command * feat: plugin ctx add previousVersion * fix: preuse should not change the version * chore: revert some change * docs: update documents * chore: remove sdk name * fix: early return in pre use hook * fix: scoop stringify --- cmd/commands/use.go | 5 +- docs/plugins/create/howto.md | 38 +++++- docs/zh-hans/plugins/create/howto.md | 36 +++++- internal/fixtures/preload.lua | 14 +++ internal/manager.go | 14 +-- internal/path.go | 33 ++++-- internal/plugin.go | 110 ++++++++++++------ internal/plugin_test.go | 74 ++++++++++++ internal/scope.go | 13 +++ internal/sdk.go | 36 ++++++ internal/testdata/plugins/java.lua | 165 +++++++++++++++++++++++++++ template.lua | 36 +++++- 12 files changed, 513 insertions(+), 61 deletions(-) create mode 100644 internal/fixtures/preload.lua create mode 100644 internal/plugin_test.go create mode 100644 internal/testdata/plugins/java.lua diff --git a/cmd/commands/use.go b/cmd/commands/use.go index a0052edf..fe4ebc41 100644 --- a/cmd/commands/use.go +++ b/cmd/commands/use.go @@ -18,11 +18,12 @@ package commands import ( "fmt" - "github.com/version-fox/vfox/internal" "os" "strings" "github.com/pterm/pterm" + "github.com/version-fox/vfox/internal" + "github.com/urfave/cli/v2" ) @@ -87,6 +88,7 @@ func useCmd(ctx *cli.Context) error { if err != nil { return fmt.Errorf("%s not supported, error: %w", name, err) } + if version == "" { list := source.List() var arr []string @@ -109,5 +111,6 @@ func useCmd(ctx *cli.Context) error { result, _ := selectPrinter.Show(fmt.Sprintf("Please select a version of %s", name)) version = internal.Version(result) } + return source.Use(version, scope) } diff --git a/docs/plugins/create/howto.md b/docs/plugins/create/howto.md index c501425e..0db0d80c 100644 --- a/docs/plugins/create/howto.md +++ b/docs/plugins/create/howto.md @@ -133,11 +133,45 @@ function PLUGIN:EnvKeys(ctx) end ``` +## PreUse + +When the user uses `vfox use`, the plugin's `PreUse` function is called. The purpose of this function is to return the +version information entered by the user. If the `PreUse` function returns version information, `vfox` will use this new +version. + +```lua +function PLUGIN:PreUse(ctx) + local runtimeVersion = ctx.runtimeVersion + --- user input version + local version = ctx.version + --- user current used version + local previousVersion = ctx.previousVersion + + --- installed sdks + local sdkInfo = ctx.installedSdks['version'] + local path = sdkInfo.path + local name = sdkInfo.name + local version = sdkInfo.version + + --- working directory + local cwd = ctx.cwd + + --- user input scope + --- could be one of global/project/session + local scope = ctx.scope + + --- return the version information + return { + version = version, + } +end +``` + ## Test Plugin Currently, VersionFox plugin testing is straightforward. You only need to place the plugin file in the `${HOME}/.version-fox/plugins` directory and verify that your features are working using different commands. You can use -`print` statements in Lua scripts for printing log. +`print`/`printTable` statements in Lua scripts for printing log. - PLUGIN:PreInstall -> `vfox install @` - PLUGIN:PostInstall -> `vfox install @` @@ -147,4 +181,4 @@ Currently, VersionFox plugin testing is straightforward. You only need to place ## Publish Plugin When you have completed the plugin and tested it without any problems, you can -directly [create a PR](https://github.com/version-fox/version-fox-plugins/pulls). \ No newline at end of file +directly [create a PR](https://github.com/version-fox/version-fox-plugins/pulls). diff --git a/docs/zh-hans/plugins/create/howto.md b/docs/zh-hans/plugins/create/howto.md index 7c9ed3ad..6e982ae8 100644 --- a/docs/zh-hans/plugins/create/howto.md +++ b/docs/zh-hans/plugins/create/howto.md @@ -127,9 +127,41 @@ function PLUGIN:EnvKeys(ctx) end ``` +## PreUse + +当用户使用 `vfox use` 的时候,会调用插件的 `PreUse` 函数。这个函数的作用是返回用户输入的版本信息。 +如果 `PreUse` 函数返回了版本信息, `vfox` 将会使用这个新的版本信息。 + +```lua +function PLUGIN:PreUse(ctx) + local runtimeVersion = ctx.runtimeVersion + --- 用户输入的版本 + local version = ctx.version + --- 用户之前环境中设置的版本 + local previousVersion = ctx.previousVersion + + --- 已安装的 SDK 信息 + local sdkInfo = ctx.installedSdks['version'] + local path = sdkInfo.path + local name = sdkInfo.name + local version = sdkInfo.version + + --- 当前工作目录 + local cwd = ctx.cwd + + --- 用户输入的 scope 信息,值为 global/project/session 其中之一 + local scope = ctx.scope + + --- 返回版本信息 + return { + version = version, + } +end +``` + ## 测试插件 -目前,`vfox` 插件测试方法很简陋。您需要将插件放在 `${HOME}/.version-fox/plugins` 目录中,并使用不同的命令验证您的功能是否正常工作。您可以在c插件中使用`print`函数来打印日志进行调试。 +目前,`vfox` 插件测试方法很简陋。您需要将插件放在 `${HOME}/.version-fox/plugins` 目录中,并使用不同的命令验证您的功能是否正常工作。您可以在c插件中使用 `print`/`printTable` 函数来打印日志进行调试。 - PLUGIN:PreInstall -> `vfox install @` - PLUGIN:PostInstall -> `vfox install @` @@ -140,4 +172,4 @@ end ## 发布插件 -当你完成插件并测试无误之后, 就可以直接[发起PR](https://github.com/version-fox/version-fox-plugins/pulls)啦~ \ No newline at end of file +当你完成插件并测试无误之后, 就可以直接[发起PR](https://github.com/version-fox/version-fox-plugins/pulls)啦~ diff --git a/internal/fixtures/preload.lua b/internal/fixtures/preload.lua new file mode 100644 index 00000000..ca75445c --- /dev/null +++ b/internal/fixtures/preload.lua @@ -0,0 +1,14 @@ +function printTable(t, indent) + indent = indent or 0 + local strIndent = string.rep(" ", indent) + for key, value in pairs(t) do + local keyStr = tostring(key) + local valueStr = tostring(value) + if type(value) == "table" then + print(strIndent .. "[" .. keyStr .. "] =>") + printTable(value, indent + 1) + else + print(strIndent .. "[" .. keyStr .. "] => " .. valueStr) + end + end +end diff --git a/internal/manager.go b/internal/manager.go index 7bb75b43..63f35e26 100644 --- a/internal/manager.go +++ b/internal/manager.go @@ -19,7 +19,6 @@ package internal import ( "encoding/json" "fmt" - "github.com/version-fox/vfox/internal/config" "io" "net/http" "net/url" @@ -29,6 +28,7 @@ import ( "strings" "github.com/pterm/pterm" + "github.com/version-fox/vfox/internal/config" "github.com/version-fox/vfox/internal/env" "github.com/version-fox/vfox/internal/util" ) @@ -105,9 +105,10 @@ func (m *Manager) LoadAllSdk() (map[string]*Sdk, error) { if d.IsDir() { continue } - if strings.HasSuffix(d.Name(), ".lua") { + sdkName := d.Name() + if strings.HasSuffix(sdkName, ".lua") { // filename first as sdk name - path := filepath.Join(m.PathMeta.PluginPath, d.Name()) + path := filepath.Join(m.PathMeta.PluginPath, sdkName) content, _ := m.loadLuaFromFileOrUrl(path) source, err := NewLuaPlugin(content, path, m) if err != nil { @@ -376,17 +377,14 @@ func newSdkManagerWithSource(sources ...RecordSource) *Manager { if err != nil { panic("Init path meta error") } + var paths []string for _, source := range sources { switch source { case GlobalRecordSource: paths = append(paths, meta.ConfigPath) case ProjectRecordSource: - curDir, err := os.Getwd() - if err != nil { - panic("Get current dir error") - } - paths = append(paths, curDir) + paths = append(paths, meta.WorkingDirectory) case SessionRecordSource: paths = append(paths, meta.CurTmpPath) } diff --git a/internal/path.go b/internal/path.go index ad937e23..66973608 100644 --- a/internal/path.go +++ b/internal/path.go @@ -18,9 +18,10 @@ package internal import ( "fmt" - "github.com/version-fox/vfox/internal/util" "os" "path/filepath" + + "github.com/version-fox/vfox/internal/util" ) type RecordSource string @@ -34,11 +35,12 @@ const ( type PathMeta struct { TempPath string // Temporary directory for the current process - CurTmpPath string - ConfigPath string - SdkCachePath string - PluginPath string - ExecutablePath string + CurTmpPath string + ConfigPath string + SdkCachePath string + PluginPath string + ExecutablePath string + WorkingDirectory string } func newPathMeta() (*PathMeta, error) { @@ -67,12 +69,19 @@ func newPathMeta() (*PathMeta, error) { return nil, fmt.Errorf("create temp dir failed: %w", err) } } + + workingDirectory, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("get current working directory failed: %w", err) + } + return &PathMeta{ - TempPath: tmpPath, - CurTmpPath: curTmpPath, - ConfigPath: configPath, - SdkCachePath: sdkCachePath, - PluginPath: pluginPath, - ExecutablePath: exePath, + TempPath: tmpPath, + CurTmpPath: curTmpPath, + ConfigPath: configPath, + SdkCachePath: sdkCachePath, + PluginPath: pluginPath, + ExecutablePath: exePath, + WorkingDirectory: workingDirectory, }, nil } diff --git a/internal/plugin.go b/internal/plugin.go index a4284210..4a5fc0c4 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -17,15 +17,20 @@ package internal import ( + _ "embed" "fmt" - "github.com/version-fox/vfox/internal/env" - "github.com/version-fox/vfox/internal/module" - lua "github.com/yuin/gopher-lua" "path/filepath" "regexp" "strings" + + "github.com/version-fox/vfox/internal/env" + "github.com/version-fox/vfox/internal/module" + lua "github.com/yuin/gopher-lua" ) +//go:embed fixtures/preload.lua +var preloadScript string + const ( LuaPluginObjKey = "PLUGIN" OsType = "OS_TYPE" @@ -81,8 +86,7 @@ func (l *LuaPlugin) Available() ([]*Package, error) { return nil, err } - table := L.ToTable(-1) // returned value - L.Pop(1) // remove received value + table := l.returnedValue() if table == nil || table.Type() == lua.LTNil { return []*Package{}, nil @@ -172,8 +176,7 @@ func (l *LuaPlugin) PreInstall(version Version) (*Package, error) { return nil, err } - table := L.ToTable(-1) // returned value - L.Pop(1) // remove received value + table := l.returnedValue() if table == nil || table.Type() == lua.LTNil { return nil, nil } @@ -249,11 +252,7 @@ func (l *LuaPlugin) PostInstall(rootPath string, sdks []*Info) error { L := l.state sdkArr := L.NewTable() for _, v := range sdks { - sdkTable := L.NewTable() - L.SetField(sdkTable, "name", lua.LString(v.Name)) - L.SetField(sdkTable, "version", lua.LString(v.Version)) - L.SetField(sdkTable, "path", lua.LString(v.Path)) - L.SetField(sdkTable, "note", lua.LString(v.Note)) + sdkTable := l.createSdkInfoTable(v) L.SetField(sdkArr, v.Name, sdkTable) } ctxTable := L.NewTable() @@ -281,10 +280,7 @@ func (l *LuaPlugin) EnvKeys(sdkPackage *Package) (env.Envs, error) { mainInfo := sdkPackage.Main sdkArr := L.NewTable() for _, v := range sdkPackage.Additions { - sdkTable := L.NewTable() - L.SetField(sdkTable, "name", lua.LString(v.Name)) - L.SetField(sdkTable, "version", lua.LString(v.Version)) - L.SetField(sdkTable, "path", lua.LString(v.Path)) + sdkTable := l.createSdkInfoTable(v) L.SetField(sdkArr, v.Name, sdkTable) } ctxTable := L.NewTable() @@ -300,8 +296,7 @@ func (l *LuaPlugin) EnvKeys(sdkPackage *Package) (env.Envs, error) { return nil, err } - table := L.ToTable(-1) // returned value - L.Pop(1) // remove received value + table := l.returnedValue() if table == nil || table.Type() == lua.LTNil || table.Len() == 0 { return nil, fmt.Errorf("no environment variables provided") } @@ -333,29 +328,80 @@ func (l *LuaPlugin) getTableField(table *lua.LTable, fieldName string) (lua.LVal return value, nil } -func (l *LuaPlugin) luaPrint() int { - L := l.state - L.SetGlobal("print", L.NewFunction(func(L *lua.LState) int { - top := L.GetTop() - for i := 1; i <= top; i++ { - fmt.Print(L.ToStringMeta(L.Get(i))) - if i != top { - fmt.Print("\t") - } - } - fmt.Println() - return 0 - })) - return 0 +func (l *LuaPlugin) returnedValue() *lua.LTable { + table := l.state.ToTable(-1) // returned value + l.state.Pop(1) // remove received value + return table } func (l *LuaPlugin) Label(version string) string { return fmt.Sprintf("%s@%s", l.Name, version) } +func (l *LuaPlugin) createSdkInfoTable(info *Info) *lua.LTable { + L := l.state + sdkTable := L.NewTable() + L.SetField(sdkTable, "name", lua.LString(info.Name)) + L.SetField(sdkTable, "version", lua.LString(info.Version)) + L.SetField(sdkTable, "path", lua.LString(info.Path)) + L.SetField(sdkTable, "note", lua.LString(info.Note)) + return sdkTable +} + +func (l *LuaPlugin) HasFunction(name string) bool { + return l.pluginObj.RawGetString(name) != lua.LNil +} + +func (l *LuaPlugin) PreUse(version Version, previousVersion Version, scope UseScope, cwd string, installedSdks []*Package) (Version, error) { + L := l.state + lInstalledSdks := L.NewTable() + for _, v := range installedSdks { + sdkTable := l.createSdkInfoTable(v.Main) + L.SetField(lInstalledSdks, string(v.Main.Version), sdkTable) + } + ctxTable := L.NewTable() + + L.SetField(ctxTable, "installedSdks", lInstalledSdks) + L.SetField(ctxTable, "runtimeVersion", lua.LString(RuntimeVersion)) + L.SetField(ctxTable, "cwd", lua.LString(cwd)) + L.SetField(ctxTable, "scope", lua.LString(scope.String())) + L.SetField(ctxTable, "version", lua.LString(version)) + L.SetField(ctxTable, "previousVersion", lua.LString(previousVersion)) + + function := l.pluginObj.RawGetString("PreUse") + if function.Type() == lua.LTNil { + return "", nil + } + if err := L.CallByParam(lua.P{ + Fn: function.(*lua.LFunction), + NRet: 1, + Protect: true, + }, l.pluginObj, ctxTable); err != nil { + return "", err + } + + table := l.returnedValue() + if table == nil || table.Type() == lua.LTNil { + return "", nil + } + + luaVer, err := l.getTableField(table, "version") + if err != nil { + // ignore version field not found + return "", nil + } + + return Version(luaVer.String()), nil +} + func NewLuaPlugin(content, path string, manager *Manager) (*LuaPlugin, error) { luaVMInstance := lua.NewState() module.Preload(luaVMInstance, manager.Config) + + if err := luaVMInstance.DoString(preloadScript); err != nil { + return nil, err + } + if err := luaVMInstance.DoString(content); err != nil { return nil, err } diff --git a/internal/plugin_test.go b/internal/plugin_test.go new file mode 100644 index 00000000..79aca6c7 --- /dev/null +++ b/internal/plugin_test.go @@ -0,0 +1,74 @@ +package internal + +import ( + "testing" + + _ "embed" +) + +//go:embed testdata/plugins/java.lua +var pluginContent string +var pluginPath = "testdata/plugins/java.lua" + +func TestPlugin(t *testing.T) { + t.Run("Available", func(t *testing.T) { + manager := NewSdkManager() + plugin, err := NewLuaPlugin(pluginContent, pluginPath, manager) + if err != nil { + t.Fatal(err) + } + + pkgs, err := plugin.Available() + if err != nil { + t.Fatal(err) + } + + if len(pkgs) != 1 { + t.Errorf("expected 1 package, got %d", len(pkgs)) + } + }) + + t.Run("PreUse", func(t *testing.T) { + manager := NewSdkManager() + + plugin, err := NewLuaPlugin(pluginContent, pluginPath, manager) + + inputVersion := Version("20.0") + previousVersion := Version("21.0") + cwd := "/home/user" + + if err != nil { + t.Fatal(err) + } + pkgs, err := plugin.Available() + if err != nil { + t.Fatal(err) + } + version, err := plugin.PreUse(inputVersion, previousVersion, Global, cwd, pkgs) + if err != nil { + t.Fatal(err) + } + + if version != "9.9.9" { + t.Errorf("expected version '9.9.9', got '%s'", version) + } + + version, err = plugin.PreUse(inputVersion, previousVersion, Project, cwd, pkgs) + if err != nil { + t.Fatal(err) + } + + if version != "10.0.0" { + t.Errorf("expected version '10.0.0', got '%s'", version) + } + + version, err = plugin.PreUse(inputVersion, previousVersion, Session, cwd, pkgs) + if err != nil { + t.Fatal(err) + } + + if version != "1.0.0" { + t.Errorf("expected version '1.0.0', got '%s'", version) + } + }) +} diff --git a/internal/scope.go b/internal/scope.go index ec7d10d6..f2a2cddb 100644 --- a/internal/scope.go +++ b/internal/scope.go @@ -23,3 +23,16 @@ const ( Project Session ) + +func (s UseScope) String() string { + switch s { + case Global: + return "global" + case Project: + return "project" + case Session: + return "session" + default: + return "unknown" + } +} diff --git a/internal/sdk.go b/internal/sdk.go index d2d36a47..1fc33ea7 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -228,12 +228,36 @@ func (b *Sdk) EnvKeys(version Version) (env.Envs, error) { return keys, nil } +func (b *Sdk) PreUse(version Version, scope UseScope) (Version, error) { + if !b.Plugin.HasFunction("PreUse") { + return version, nil + } + + newVersion, err := b.Plugin.PreUse(version, b.Current(), scope, b.sdkManager.PathMeta.WorkingDirectory, b.getLocalSdkPackages()) + if err != nil { + return "", fmt.Errorf("plugin [PreUse] error: err:%w", err) + } + + // If the plugin does not return a version, it means that the plugin does not want to change the version. + if newVersion == "" { + return version, nil + } + + return newVersion, nil +} + func (b *Sdk) Use(version Version, scope UseScope) error { // FIXME The default is Session under unix-like, and the default is Global under windows. if !env.IsHookEnv() { pterm.Printf("Warning: The current shell lacks hook support or configuration. It has switched to global scope automatically.\n") scope = Global } + + version, err := b.PreUse(version, scope) + if err != nil { + return err + } + label := b.label(version) if !b.checkExists(version) { return fmt.Errorf("%s is not installed", label) @@ -294,6 +318,18 @@ func (b *Sdk) List() []Version { return versions } +func (b *Sdk) getLocalSdkPackages() []*Package { + var infos []*Package + for _, version := range b.List() { + info, err := b.getLocalSdkPackage(version) + if err != nil { + continue + } + infos = append(infos, info) + } + return infos +} + func (b *Sdk) Current() Version { version := b.sdkManager.Record.Export()[b.Plugin.Filename] return Version(version) diff --git a/internal/testdata/plugins/java.lua b/internal/testdata/plugins/java.lua new file mode 100644 index 00000000..78dd6e30 --- /dev/null +++ b/internal/testdata/plugins/java.lua @@ -0,0 +1,165 @@ +--- Common libraries provided by VersionFox (optional) +local http = require("http") +local json = require("json") +local html = require("html") + +--- The following two parameters are injected by VersionFox at runtime +--- Operating system type at runtime (Windows, Linux, Darwin) +OS_TYPE = "" +--- Operating system architecture at runtime (amd64, arm64, etc.) +ARCH_TYPE = "" + +PLUGIN = { + --- Plugin name + name = "java", + --- Plugin author + author = "Lihan", + --- Plugin version + version = "0.0.1", + --- Plugin description + description = "xxx", + -- Update URL + updateUrl = "{URL}/sdk.lua", + -- minimum compatible vfox version + minRuntimeVersion = "0.2.2", +} + +--- Returns some pre-installed information, such as version number, download address, local files, etc. +--- If checksum is provided, vfox will automatically check it for you. +--- @param ctx table +--- @field ctx.version string User-input version +--- @return table Version information +function PLUGIN:PreInstall(ctx) + local version = ctx.version + local runtimeVersion = ctx.runtimeVersion + return { + --- Version number + version = "xxx", + --- remote URL or local file path [optional] + url = "xxx", + --- SHA256 checksum [optional] + sha256 = "xxx", + --- md5 checksum [optional] + md5 = "xxx", + --- sha1 checksum [optional] + sha1 = "xxx", + --- sha512 checksum [optional] + sha512 = "xx", + --- additional need files [optional] + addition = { + { + --- additional file name ! + name = "xxx", + --- remote URL or local file path [optional] + url = "xxx", + --- SHA256 checksum [optional] + sha256 = "xxx", + --- md5 checksum [optional] + md5 = "xxx", + --- sha1 checksum [optional] + sha1 = "xxx", + --- sha512 checksum [optional] + sha512 = "xx", + } + } + } +end + +--- Extension point, called after PreInstall, can perform additional operations, +--- such as file operations for the SDK installation directory or compile source code +--- Currently can be left unimplemented! +function PLUGIN:PostInstall(ctx) + --- ctx.rootPath SDK installation directory + local rootPath = ctx.rootPath + local runtimeVersion = ctx.runtimeVersion + local sdkInfo = ctx.sdkInfo['sdk-name'] + local path = sdkInfo.path + local version = sdkInfo.version + local name = sdkInfo.name +end + +--- Return all available versions provided by this plugin +--- @param ctx table Empty table used as context, for future extension +--- @return table Descriptions of available versions and accompanying tool descriptions +function PLUGIN:Available(ctx) + local runtimeVersion = ctx.runtimeVersion + return { + { + version = "xxxx", + note = "LTS", + addition = { + { + name = "npm", + version = "8.8.8", + } + } + } + } +end + +--- Each SDK may have different environment variable configurations. +--- This allows plugins to define custom environment variables (including PATH settings) +--- Note: Be sure to distinguish between environment variable settings for different platforms! +--- @param ctx table Context information +--- @field ctx.path string SDK installation directory +function PLUGIN:EnvKeys(ctx) + --- this variable is same as ctx.sdkInfo['plugin-name'].path + local mainPath = ctx.path + local runtimeVersion = ctx.runtimeVersion + local sdkInfo = ctx.sdkInfo['sdk-name'] + local path = sdkInfo.path + local version = sdkInfo.version + local name = sdkInfo.name + return { + { + key = "JAVA_HOME", + value = mainPath + }, + { + key = "PATH", + value = mainPath .. "/bin" + } + } +end + +--- When user invoke `use` command, this function will be called to get the +--- valid version information. +--- @param ctx table Context information +function PLUGIN:PreUse(ctx) + local runtimeVersion = ctx.runtimeVersion + --- user input version + local version = ctx.version + --- installed sdks + local sdkInfo = ctx.installedSdks['xxxx'] + local path = sdkInfo.path + local name = sdkInfo.name + local sdkVersion = sdkInfo.version + + --- working directory + local cwd = ctx.cwd + + printTable(ctx) + + --- user input scope + local scope = ctx.scope + + if (scope == "global") then + print("return 9.9.9") + return { + version = "9.9.9", + } + end + + if (scope == "project") then + print("return 10.0.0") + return { + version = "10.0.0", + } + end + + print("return 1.0.0") + + return { + version = "1.0.0" + } +end diff --git a/template.lua b/template.lua index d94aa981..5e8dde8b 100644 --- a/template.lua +++ b/template.lua @@ -1,4 +1,3 @@ - --- Common libraries provided by VersionFox (optional) local http = require("http") local json = require("json") @@ -41,7 +40,7 @@ function PLUGIN:PreInstall(ctx) --- SHA256 checksum [optional] sha256 = "xxx", --- md5 checksum [optional] - md5= "xxx", + md5 = "xxx", --- sha1 checksum [optional] sha1 = "xxx", --- sha512 checksum [optional] @@ -56,7 +55,7 @@ function PLUGIN:PreInstall(ctx) --- SHA256 checksum [optional] sha256 = "xxx", --- md5 checksum [optional] - md5= "xxx", + md5 = "xxx", --- sha1 checksum [optional] sha1 = "xxx", --- sha512 checksum [optional] @@ -121,4 +120,33 @@ function PLUGIN:EnvKeys(ctx) value = mainPath .. "/bin" } } -end \ No newline at end of file +end + +--- When user invoke `use` command, this function will be called to get the +--- valid version information. +--- @param ctx table Context information +function PLUGIN:PreUse(ctx) + local runtimeVersion = ctx.runtimeVersion + --- user input version + local version = ctx.version + --- user current used version + local previousVersion = ctx.previousVersion + + --- installed sdks + local sdkInfo = ctx.installedSdks['version'] + local path = sdkInfo.path + local name = sdkInfo.name + local version = sdkInfo.version + + --- working directory + local cwd = ctx.cwd + + --- user input scope + --- could be one of global/project/session + local scope = ctx.scope + + --- return the version information + return { + version = version, + } +end From f1c3d5b036c97224c172a64b85a9106cbd8f2bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=8E=E5=A3=B0?= Date: Tue, 5 Mar 2024 17:08:11 +0800 Subject: [PATCH 23/24] chore: add .tool-versions (#80) --- .tool-versions | 1 + go.mod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..1e576d1c --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.21.7 diff --git a/go.mod b/go.mod index 07809f00..8cc70032 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/version-fox/vfox -go 1.21.5 +go 1.21.7 require ( atomicgo.dev/cursor v0.2.0 From eb2bcd514a1bfe6a37873f78e5a806e37eb5d007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=8E=E5=A3=B0?= Date: Tue, 5 Mar 2024 17:08:23 +0800 Subject: [PATCH 24/24] ci: add build ci (#79) --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ internal/sdk.go | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a81bb3cf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: VersionFox CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [ '1.21.x' ] + + steps: + - uses: actions/checkout@v4 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - name: Display Go version + run: go version + - name: Install dependencies + run: | + go get . + - name: Build with the Go CLI + run: | + go build . + - name: Test with the Go CLI + run: | + go test ./internal diff --git a/internal/sdk.go b/internal/sdk.go index 1fc33ea7..58012b13 100644 --- a/internal/sdk.go +++ b/internal/sdk.go @@ -287,7 +287,7 @@ func (b *Sdk) Use(version Version, scope UseScope) error { } } b.sdkManager.Record.Add(b.Plugin.Filename, string(version)) - err := b.sdkManager.Record.Save() + err = b.sdkManager.Record.Save() if err != nil { return err }