From 3bc4d4a390fa9b2f1b81faed8b987c3b59e7d76b Mon Sep 17 00:00:00 2001 From: kcmvp Date: Tue, 12 Dec 2023 11:07:29 +0800 Subject: [PATCH] #5:init release --- cmd/{util.go => action/action.go} | 29 ++- cmd/common/cmd.go | 5 - cmd/gen.go | 28 --- cmd/git/git_hook.go | 58 ----- cmd/git/git_hook_test.go | 10 - cmd/plugin.go | 50 +++++ cmd/plugin/plugin_func.go | 68 ++++++ cmd/plugin_test.go | 76 +++++++ cmd/{git => resources}/commit_msg.tmpl | 0 cmd/{ => resources}/setup.json | 5 - cmd/root.go | 72 +++--- cmd/{root_action.go => root/root_func.go} | 138 +++++------- cmd/root_test.go | 1 - cmd/setup.go | 95 +++----- .../commit_msg.go => setup/commit_msg.tmpl} | 2 + cmd/setup/setup.json | 16 ++ cmd/setup/setup_func.go | 109 +++++++++ cmd/setup_action.go | 10 - cmd/setup_test.go | 54 +++-- config/commit_msg.go | 0 go.mod | 1 - go.sum | 2 - gob.yml | 15 -- internal/project.go | 208 ++++++++++++++++-- internal/project_test.go | 49 +++++ setup.md | 2 - 26 files changed, 741 insertions(+), 362 deletions(-) rename cmd/{util.go => action/action.go} (54%) delete mode 100644 cmd/common/cmd.go delete mode 100644 cmd/gen.go delete mode 100644 cmd/git/git_hook.go delete mode 100644 cmd/git/git_hook_test.go create mode 100644 cmd/plugin.go create mode 100644 cmd/plugin/plugin_func.go create mode 100644 cmd/plugin_test.go rename cmd/{git => resources}/commit_msg.tmpl (100%) rename cmd/{ => resources}/setup.json (63%) rename cmd/{root_action.go => root/root_func.go} (54%) rename cmd/{git/commit_msg.go => setup/commit_msg.tmpl} (82%) create mode 100644 cmd/setup/setup.json create mode 100644 cmd/setup/setup_func.go delete mode 100644 cmd/setup_action.go mode change 100644 => 100755 config/commit_msg.go delete mode 100644 gob.yml create mode 100644 internal/project_test.go diff --git a/cmd/util.go b/cmd/action/action.go similarity index 54% rename from cmd/util.go rename to cmd/action/action.go index fd71082..50e9a2c 100644 --- a/cmd/util.go +++ b/cmd/action/action.go @@ -1,17 +1,40 @@ -package cmd +package action import ( "bufio" "fmt" + "github.com/fatih/color" "github.com/kcmvp/gob/internal" "github.com/samber/lo" + "github.com/spf13/cobra" "io" "os" "os/exec" + "path/filepath" "strings" ) -func streamOutput(cmd *exec.Cmd, file string, errWords ...string) error { +type Execution func(cmd *cobra.Command, args ...string) error + +type CmdAction lo.Tuple2[string, Execution] + +func PrintCmd(cmd *cobra.Command, msg string) error { + if ok, file := internal.TestEnv(); ok { + // Get the call stack + outputFile, err := os.Create(filepath.Join(internal.CurProject().Target(), file)) + if err != nil { + return err + } + defer outputFile.Close() + writer := io.MultiWriter(os.Stdout, outputFile) + fmt.Fprintln(writer, msg) + } else { + cmd.Println(msg) + } + return nil +} + +func StreamExtCmdOutput(cmd *exec.Cmd, file string, errWords ...string) error { // Create a pipe to capture the command's combined output stdout, err := cmd.StdoutPipe() if err != nil { @@ -35,7 +58,7 @@ func streamOutput(cmd *exec.Cmd, file string, errWords ...string) error { if lo.SomeBy(errWords, func(item string) bool { return strings.Contains(line, item) }) { - internal.Red.Fprintln(os.Stdout, line) + color.Red(line) fmt.Fprintln(outputFile, line) } else { fmt.Fprintln(writer, line) diff --git a/cmd/common/cmd.go b/cmd/common/cmd.go deleted file mode 100644 index 1c0c3dd..0000000 --- a/cmd/common/cmd.go +++ /dev/null @@ -1,5 +0,0 @@ -package common - -import "github.com/spf13/cobra" - -type ArgFunc func(cmd *cobra.Command) error diff --git a/cmd/gen.go b/cmd/gen.go deleted file mode 100644 index 0efcb9a..0000000 --- a/cmd/gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// genCmd represents the generate command -var genCmd = &cobra.Command{ - Use: "gen", - Short: "Initialize useful infrastructures and tools", - Long: `Initialize useful infrastructures and tools such as: -git hook, linter and so. run "gob init -h" get more information`, - Args: func(cmd *cobra.Command, args []string) error { - return cobra.OnlyValidArgs(cmd, args) //nolint - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("init called") - }, -} - -func init() { - viper.SetDefault("ContentDir", "content") -} - -// https://github.com/jedib0t/go-pretty -// https://github.com/guptarohit/asciigraph diff --git a/cmd/git/git_hook.go b/cmd/git/git_hook.go deleted file mode 100644 index 5c029e9..0000000 --- a/cmd/git/git_hook.go +++ /dev/null @@ -1,58 +0,0 @@ -package git - -import ( - "bufio" - "embed" - "fmt" - "github.com/fatih/color" - "github.com/go-git/go-git/v5" - "github.com/kcmvp/gob/cmd/common" - "github.com/kcmvp/gob/internal" - "github.com/samber/lo" - "github.com/spf13/cobra" - "io" - "os" - "path/filepath" - "runtime" -) - -//go:embed *.tmpl -var gitHookTempDir embed.FS - -var SetupGitHookFunc common.ArgFunc = func(cmd *cobra.Command) error { - // check the project in git nor not - if _, err := git.PlainOpen(internal.CurProject().Root()); err != nil { - color.Yellow("Project is not in the source control, please add it to source repository") - return err - } - // generate commit_msg hook - os.Mkdir(filepath.Join(internal.CurProject().Root(), "config"), 0755) - src, _ := gitHookTempDir.Open("commit_msg.tmpl") - defer src.Close() - dest, _ := os.Create(filepath.Join(internal.CurProject().Root(), "config", "commit_msg.go")) - defer dest.Close() - io.Copy(dest, src) - - hookMap := map[string]string{ - "commit-msg": fmt.Sprintf("go run %s/config/commit_msg.go $1 $2", internal.CurProject().Root()), - "pre-commit": "gob lint test", - "pre-push": "gob lint test", - } - shell := lo.IfF(runtime.GOOS == "windows", func() string { - return "#!/usr/bin/env pwsh\n" - }).Else("#!/bin/sh\n") - for name, script := range hookMap { - msgHook, _ := os.OpenFile(filepath.Join(internal.CurProject().Root(), ".git", "hooks", name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - writer := bufio.NewWriter(msgHook) - writer.WriteString(shell) - writer.WriteString("\n") - writer.WriteString(script) - writer.Flush() - msgHook.Close() - } - return nil -} - -var Validate = func(cmd *cobra.Command, args []string) error { - return nil -} diff --git a/cmd/git/git_hook_test.go b/cmd/git/git_hook_test.go deleted file mode 100644 index b3810d6..0000000 --- a/cmd/git/git_hook_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package git - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestName(t *testing.T) { - assert.Equal(t, 1, 1) -} diff --git a/cmd/plugin.go b/cmd/plugin.go new file mode 100644 index 0000000..3ee2734 --- /dev/null +++ b/cmd/plugin.go @@ -0,0 +1,50 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "github.com/fatih/color" + "github.com/kcmvp/gob/cmd/plugin" + "github.com/spf13/cobra" +) + +// pluginCmd represents the plugin command +var pluginCmd = &cobra.Command{ + Use: "plugin", + Short: "List all configured plugins", + Long: `List all configured plugins`, + RunE: func(cmd *cobra.Command, args []string) error { + // run 'gob plugin' will list all the configured plugins + // run 'gob plugin -u' will list all the configured plugins and install the uninstalled tools. + return plugin.List(cmd) + }, +} + +// installPluginCmd represents the plugin install command +var installPluginCmd = &cobra.Command{ + Use: "install", + Short: "Install a tool as gob plugin", + Long: `Install a tool as gob plugin`, + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.ExactArgs(1)(cmd, args); err != nil { + return fmt.Errorf(color.RedString(err.Error())) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return plugin.Install(cmd, args...) + }, +} + +func init() { + // init pluginCmd + rootCmd.AddCommand(pluginCmd) + pluginCmd.Flags().BoolVarP(&plugin.UpdateList, "update", "u", false, "update configured plugins") + + // init installPluginCmd + pluginCmd.AddCommand(installPluginCmd) + installPluginCmd.Flags().StringVarP(&plugin.ToolAlias, "alias", "a", "", "alias of the tool") + installPluginCmd.Flags().StringVarP(&plugin.ToolCommand, "command", "c", "", "default command of this tool") +} diff --git a/cmd/plugin/plugin_func.go b/cmd/plugin/plugin_func.go new file mode 100644 index 0000000..c0fddd4 --- /dev/null +++ b/cmd/plugin/plugin_func.go @@ -0,0 +1,68 @@ +package plugin + +import ( + "errors" + "fmt" + "github.com/fatih/color" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" + "github.com/kcmvp/gob/cmd/action" + "github.com/kcmvp/gob/internal" + "github.com/samber/lo" + "github.com/spf13/cobra" + "os" + "path/filepath" + "strings" +) + +// ToolAlias is the tool alias, for the convenience of run 'gob alias' +var ToolAlias string + +// ToolCommand is the tool command, it's the default command when run 'gob alias' +var ToolCommand string + +// Install the specified tool as gob plugin +var Install action.Execution = func(cmd *cobra.Command, args ...string) error { + if strings.HasSuffix(args[0], "@master") || strings.HasSuffix(args[0], "@latest") { + return fmt.Errorf("please use specific version instead of 'master' or 'latest'") + } + err := internal.CurProject().InstallPlugin(args[0], ToolAlias, ToolCommand) + if errors.Is(err, internal.PluginExists) { + color.Yellow("Plugin %s exists", args[0]) + err = nil + } + return err +} + +var UpdateList bool +var List action.Execution = func(cmd *cobra.Command, _ ...string) error { + plugins := internal.CurProject().Plugins() + ct := table.Table{} + ct.SetTitle("Installed Plugins") + ct.AppendRow(table.Row{"Command", "Alias", "Method", "URL"}) + style := table.StyleDefault + style.Options.DrawBorder = true + style.Options.SeparateRows = true + style.Options.SeparateColumns = true + style.Title.Align = text.AlignCenter + style.HTML.CSSClass = table.DefaultHTMLCSSClass + ct.SetStyle(style) + rows := lo.Map(plugins, func(item lo.Tuple4[string, string, string, string], index int) table.Row { + return table.Row{item.A, item.B, item.C, item.D} + }) + ct.AppendRows(rows) + action.PrintCmd(cmd, ct.Render()) + if UpdateList { + for _, plugin := range plugins { + _, name := internal.NormalizePlugin(plugin.D) + if _, err := os.Stat(filepath.Join(os.Getenv("GOPATH"), "bin", name)); err != nil { + if err = internal.CurProject().InstallPlugin(plugin.D, plugin.A, plugin.C); err != nil { + color.Yellow("Waring: failed to install %s", plugin.D) + } + } else { + fmt.Printf("%s exists on the system \n", plugin.D) + } + } + } + return nil +} diff --git a/cmd/plugin_test.go b/cmd/plugin_test.go new file mode 100644 index 0000000..b27d713 --- /dev/null +++ b/cmd/plugin_test.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "bytes" + "github.com/kcmvp/gob/internal" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +var v6 = "github.com/ofabry/go-callvis@v0.6.1" +var v7 = "github.com/ofabry/go-callvis@v0.7.0" +var golanglint = "github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2" + +func TestInstallPlugin(t *testing.T) { + internal.CurProject().LoadSettings() + cfg := internal.CurProject().Config() + defer func() { + os.Remove(cfg) + }() + os.Chdir(internal.CurProject().Root()) + b := bytes.NewBufferString("") + rootCmd.SetOut(b) + rootCmd.SetArgs([]string{"plugin", "install", v6, "-a=callvis", "-c=run"}) + err := rootCmd.Execute() + assert.NoError(t, err) + plugin, ok := lo.Find(internal.CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == v6 + }) + assert.Truef(t, ok, "%s should be installed successsfully", v6) + assert.Equal(t, "go-callvis", plugin.A) + assert.Equal(t, "callvis", plugin.B) + assert.Equal(t, "run", plugin.C) + assert.Equal(t, v6, plugin.D) + // install same plugin again + rootCmd.SetArgs([]string{"plugin", "install", v6, "-a=callvis", "-c=run"}) + err = rootCmd.Execute() + assert.NoError(t, err) + plugin, ok = lo.Find(internal.CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == v6 + }) + assert.Truef(t, ok, "%s should be installed successsfully", v6) + assert.Equal(t, "go-callvis", plugin.A) + assert.Equal(t, "callvis", plugin.B) + assert.Equal(t, "run", plugin.C) + assert.Equal(t, v6, plugin.D) + // install same plugin with different version + rootCmd.SetArgs([]string{"plugin", "install", v7, "-a=callvis7", "-c=run7"}) + err = rootCmd.Execute() + assert.NoError(t, err) + plugin, ok = lo.Find(internal.CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == v7 + }) + assert.Truef(t, ok, "%s should be installed successsfully", v7) + assert.Equal(t, "go-callvis", plugin.A) + assert.Equal(t, "callvis7", plugin.B) + assert.Equal(t, "run7", plugin.C) + assert.Equal(t, v7, plugin.D) + + // install another plugin + rootCmd.SetArgs([]string{"plugin", "install", golanglint, "-a=lint", "-c=lint-run"}) + err = rootCmd.Execute() + assert.NoError(t, err) + rootCmd.SetArgs([]string{"plugin"}) + err = rootCmd.Execute() + assert.NoError(t, err) + plugin, ok = lo.Find(internal.CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == golanglint + }) + assert.Equal(t, "golangci-lint", plugin.A) + assert.Equal(t, "lint", plugin.B) + assert.Equal(t, "lint-run", plugin.C) + assert.Equal(t, golanglint, plugin.D) + assert.Equal(t, 2, len(internal.CurProject().Plugins())) +} diff --git a/cmd/git/commit_msg.tmpl b/cmd/resources/commit_msg.tmpl similarity index 100% rename from cmd/git/commit_msg.tmpl rename to cmd/resources/commit_msg.tmpl diff --git a/cmd/setup.json b/cmd/resources/setup.json similarity index 63% rename from cmd/setup.json rename to cmd/resources/setup.json index 6293fdf..9eb9a38 100644 --- a/cmd/setup.json +++ b/cmd/resources/setup.json @@ -1,9 +1,4 @@ [ - { - "name": "lint", - "url": "github.com/golangci/golangci-lint/cmd/golangci-lint", - "desc": "setup golangci-lint" - }, { "name": "githook", "desc": "setup git hook" diff --git a/cmd/root.go b/cmd/root.go index a19dc25..df3295a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,58 +3,38 @@ package cmd import ( "context" - "github.com/kcmvp/gob/cmd/common" + "fmt" + "github.com/fatih/color" + "github.com/kcmvp/gob/cmd/action" + "github.com/kcmvp/gob/cmd/root" "github.com/kcmvp/gob/internal" "github.com/samber/lo" "github.com/spf13/cobra" "os" ) -const ( - CleanCacheFlag = "cache" - CleanTestCacheFlag = "testcache" - CleanModCacheFlag = "modcache" - LintAllFlag = "all" -) - -// cleanCache the same as 'go clean -cache' -var cleanCache bool - -// cleanTestCache the same as `go clean -testcache' -var cleanTestCache bool - -// cleanModCache the same as 'go clean -modcache' -var cleanModCache bool - -// lintAll stands for lint on all source code -var lintAll bool - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "gob", Short: "Go project boot", Long: `Supply most frequently used tool and best practices for go project development`, - ValidArgs: lo.Map(buildActions, func(item lo.Tuple2[string, common.ArgFunc], _ int) string { + ValidArgs: lo.Map(root.BuildActions(), func(item action.CmdAction, _ int) string { return item.A }), Args: cobra.MatchAll(cobra.OnlyValidArgs, cobra.MinimumNArgs(1)), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - //@todo validate the project according to gen.yml - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - buildProject(cmd, args) + RunE: func(cmd *cobra.Command, args []string) error { + return buildProject(cmd, args) }, } func Execute() { currentDir, err := os.Getwd() if err != nil { - internal.Red.Printf("Failed to execute command: %v", err) + color.Red("Failed to execute command: %v", err) return } if internal.CurProject().Root() != currentDir { - internal.Yellow.Println("Please execute the command in the project root dir") + color.Red("Please execute the command in the project root dir") return } ctx := context.Background() @@ -63,12 +43,34 @@ func Execute() { } } +func buildProject(cmd *cobra.Command, args []string) error { + uArgs := lo.Uniq(args) + expectedActions := lo.Filter(root.BuildActions(), func(item action.CmdAction, index int) bool { + return lo.Contains(uArgs, item.A) + }) + // Check if the folder exists + os.Mkdir(internal.CurProject().Target(), os.ModePerm) + for _, action := range expectedActions { + msg := fmt.Sprintf("Start %s project", action.A) + fmt.Printf("%-20s ...... \n", msg) + if err := action.B(cmd); err != nil { + color.Red("Failed to %s project %v \n", action.A, err.Error()) + return err + } + } + return nil +} + func init() { - rootCmd.SetErrPrefix(internal.Red.Sprintf("Error:")) - rootCmd.AddCommand(setupCmd) + rootCmd.SetErrPrefix(color.RedString("Error:")) + rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error { + return lo.IfF(err != nil, func() error { + return fmt.Errorf(color.RedString(err.Error())) + }).Else(nil) + }) rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - rootCmd.Flags().BoolVar(&cleanCache, CleanCacheFlag, false, "to remove the entire go build cache") - rootCmd.Flags().BoolVar(&cleanTestCache, CleanTestCacheFlag, false, "to expire all test results in the go build cache") - rootCmd.Flags().BoolVar(&cleanModCache, CleanModCacheFlag, false, "to remove the entire module download cache") - rootCmd.Flags().BoolVar(&lintAll, LintAllFlag, false, "lint scan all source code, default only on changed source code") + rootCmd.Flags().BoolVar(&root.CleanCache, root.CleanCacheFlag, false, "to remove the entire go build cache") + rootCmd.Flags().BoolVar(&root.CleanTestCache, root.CleanTestCacheFlag, false, "to expire all test results in the go build cache") + rootCmd.Flags().BoolVar(&root.CleanModCache, root.CleanModCacheFlag, false, "to remove the entire module download cache") + rootCmd.Flags().BoolVar(&root.LintAll, root.LintAllFlag, false, "lint scan all source code, default only on changed source code") } diff --git a/cmd/root_action.go b/cmd/root/root_func.go similarity index 54% rename from cmd/root_action.go rename to cmd/root/root_func.go index 0f81e3e..f48ccf9 100644 --- a/cmd/root_action.go +++ b/cmd/root/root_func.go @@ -1,12 +1,11 @@ -package cmd +package root import ( "bufio" "fmt" "github.com/fatih/color" - "github.com/kcmvp/gob/cmd/common" + "github.com/kcmvp/gob/cmd/action" "github.com/kcmvp/gob/internal" - "github.com/samber/lo" "github.com/spf13/cobra" "io/fs" "os" @@ -16,16 +15,18 @@ import ( "strings" ) +var CleanCache bool +var CleanTestCache bool +var CleanModCache bool +var LintAll bool + const ( - cleanAction = "clean" - testAction = "test" - lintAction = "lint" - buildAction = "build" - TargetFolder = "target" + CleanCacheFlag = "cache" + CleanTestCacheFlag = "testcache" + CleanModCacheFlag = "modcache" + LintAllFlag = "all" ) -var targetFolder = fmt.Sprintf("%s/target", internal.CurProject().Root()) - func findMain(dir string) (string, error) { var mf string re := regexp.MustCompile(`func\s+main\s*\(\s*\)`) @@ -57,57 +58,7 @@ func findMain(dir string) (string, error) { return mf, err } -var cleanFunc common.ArgFunc = func(cmd *cobra.Command) error { - // clean target folder - target := filepath.Join(internal.CurProject().Root(), TargetFolder) - filepath.WalkDir(target, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if path != target { - if err = os.RemoveAll(path); err != nil { - return err - } - } - return nil - }) - fmt.Println("Clean target folder successfully !") - // clean cache - args := []string{"clean"} - if cleanCache { - args = append(args, fmt.Sprintf("--%s", CleanCacheFlag)) - } - if cleanTestCache { - args = append(args, fmt.Sprintf("--%s", CleanTestCacheFlag)) - } - if cleanModCache { - args = append(args, fmt.Sprintf("--%s", CleanModCacheFlag)) - } - _, err := exec.Command("go", args...).CombinedOutput() - if len(args) > 1 && err == nil { - fmt.Println("Clean cache successfully !") - } - return nil -} - -var lintFunc common.ArgFunc = func(cmd *cobra.Command) error { - return nil -} - -var testFunc common.ArgFunc = func(cmd *cobra.Command) error { - coverProfile := fmt.Sprintf("-coverprofile=%s/cover.out", targetFolder) - testCmd := exec.Command("go", []string{"test", "-v", coverProfile, "./..."}...) - err := streamOutput(testCmd, fmt.Sprintf("%s/test.log", targetFolder), "FAIL:") - if err != nil { - return err - } - exec.Command("go", []string{"tool", "cover", fmt.Sprintf("-html=%s/cover.out", targetFolder), fmt.Sprintf("-o=%s/cover.html", targetFolder)}...).CombinedOutput() - color.Green("Test report is generated at %s/test.log \n", targetFolder) - color.Green("Coverage report is generated at %s/cover.html \n", targetFolder) - return nil -} - -var buildFunc common.ArgFunc = func(cmd *cobra.Command) error { +var buildCommand = func(_ *cobra.Command, args ...string) error { dirs, err := internal.FindGoFilesByPkg("main") if err != nil { return err @@ -124,7 +75,7 @@ var buildFunc common.ArgFunc = func(cmd *cobra.Command) error { if f, exists := bm[binary]; exists { return fmt.Errorf("file %s has already built as %s, please rename %s", f, binary, mf) } - output := filepath.Join(internal.CurProject().Root(), TargetFolder, binary) + output := filepath.Join(internal.CurProject().Root(), internal.CurProject().Target(), binary) if _, err := exec.Command("go", "build", "-o", output, mf).CombinedOutput(); err != nil { //nolint return err } else { @@ -138,26 +89,51 @@ var buildFunc common.ArgFunc = func(cmd *cobra.Command) error { return nil } -var buildActions = []lo.Tuple2[string, common.ArgFunc]{ - lo.T2(cleanAction, cleanFunc), - lo.T2(testAction, testFunc), - lo.T2(lintAction, lintFunc), - lo.T2(buildAction, buildFunc), +var cleanCommand = func(cmd *cobra.Command, _ ...string) error { + // clean target folder + os.RemoveAll(internal.CurProject().Target()) + os.Mkdir(internal.CurProject().Target(), os.ModePerm) + fmt.Println("Clean target folder successfully !") + // clean cache + args := []string{"clean"} + if CleanCache { + args = append(args, fmt.Sprintf("--%s", CleanCacheFlag)) + } + if CleanTestCache { + args = append(args, fmt.Sprintf("--%s", CleanTestCacheFlag)) + } + if CleanModCache { + args = append(args, fmt.Sprintf("--%s", CleanModCacheFlag)) + } + _, err := exec.Command("go", args...).CombinedOutput() + if len(args) > 1 && err == nil { + fmt.Println("Clean cache successfully !") + } + return nil +} +var testCommand = func(_ *cobra.Command, args ...string) error { + coverProfile := fmt.Sprintf("-coverprofile=%s/cover.out", internal.CurProject().Target()) + testCmd := exec.Command("go", []string{"test", "-v", coverProfile, "./..."}...) + err := action.StreamExtCmdOutput(testCmd, fmt.Sprintf("%s/test.log", internal.CurProject().Target()), "FAIL:") + if err != nil { + return err + } + exec.Command("go", []string{"tool", "cover", fmt.Sprintf("-html=%s/cover.out", internal.CurProject().Target()), fmt.Sprintf("-o=%s/cover.html", internal.CurProject().Target())}...).CombinedOutput() + color.Green("Test report is generated at %s/test.log \n", internal.CurProject().Target()) + color.Green("Coverage report is generated at %s/cover.html \n", internal.CurProject().Target()) + return nil } -var buildProject = func(cmd *cobra.Command, args []string) { - uArgs := lo.Uniq(args) - expectedActions := lo.Filter(buildActions, func(item lo.Tuple2[string, common.ArgFunc], index int) bool { - return lo.Contains(uArgs, item.A) - }) - // Check if the folder exists - os.Mkdir(targetFolder, 0755) - for _, action := range expectedActions { - msg := fmt.Sprintf("Start %s project", action.A) - fmt.Printf("%-20s ...... \n", msg) - if err := action.B(cmd); err != nil { - internal.Red.Printf("Failed to %s project %v \n", action.A, err.Error()) - break - } +var lintCommand = func(cmd *cobra.Command, args ...string) error { + + return nil +} + +func BuildActions() []action.CmdAction { + return []action.CmdAction{ + {"build", buildCommand}, + {"clean", cleanCommand}, + {"test", testCommand}, + {"lint", lintCommand}, } } diff --git a/cmd/root_test.go b/cmd/root_test.go index 512675c..734bb5e 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,6 @@ func TestBuilder_Build(t *testing.T) { os.Chdir(internal.CurProject().Root()) b := bytes.NewBufferString("") rootCmd.SetOut(b) - //rootCmd.SetArgs([]string{"action", "--cache"}) rootCmd.SetArgs([]string{"test"}) err := rootCmd.Execute() require.NoError(t, err) diff --git a/cmd/setup.go b/cmd/setup.go index 6e8294e..cd3540b 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -3,85 +3,46 @@ package cmd import ( _ "embed" "fmt" - "github.com/jedib0t/go-pretty/v6/table" - "github.com/kcmvp/gob/cmd/common" - "github.com/kcmvp/gob/cmd/git" + "github.com/fatih/color" + "github.com/kcmvp/gob/cmd/action" + "github.com/kcmvp/gob/cmd/setup" "github.com/samber/lo" "github.com/spf13/cobra" - "github.com/thedevsaddam/gojsonq/v2" + "strings" ) -//go:embed setup.json -var data []byte - -const ( - GitHook = "githook" - Onion = "onion" - List = "list" -) - -type Setup struct { - Name string - Url string - Desc string -} - -var setups []Setup -var list bool - -var listFunc common.ArgFunc = func(cmd *cobra.Command) error { - ct := table.Table{} - ct.SetTitle("Available Setups") - ct.AppendHeader(table.Row{"#", "Name", "Description"}) - style := table.StyleDefault - style.Options.DrawBorder = true - style.Options.SeparateRows = true - style.Options.SeparateColumns = true - style.HTML.CSSClass = table.DefaultHTMLCSSClass - ct.SetStyle(style) - consoleRows := lo.Map(setups, func(setup Setup, i int) table.Row { - return table.Row{i + 1, setup.Name, setup.Desc} - }) - ct.AppendRows(consoleRows) - fmt.Println(ct.Render()) - return nil -} - -var setupFuncs = []lo.Tuple2[string, common.ArgFunc]{ - lo.T2(List, listFunc), - lo.T2(Onion, onionFunc), - lo.T2(GitHook, git.SetupGitHookFunc), -} - // setupCmd represents the setup command var setupCmd = &cobra.Command{ Use: "setup", Short: "Setup useful infrastructures and tools", - Long: `Setup useful infrastructures and tools such as: -git hook, linter and so on. run "gob setup list" to list all supported artifacts`, - ValidArgs: []string{List}, - Args: cobra.MatchAll(cobra.OnlyValidArgs, cobra.MaximumNArgs(1)), - Run: func(cmd *cobra.Command, args []string) { - if len(args) > 0 { - fn, _ := lo.Find(setupFuncs, func(item lo.Tuple2[string, common.ArgFunc]) bool { - return args[0] == item.A - }) - fn.B(cmd) + Long: `Setup useful infrastructures and tools +Run 'gob setup list get full supported list'`, + ValidArgs: func() []string { + return lo.Map(setup.Actions, func(item action.CmdAction, index int) string { + return item.A + }) + }(), + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.MatchAll(cobra.OnlyValidArgs, cobra.ExactArgs(1))(cmd, args); err != nil { + return fmt.Errorf(color.RedString(err.Error())) } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + fn, _ := lo.Find(setup.Actions, func(item action.CmdAction) bool { + return item.A == args[0] + }) + return fn.B(cmd, args...) }, } func init() { - jq := gojsonq.New().FromString(string(data)) - v := jq.Select("name", "url", "desc").Get() - for _, item := range v.([]interface{}) { - m := item.(map[string]interface{}) - setups = append(setups, Setup{ - Name: fmt.Sprintf("%s", m["name"]), - Url: fmt.Sprintf("%s", m["url"]), - Desc: fmt.Sprintf("%s", m["desc"]), + setupCmd.SetUsageFunc(func(command *cobra.Command) error { + keys := lo.Map(setup.Actions, func(item action.CmdAction, _ int) string { + return item.A }) - setupCmd.ValidArgs = append(setupCmd.ValidArgs, fmt.Sprintf("%s", m["name"])) - } - setupCmd.Flags().BoolVarP(&list, "list", "l", false, "list all artifactss") + color.HiYellow("Valid Arguments: %s", strings.Join(keys, ", ")) + return nil + }) + rootCmd.AddCommand(setupCmd) } diff --git a/cmd/git/commit_msg.go b/cmd/setup/commit_msg.tmpl similarity index 82% rename from cmd/git/commit_msg.go rename to cmd/setup/commit_msg.tmpl index db96e71..4d95dd7 100644 --- a/cmd/git/commit_msg.go +++ b/cmd/setup/commit_msg.tmpl @@ -3,6 +3,7 @@ package main import ( + "github.com/fatih/color" "os" "regexp" ) @@ -16,6 +17,7 @@ func main() { commitMsg := regex.ReplaceAllString(string(input), "") regex = regexp.MustCompile(commitMsgPattern) if !regex.MatchString(commitMsg) { + color.Red("Error: commit message must follow %s", commitMsgPattern) os.Exit(1) } os.Exit(0) diff --git a/cmd/setup/setup.json b/cmd/setup/setup.json new file mode 100644 index 0000000..9eb9a38 --- /dev/null +++ b/cmd/setup/setup.json @@ -0,0 +1,16 @@ +[ + { + "name": "githook", + "desc": "setup git hook" + }, + { + "name": "gitflow", + "desc": "setup github workflow" + }, + { + "name": "onion", + "desc": "generate onion architecture project layout" + } +] + + diff --git a/cmd/setup/setup_func.go b/cmd/setup/setup_func.go new file mode 100644 index 0000000..9f5c018 --- /dev/null +++ b/cmd/setup/setup_func.go @@ -0,0 +1,109 @@ +package setup + +import ( + "bufio" + _ "embed" + "encoding/json" + "fmt" + "github.com/fatih/color" + "github.com/go-git/go-git/v5" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" + "github.com/kcmvp/gob/cmd/action" + "github.com/kcmvp/gob/internal" + "github.com/samber/lo" + "github.com/spf13/cobra" + "log" + "os" + "path/filepath" +) + +//go:embed setup.json +var data []byte + +//go:embed commit_msg.tmpl +var hook []byte + +var setups []Setup + +type Setup struct { + Name string `json:"name"` + Desc string `json:"desc"` +} + +func init() { + json.Unmarshal(data, &setups) + // for each command there should be a action + if missed, ok := lo.Find(setups, func(setup Setup) bool { + return !lo.ContainsBy(Actions, func(action action.CmdAction) bool { + return action.A == setup.Name + }) + }); ok { + log.Fatal(color.RedString("Fatal: missed action for %s\n", missed.Name)) + } +} + +var Actions = []action.CmdAction{ + {"list", list}, + {"githook", gitHook}, + {"gitflow", gitflow}, + {"onion", onion}, +} + +var gitflow action.Execution = func(cmd *cobra.Command, args ...string) error { + return nil +} + +var onion action.Execution = func(cmd *cobra.Command, args ...string) error { + return nil +} + +var list action.Execution = func(cmd *cobra.Command, args ...string) error { + ct := table.Table{} + ct.SetTitle("Available Actions") + ct.AppendRow(table.Row{"#", "Name", "Description"}) + style := table.StyleDefault + style.Options.DrawBorder = true + style.Options.SeparateRows = true + style.Options.SeparateColumns = true + style.Title.Align = text.AlignCenter + style.HTML.CSSClass = table.DefaultHTMLCSSClass + ct.SetStyle(style) + consoleRows := lo.Map(setups, func(setup Setup, i int) table.Row { + return table.Row{i + 1, setup.Name, setup.Desc} + }) + ct.AppendRows(consoleRows) + action.PrintCmd(cmd, ct.Render()) + return nil +} + +var gitHook action.Execution = func(cmd *cobra.Command, args ...string) error { + if _, err := git.PlainOpen(internal.CurProject().Root()); err != nil { + color.Yellow("Project is not in the source control, please add it to source repository") + return err + } + config := filepath.Join(internal.CurProject().Root(), "config") + os.Mkdir(config, os.ModePerm) + err := os.WriteFile(filepath.Join(config, "commit_msg.go"), hook, os.ModePerm) + if err != nil { + return err + } + hookMap := map[string]string{ + "commit-msg": fmt.Sprintf("go run %s/config/commit_msg.go $1 $2", internal.CurProject().Root()), + "pre-commit": "gob lint test", + "pre-push": "gob lint test", + } + shell := lo.IfF(internal.Windows(), func() string { + return "#!/usr/bin/env pwsh\n" + }).Else("#!/bin/sh\n") + for name, script := range hookMap { + msgHook, _ := os.OpenFile(filepath.Join(internal.CurProject().Root(), ".git", "hooks", name), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + writer := bufio.NewWriter(msgHook) + writer.WriteString(shell) + writer.WriteString("\n") + writer.WriteString(script) + writer.Flush() + msgHook.Close() + } + return nil +} diff --git a/cmd/setup_action.go b/cmd/setup_action.go deleted file mode 100644 index 0588acd..0000000 --- a/cmd/setup_action.go +++ /dev/null @@ -1,10 +0,0 @@ -package cmd - -import ( - "github.com/kcmvp/gob/cmd/common" - "github.com/spf13/cobra" -) - -var onionFunc common.ArgFunc = func(cmd *cobra.Command) error { - return nil -} diff --git a/cmd/setup_test.go b/cmd/setup_test.go index 57bfa54..d5d96a9 100644 --- a/cmd/setup_test.go +++ b/cmd/setup_test.go @@ -1,29 +1,41 @@ package cmd import ( - "bytes" - "github.com/kcmvp/gob/internal" "github.com/stretchr/testify/require" - "os" "testing" ) -func TestSetup(t *testing.T) { - os.Chdir(internal.CurProject().Root()) - b := bytes.NewBufferString("") - rootCmd.SetOut(b) - //rootCmd.SetArgs([]string{"action", "--cache"}) - rootCmd.SetArgs([]string{"setup", "-l"}) - err := rootCmd.Execute() - require.NoError(t, err) +func TestSetupArgs(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + }{ + { + "no arg", + []string{}, + true, + }, + { + "two args", + []string{"list", "githook"}, + true, + }, + { + "non-exist arg", + []string{"abc"}, + true, + }, + { + "list", + []string{"list"}, + false, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + err := setupCmd.ValidateArgs(testCase.args) + require.True(t, testCase.wantErr == (err != nil)) + }) + } } - -//func TestSetup_GitHook(t *testing.T) { -// os.Chdir(internal.CurProject().Root()) -// b := bytes.NewBufferString("") -// rootCmd.SetOut(b) -// //rootCmd.SetArgs([]string{"action", "--cache"}) -// rootCmd.SetArgs([]string{"setup", "githook"}) -// err := rootCmd.Execute() -// require.NoError(t, err) -//} diff --git a/config/commit_msg.go b/config/commit_msg.go old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod index 2325f42..8fb4544 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 - github.com/thedevsaddam/gojsonq/v2 v2.5.2 ) require ( diff --git a/go.sum b/go.sum index 095b9bd..70df47a 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= -github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/gob.yml b/gob.yml deleted file mode 100644 index db9cf19..0000000 --- a/gob.yml +++ /dev/null @@ -1,15 +0,0 @@ -githook: - pre-commit: - - test - - linter - pre-push: - - test - - linter - commit-msg: - menLength: 10 - format: '#\d:.*' -#github-workflows: true -golangci-lint: v1.2 - - - diff --git a/internal/project.go b/internal/project.go index fe357d7..0a0ca5d 100644 --- a/internal/project.go +++ b/internal/project.go @@ -2,56 +2,105 @@ package internal import ( "bufio" + "errors" "fmt" "github.com/fatih/color" + "github.com/samber/lo" + "github.com/spf13/viper" + "io/fs" "log" + "os" "os/exec" + "path/filepath" + "runtime" "strings" "sync" ) +const pluginKey = "plugins" + var ( - Yellow *color.Color - Red *color.Color - Blue *color.Color once sync.Once - project *Project + project Project + module string + //root string ) -func init() { - Yellow = color.New(color.FgYellow) - Red = color.New(color.FgRed) +var PluginExists = PluginExistsError{"plugin exists"} + +type PluginExistsError struct { + errorString string } -func CurProject() *Project { - once.Do(func() { - project = initProject() - }) - return project +func (p PluginExistsError) Error() string { + return p.errorString } type Project struct { root string module string deps []string + viper *viper.Viper + cfg string +} + +func TestEnv() (bool, string) { + var test bool + var file string + callers := make([]uintptr, 20) + n := runtime.Callers(0, callers) + frames := runtime.CallersFrames(callers[:n]) + for { + frame, more := frames.Next() + test = strings.HasSuffix(frame.File, "_test.go") && strings.HasPrefix(frame.Function, module) + if test || !more { + file, _ = lo.Last(strings.Split(frame.Function, ".")) + break + } + } + return test, file +} + +func (project *Project) LoadSettings() { + testEnv, _ := TestEnv() + v := viper.New() + v.SetConfigType("yaml") + path := project.Root() + name := "gob" + if testEnv { + name = fmt.Sprintf("gob-%s", lo.RandomString(12, lo.AlphanumericCharset)) + path = project.Target() + } + v.AddConfigPath(path) + v.SetConfigName(name) + if err := v.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + if errors.As(err, &configFileNotFoundError) { + color.Yellow("Warning: can not find configuration gob.yaml") + } + } + project.cfg = fmt.Sprintf("%s.yaml", filepath.Join(path, name)) + project.viper = v } -func initProject() *Project { +func init() { cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}:{{.Path}}") output, err := cmd.Output() if err != nil || len(string(output)) == 0 { - log.Fatal(Red.Sprintf("Error executing command: %v", err)) + log.Fatal(color.RedString("Error executing command: %v", err)) } item := strings.Split(strings.TrimSpace(string(output)), ":") - project := &Project{ + //root = item[0] + module = item[1] + project = Project{ root: item[0], - module: item[1], + module: module, } cmd = exec.Command("go", "list", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", "-deps") output, err = cmd.Output() if err != nil { - log.Fatal(Red.Sprintf("Error executing command: %v", err)) + log.Fatal(color.RedString("Error executing command: %v", err)) } scanner := bufio.NewScanner(strings.NewReader(string(output))) var deps []string @@ -62,17 +111,35 @@ func initProject() *Project { } } project.deps = deps - return project } +// CurProject return Project struct +func CurProject() *Project { + once.Do(func() { + project.LoadSettings() + }) + return &project +} + +func (project *Project) Config() string { + return project.cfg +} + +// Root return root dir of the project func (project *Project) Root() string { return project.root } +// Module return current project module name func (project *Project) Module() string { return project.module } +func (project *Project) Target() string { + return filepath.Join(project.root, "target") +} + +// FindGoFilesByPkg return all go source file in a package func FindGoFilesByPkg(pkg string) ([]string, error) { cmd := exec.Command("go", "list", "-f", fmt.Sprintf("{{if eq .Name \"%s\"}}{{.Dir}}{{end}}", pkg), "./...") output, err := cmd.Output() @@ -92,3 +159,108 @@ func FindGoFilesByPkg(pkg string) ([]string, error) { } return dirs, nil } + +// Plugins all the configured plugins +func (project *Project) Plugins() []lo.Tuple4[string, string, string, string] { + if project.viper.Get(pluginKey) != nil { + plugins := project.viper.Get(pluginKey).(map[string]any) + return lo.MapToSlice(plugins, func(key string, value any) lo.Tuple4[string, string, string, string] { + attr := value.(map[string]any) + //@todo validate attribute for null or empty + return lo.Tuple4[string, string, string, string]{ + key, attr["alias"].(string), attr["command"].(string), attr["url"].(string), + } + }) + } else { + return []lo.Tuple4[string, string, string, string]{} + } +} + +// NormalizePlugin returns the base name and versioned name of the plugin +func NormalizePlugin(url string) (base string, name string) { + name, _ = lo.Last(strings.Split(url, "/")) + base = strings.Split(name, "@")[0] + name = strings.ReplaceAll(name, "@", "-") + if Windows() { + name = fmt.Sprintf("%s.exe", name) + } + return +} + +// PluginInstalled return true if the plugin is installed +func (project *Project) PluginInstalled(url string) bool { + _, name := NormalizePlugin(url) + _, err := os.Stat(filepath.Join(os.Getenv("GOPATH"), "bin", name)) + return err == nil +} + +func (project *Project) PluginConfigured(url string) bool { + _, ok := lo.Find(CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == strings.TrimSpace(url) + }) + return ok +} + +// InstallPlugin install the tool as gob plugin save it in gob.yml +func (project *Project) InstallPlugin(url string, aliasAndCommand ...string) error { + base, name := NormalizePlugin(url) + gopath := os.Getenv("GOPATH") + installed := project.PluginInstalled(url) + configured := project.PluginConfigured(url) + if installed && configured { + return PluginExists + } else { + var err error + if !installed { + // install only + dir, _ := os.MkdirTemp("", base) + os.Setenv("GOPATH", dir) + fmt.Sprintf("Installing %s ...... \n", url) + _, err = exec.Command("go", "install", url).CombinedOutput() + if err != nil { + return fmt.Errorf("failed to install %s: %v", url, err) + } + defer func() { + os.Setenv("GOPATH", gopath) + os.RemoveAll(dir) + }() + if err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && strings.HasPrefix(d.Name(), base) { + err = os.Rename(path, filepath.Join(gopath, "bin", name)) + if err != nil { + return err + } + fmt.Printf("%s is installed successfully \n", url) + return filepath.SkipAll + } + return err + }); err != nil { + return err + } + } + if !configured { + // install & update configuration + fmt.Println("Updating configuration ......") + var alias, command string + if len(aliasAndCommand) > 0 { + alias = aliasAndCommand[0] + } + if len(aliasAndCommand) > 1 { + command = aliasAndCommand[1] + } + project.viper.Set(fmt.Sprintf("%s.%s.%s", pluginKey, base, "alias"), alias) + project.viper.Set(fmt.Sprintf("%s.%s.%s", pluginKey, base, "command"), command) + project.viper.Set(fmt.Sprintf("%s.%s.%s", pluginKey, base, "url"), url) + err = project.viper.WriteConfigAs(project.cfg) + } + return err + } +} + +// Windows return true when current os is Windows +func Windows() bool { + return runtime.GOOS == "windows" +} diff --git a/internal/project_test.go b/internal/project_test.go new file mode 100644 index 0000000..08ed78e --- /dev/null +++ b/internal/project_test.go @@ -0,0 +1,49 @@ +package internal + +import ( + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +var v6 = "github.com/ofabry/go-callvis@v0.6.1" +var v7 = "github.com/ofabry/go-callvis@v0.7.0" + +func TestInstallPlugin(t *testing.T) { + CurProject().LoadSettings() + cfg := CurProject().Config() + defer func() { + os.Remove(cfg) + }() + err := CurProject().InstallPlugin(v6, "callvis") + assert.NoError(t, err) + gopath := os.Getenv("GOPATH") + _, name := NormalizePlugin(v6) + _, err = os.Stat(filepath.Join(gopath, "bin", name)) + assert.NoError(t, err) + plugin, ok := lo.Find(CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == v6 + }) + assert.True(t, ok) + assert.Equal(t, "go-callvis", plugin.A) + assert.Equal(t, "callvis", plugin.B) + assert.Empty(t, plugin.C) + assert.Equal(t, v6, plugin.D) + + // install same plugin again + err = CurProject().InstallPlugin(v6, "callvis") + assert.Error(t, err) + + // update the plugin + err = CurProject().InstallPlugin(v7, "callvisv7", "run") + assert.NoError(t, err) + plugin, ok = lo.Find(CurProject().Plugins(), func(item lo.Tuple4[string, string, string, string]) bool { + return item.D == v7 + }) + assert.True(t, ok) + assert.Equal(t, 1, len(CurProject().Plugins())) + assert.Equal(t, plugin.B, "callvisv7") + assert.Equal(t, plugin.C, "run") +} diff --git a/setup.md b/setup.md index 0302739..b3a0cae 100644 --- a/setup.md +++ b/setup.md @@ -8,5 +8,3 @@ ### Fiber ## GRPC - -