diff --git a/.gitignore b/.gitignore index 22db2f280..26cd0344b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist node_modules docs/package-lock.json -.omc \ No newline at end of file +.omc +cmd/jzero/jzero \ No newline at end of file diff --git a/cmd/jzero/internal/command/add/add.go b/cmd/jzero/internal/command/add/add.go index 4bd1441fb..5e4671e48 100644 --- a/cmd/jzero/internal/command/add/add.go +++ b/cmd/jzero/internal/command/add/add.go @@ -1,11 +1,15 @@ package add import ( + "fmt" + "github.com/spf13/cobra" "github.com/jzero-io/jzero/cmd/jzero/internal/command/add/addapi" "github.com/jzero-io/jzero/cmd/jzero/internal/command/add/addproto" "github.com/jzero-io/jzero/cmd/jzero/internal/command/add/addsql" + "github.com/jzero-io/jzero/cmd/jzero/internal/config" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" ) // addCmd represents the add command @@ -19,27 +23,30 @@ var addApiCmd = &cobra.Command{ Use: "api", Short: `Add api`, RunE: func(cmd *cobra.Command, args []string) error { - return addapi.Run(args) + return runAddStage("api", args, addapi.Run) }, - SilenceUsage: true, + SilenceUsage: true, + SilenceErrors: true, } var addProtoCmd = &cobra.Command{ Use: "proto", Short: `Add proto`, RunE: func(cmd *cobra.Command, args []string) error { - return addproto.Run(args) + return runAddStage("proto", args, addproto.Run) }, - SilenceUsage: true, + SilenceUsage: true, + SilenceErrors: true, } var addSqlCmd = &cobra.Command{ Use: "sql", Short: `Add sql`, RunE: func(cmd *cobra.Command, args []string) error { - return addsql.Run(args) + return runAddStage("sql", args, addsql.Run) }, - SilenceUsage: true, + SilenceUsage: true, + SilenceErrors: true, } func GetCommand() *cobra.Command { @@ -50,3 +57,33 @@ func GetCommand() *cobra.Command { addCmd.AddCommand(addSqlCmd) return addCmd } + +func runAddStage(kind string, args []string, fn func([]string) (string, error)) error { + target, err := fn(args) + if config.C.Add.Output != "file" || config.C.Quiet { + return err + } + + title := console.Green("Add") + " " + console.Yellow(kind) + fmt.Printf("%s\n", console.BoxHeader("", title)) + + if err != nil { + if target != "" { + fmt.Printf("%s\n", console.BoxErrorItem(target)) + } + for _, line := range console.NormalizeErrorLines(err.Error()) { + fmt.Printf("%s\n", console.BoxDetailItem(line)) + } + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + if config.C.Quiet { + return err + } + return console.MarkRenderedError(err) + } + + if target != "" { + fmt.Printf("%s\n", console.BoxItem(target)) + } + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) + return nil +} diff --git a/cmd/jzero/internal/command/add/addapi/addapi.go b/cmd/jzero/internal/command/add/addapi/addapi.go index f9827211e..c13e97a53 100644 --- a/cmd/jzero/internal/command/add/addapi/addapi.go +++ b/cmd/jzero/internal/command/add/addapi/addapi.go @@ -15,7 +15,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/templatex" ) -func Run(args []string) error { +func Run(args []string) (string, error) { baseDir := filepath.Join("desc", "api") apiName := args[0] @@ -24,6 +24,8 @@ func Run(args []string) error { apiName = strings.TrimSuffix(apiName, ".api") } + target := filepath.Join(baseDir, apiName+".api") + // fix https://github.com/jzero-io/jzero/issues/405. // For jzero, each api file, the server name can be different. template, err := templatex.ParseTemplate(filepath.Join("api", "template.api.tpl"), map[string]any{ @@ -31,24 +33,24 @@ func Run(args []string) error { "Group": apiName, }, embeded.ReadTemplateFile(filepath.Join("api", "template.api.tpl"))) if err != nil { - return err + return target, err } if config.C.Add.Output == "file" { - if filex.FileExists(filepath.Join(baseDir, apiName+".api")) { - return fmt.Errorf("%s already exists", apiName) + if filex.FileExists(target) { + return target, fmt.Errorf("%s already exists", apiName) } _ = os.MkdirAll(filepath.Dir(filepath.Join(baseDir, apiName)), 0o755) - err = os.WriteFile(filepath.Join(baseDir, apiName+".api"), template, 0o644) + err = os.WriteFile(target, template, 0o644) if err != nil { - return err + return target, err } // format - return format.ApiFormatByPath(filepath.Join(baseDir, apiName+".api"), false) + return target, format.ApiFormatByPath(target, false) } fmt.Println(string(template)) - return nil + return target, nil } diff --git a/cmd/jzero/internal/command/add/addproto/addproto.go b/cmd/jzero/internal/command/add/addproto/addproto.go index 8a142c14e..17cb4af92 100644 --- a/cmd/jzero/internal/command/add/addproto/addproto.go +++ b/cmd/jzero/internal/command/add/addproto/addproto.go @@ -14,7 +14,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/templatex" ) -func Run(args []string) error { +func Run(args []string) (string, error) { baseDir := filepath.Join("desc", "proto") protoName := args[0] @@ -23,6 +23,8 @@ func Run(args []string) error { protoName = strings.TrimSuffix(protoName, ".proto") } + target := filepath.Join(baseDir, protoName+".proto") + frameType, _ := desc.GetFrameType() if frameType == "" { frameType = "rpc" @@ -35,22 +37,22 @@ func Run(args []string) error { "Service": stringx.ToCamel(protoName), }, embeded.ReadTemplateFile(filepath.Join(frameType, "template.proto.tpl"))) if err != nil { - return err + return target, err } if config.C.Add.Output == "file" { - if filex.FileExists(filepath.Join(baseDir, protoName+".proto")) { - return fmt.Errorf("%s already exists", protoName) + if filex.FileExists(target) { + return target, fmt.Errorf("%s already exists", protoName) } _ = os.MkdirAll(filepath.Dir(filepath.Join(baseDir, protoName)), 0o755) - err = os.WriteFile(filepath.Join(baseDir, protoName+".proto"), template, 0o644) + err = os.WriteFile(target, template, 0o644) if err != nil { - return err + return target, err } - return nil + return target, nil } fmt.Println(string(template)) - return nil + return target, nil } diff --git a/cmd/jzero/internal/command/add/addsql/addsql.go b/cmd/jzero/internal/command/add/addsql/addsql.go index 0d95b3f5e..4dd6320dd 100644 --- a/cmd/jzero/internal/command/add/addsql/addsql.go +++ b/cmd/jzero/internal/command/add/addsql/addsql.go @@ -12,7 +12,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/templatex" ) -func Run(args []string) error { +func Run(args []string) (string, error) { baseDir := filepath.Join("desc", "sql") sqlName := args[0] @@ -21,26 +21,28 @@ func Run(args []string) error { sqlName = strings.TrimSuffix(sqlName, ".sql") } + target := filepath.Join(baseDir, sqlName+".sql") + template, err := templatex.ParseTemplate(filepath.Join("model", "template.sql.tpl"), map[string]any{ "Name": sqlName, }, embeded.ReadTemplateFile(filepath.Join("model", "template.sql.tpl"))) if err != nil { - return err + return target, err } if config.C.Add.Output == "file" { - if filex.FileExists(filepath.Join(baseDir, sqlName+".sql")) { - return fmt.Errorf("%s already exists", sqlName) + if filex.FileExists(target) { + return target, fmt.Errorf("%s already exists", sqlName) } _ = os.MkdirAll(filepath.Dir(filepath.Join(baseDir, sqlName)), 0o755) - err = os.WriteFile(filepath.Join(baseDir, sqlName+".sql"), template, 0o644) + err = os.WriteFile(target, template, 0o644) if err != nil { - return err + return target, err } - return nil + return target, nil } fmt.Println(string(template)) - return nil + return target, nil } diff --git a/cmd/jzero/internal/command/check/check.go b/cmd/jzero/internal/command/check/check.go index 76488ddb3..5be7a99b0 100644 --- a/cmd/jzero/internal/command/check/check.go +++ b/cmd/jzero/internal/command/check/check.go @@ -25,6 +25,14 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" ) +type toolCheckSpec struct { + name string + required string + current *version.Version + ensurePath func() error + install func() error +} + var toolVersionCheck = map[string]string{ "protoc": "32.0", "goctl": "1.9.2", @@ -243,14 +251,165 @@ func installProtoc() error { return nil } +func RunCheckCommand(all bool) error { + frameType, err := desc.GetFrameType() + if err != nil { + return err + } + if frameType == "" && !all { + return nil + } + + specs := []toolCheckSpec{ + { + name: "goctl", + required: toolVersionCheck["goctl"], + current: config.C.ToolVersion().GoctlVersion, + ensurePath: func() error { + _, err := env.LookPath("goctl") + if err != nil { + return errors.New("goctl is not installed") + } + return nil + }, + install: func() error { + return golang.Install(fmt.Sprintf("github.com/zeromicro/go-zero/tools/goctl@v%s", toolVersionCheck["goctl"])) + }, + }, + } + + if frameType == "rpc" || frameType == "gateway" || all { + specs = append(specs, + toolCheckSpec{ + name: "protoc", + required: toolVersionCheck["protoc"], + current: config.C.ToolVersion().ProtocVersion, + ensurePath: func() error { + _, err := env.LookPath("protoc") + if err != nil { + return errors.New("protoc is not installed") + } + return nil + }, + install: installProtoc, + }, + toolCheckSpec{ + name: "protoc-gen-go", + required: toolVersionCheck["protoc-gen-go"], + current: config.C.ToolVersion().ProtocGenGoVersion, + ensurePath: func() error { + _, err := env.LookPath("protoc-gen-go") + if err != nil { + return errors.New("protoc-gen-go is not installed") + } + return nil + }, + install: func() error { + return golang.Install(fmt.Sprintf("google.golang.org/protobuf/cmd/protoc-gen-go@v%s", toolVersionCheck["protoc-gen-go"])) + }, + }, + toolCheckSpec{ + name: "protoc-gen-go-grpc", + required: toolVersionCheck["protoc-gen-go-grpc"], + current: config.C.ToolVersion().ProtocGenGoGrpcVersion, + ensurePath: func() error { + _, err := env.LookPath("protoc-gen-go-grpc") + if err != nil { + return errors.New("protoc-gen-go-grpc is not installed") + } + return nil + }, + install: func() error { + return golang.Install(fmt.Sprintf("google.golang.org/grpc/cmd/protoc-gen-go-grpc@v%s", toolVersionCheck["protoc-gen-go-grpc"])) + }, + }, + toolCheckSpec{ + name: "protoc-gen-openapiv2", + required: toolVersionCheck["protoc-gen-openapiv2"], + current: config.C.ToolVersion().ProtocGenOpenapiv2Version, + ensurePath: func() error { + _, err := env.LookPath("protoc-gen-openapiv2") + if err != nil { + return errors.New("protoc-gen-openapiv2 is not installed") + } + return nil + }, + install: func() error { + return golang.Install(fmt.Sprintf("github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v%s", toolVersionCheck["protoc-gen-openapiv2"])) + }, + }, + ) + } + + if config.C.Quiet { + for _, spec := range specs { + if _, err := ensureToolForCheck(spec); err != nil { + return err + } + } + return nil + } + + title := console.Green("Check") + " " + console.Yellow("tools") + fmt.Printf("%s\n", console.BoxHeader("", title)) + + for _, spec := range specs { + item, err := ensureToolForCheck(spec) + if err != nil { + fmt.Printf("%s\n", console.BoxErrorItem(spec.name)) + for _, line := range console.NormalizeErrorLines(err.Error()) { + fmt.Printf("%s\n", console.BoxDetailItem(line)) + } + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + return console.MarkRenderedError(err) + } + fmt.Printf("%s\n", console.BoxItem(item)) + } + + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) + return nil +} + +func ensureToolForCheck(spec toolCheckSpec) (string, error) { + action := "ok" + if err := spec.ensurePath(); err != nil { + action = "installed" + if err := spec.install(); err != nil { + return "", err + } + if err := spec.ensurePath(); err != nil { + return "", err + } + return fmt.Sprintf("%s (%s %s)", spec.name, action, spec.required), nil + } + + checkVersion, err := version.NewVersion(spec.required) + if err != nil { + return "", err + } + if spec.current == nil || spec.current.LessThan(checkVersion) { + action = "upgraded" + if err := spec.install(); err != nil { + return "", err + } + if err := spec.ensurePath(); err != nil { + return "", err + } + return fmt.Sprintf("%s (%s to %s)", spec.name, action, spec.required), nil + } + + return fmt.Sprintf("%s (v%s)", spec.name, spec.required), nil +} + // checkCmd represents the check command var checkCmd = &cobra.Command{ Use: "check", Short: `Check and install all needed tools`, - Run: func(cmd *cobra.Command, args []string) { - err := RunCheck(true) - cobra.CheckErr(err) + RunE: func(cmd *cobra.Command, args []string) error { + return RunCheckCommand(false) }, + SilenceUsage: true, + SilenceErrors: true, } func GetCommand() *cobra.Command { diff --git a/cmd/jzero/internal/command/format/formatgo/formatgo.go b/cmd/jzero/internal/command/format/formatgo/formatgo.go index e9bb86249..37f0850e8 100644 --- a/cmd/jzero/internal/command/format/formatgo/formatgo.go +++ b/cmd/jzero/internal/command/format/formatgo/formatgo.go @@ -6,7 +6,6 @@ import ( "regexp" "github.com/jzero-io/go_fmt/gofmtapi" - "github.com/zeromicro/go-zero/core/logx" "golang.org/x/sync/errgroup" "github.com/jzero-io/jzero/cmd/jzero/internal/config" @@ -34,7 +33,6 @@ func FormatFiles(files []string) error { if len(opt.Files) == 0 { return nil } - logx.Debugf("format files: %v", opt.Files) return gf.Execute(opt) } diff --git a/cmd/jzero/internal/command/gen/gen.go b/cmd/jzero/internal/command/gen/gen.go index caea3f98e..b04173115 100644 --- a/cmd/jzero/internal/command/gen/gen.go +++ b/cmd/jzero/internal/command/gen/gen.go @@ -17,6 +17,8 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/command/gen/genswagger" "github.com/jzero-io/jzero/cmd/jzero/internal/command/gen/genzrpcclient" "github.com/jzero-io/jzero/cmd/jzero/internal/config" + "github.com/jzero-io/jzero/cmd/jzero/internal/hooks" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/mod" ) @@ -25,9 +27,19 @@ var genCmd = &cobra.Command{ Use: "gen", Short: `Used to generate server code with api, proto, sql desc file`, RunE: func(cmd *cobra.Command, args []string) error { - return gen.Run() + err := gen.Run() + if err == nil { + return nil + } + + _ = hooks.Run(cmd, "After", "gen", config.C.Gen.Hooks.After) + if config.C.Quiet { + return err + } + return console.MarkRenderedError(err) }, - SilenceUsage: true, + SilenceUsage: true, + SilenceErrors: true, } // genZRpcClientCmd represents the rpcClient command @@ -35,6 +47,12 @@ var genZRpcClientCmd = &cobra.Command{ Use: "zrpcclient", Short: `Gen zrpc client code by proto`, RunE: func(cmd *cobra.Command, args []string) error { + hasInput, err := genzrpcclient.HasInput() + cobra.CheckErr(err) + if !hasInput { + return nil + } + wd, err := os.Getwd() cobra.CheckErr(err) mod, err := mod.GetGoMod(wd) @@ -64,6 +82,8 @@ var genZRpcClientCmd = &cobra.Command{ } return genzrpcclient.Generate(genModule) }, + SilenceUsage: true, + SilenceErrors: true, } // genSwaggerCmd represents the genSwagger command @@ -73,6 +93,8 @@ var genSwaggerCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { return genswagger.Gen() }, + SilenceUsage: true, + SilenceErrors: true, } func GetCommand() *cobra.Command { diff --git a/cmd/jzero/internal/command/gen/gen/gen.go b/cmd/jzero/internal/command/gen/gen/gen.go index fb43c2e84..1ea2ea90e 100644 --- a/cmd/jzero/internal/command/gen/gen/gen.go +++ b/cmd/jzero/internal/command/gen/gen/gen.go @@ -5,10 +5,10 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/pkg/errors" "github.com/rinchsan/gosimports" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/api/spec" rpcparser "github.com/zeromicro/go-zero/tools/goctl/rpc/parser" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" @@ -20,6 +20,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/mod" ) @@ -29,10 +30,6 @@ func Run() error { config.C.Style = config.C.Gen.Style } - if !config.C.Quiet { - fmt.Printf("%s working dir %s\n", console.Green("Enter"), config.C.Wd()) - } - var module string moduleStruct, err := mod.GetGoMod(config.C.Wd()) if err != nil { @@ -55,38 +52,154 @@ func Run() error { jzeroModel := genmodel.JzeroModel{ Module: module, } - err = jzeroModel.Gen() - if err != nil { - return err + + modelTitleFn := func() string { + modelTitle := "model" + if config.C.Gen.ModelDatasource { + dsString := config.C.Gen.ModelDatasourceUrl[0] + if len(config.C.Gen.ModelDatasourceUrl) > 1 { + dsString = fmt.Sprintf("%s...", config.C.Gen.ModelDatasourceUrl[0]) + } + modelTitle += " " + console.Cyan(fmt.Sprintf("by Datasource(%s)", dsString)) + } + return console.Green("Gen") + " " + console.Yellow(modelTitle) + } + + modelHeaderShown := config.C.Gen.GitChange && !config.C.Quiet && pathx.FileExists(config.C.SqlDir()) + + // Show box header immediately for git-change mode only if sql dir exists + if modelHeaderShown { + modelTitle := "model" + if config.C.Gen.ModelDatasource { + modelTitle += " " + console.Cyan(fmt.Sprintf("by Datasource(%s)", strings.Join(config.C.Gen.ModelDatasourceUrl, ","))) + } + title := console.Green("Gen") + " " + console.Yellow(modelTitle) + " " + console.Cyan("(git-change mode)") + fmt.Printf("%s\n", console.BoxHeader("", title)) + } + + // Generate model + progressChan := make(chan progress.Message, 10) + done := make(chan struct{}) + var modelErr error + go func() { + modelFiles, genErr := jzeroModel.Gen(progressChan) + if genErr != nil { + modelErr = genErr + } + _ = modelFiles + close(done) + }() + + modelState := progress.ConsumeStage(progressChan, done, modelTitleFn(), config.C.Quiet, modelHeaderShown) + progress.FinishStage(modelTitleFn(), config.C.Quiet, &modelState, modelErr) + + if modelErr != nil { + return modelErr } + var apiSpecMap map[string]*spec.ApiSpec + var protoSpecMap map[string]*rpcparser.Proto + jzeroApi := genapi.JzeroApi{ Module: module, } - apiSpecMap, err := jzeroApi.Gen() - if err != nil { - return err + + apiTitleFn := func() string { + title := console.Green("Gen") + " " + console.Yellow("api") + if config.C.Gen.GitChange { + title += " " + console.Cyan("(git-change mode)") + } + return title + } + + apiHeaderShown := !config.C.Quiet && pathx.FileExists(config.C.ApiDir()) + + // Generate api + apiProgressChan := make(chan progress.Message, 10) + apiDone := make(chan struct{}) + var apiErr error + + // Show box header immediately before starting goroutine + if apiHeaderShown { + fmt.Printf("%s\n", console.BoxHeader("", apiTitleFn())) + } + + go func() { + apiSpecMap, apiErr = jzeroApi.Gen(apiProgressChan) + close(apiDone) + }() + + apiState := progress.ConsumeStage(apiProgressChan, apiDone, apiTitleFn(), config.C.Quiet, apiHeaderShown) + progress.FinishStage(apiTitleFn(), config.C.Quiet, &apiState, apiErr) + + if apiErr != nil { + return apiErr } jzeroRpc := genrpc.JzeroRpc{ Module: module, } - protoSpecMap, err := jzeroRpc.Gen() - if err != nil { - return err + + rpcTitleFn := func() string { + title := console.Green("Gen") + " " + console.Yellow("rpc") + if config.C.Gen.GitChange { + title += " " + console.Cyan("(git-change mode)") + } + return title + } + + rpcHeaderShown := config.C.Gen.GitChange && !config.C.Quiet && pathx.FileExists(config.C.ProtoDir()) + + // Show box header immediately for git-change mode + if rpcHeaderShown { + fmt.Printf("%s\n", console.BoxHeader("", rpcTitleFn())) + } + + // Generate rpc + rpcProgressChan := make(chan progress.Message, 10) + rpcDone := make(chan struct{}) + var rpcErr error + go func() { + protoSpecMap, rpcErr = jzeroRpc.Gen(rpcProgressChan) + close(rpcProgressChan) + close(rpcDone) + }() + + rpcState := progress.ConsumeStage(rpcProgressChan, rpcDone, rpcTitleFn(), config.C.Quiet, rpcHeaderShown) + progress.FinishStage(rpcTitleFn(), config.C.Quiet, &rpcState, rpcErr) + + if rpcErr != nil { + return rpcErr } jzeroMongo := genmongo.JzeroMongo{ Module: module, } - err = jzeroMongo.Gen() - if err != nil { - return err + + // Only show mongo box if there are mongo types to generate + var mongoShown bool + if len(config.C.Gen.MongoType) > 0 { + if !config.C.Quiet { + title := console.Green("Gen") + " " + console.Yellow("mongo") + fmt.Printf("%s\n", console.BoxHeader("", title)) + mongoShown = true + } + err = jzeroMongo.Gen() + if !config.C.Quiet && mongoShown { + if err != nil { + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + } else { + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) + } + } + if err != nil { + return err + } } // 收集并保存元数据(复用已解析的数据) if err = collectAndSaveMetadata(apiSpecMap, protoSpecMap); err != nil { - logx.Debugf("collect and save metadata error: %s", err.Error()) + // Debug removed("collect and save metadata error: %s", err.Error()) } return nil @@ -129,14 +242,10 @@ func RemoveExtraFiles(wd, style string) { if err == nil { for _, v := range apiFilenames { if desc.GetApiFrameMainGoFilename(wd, v, style) != "main.go" { - if err := os.Remove(filepath.Join(wd, desc.GetApiFrameMainGoFilename(wd, v, style))); err != nil && !errors.Is(err, os.ErrNotExist) { - logx.Debugf("remove api frame main go file error: %s", err.Error()) - } + _ = os.Remove(filepath.Join(wd, desc.GetApiFrameMainGoFilename(wd, v, style))) } if desc.GetApiFrameEtcFilename(wd, v, style) != "etc.yaml" { - if err := os.Remove(filepath.Join(wd, "etc", desc.GetApiFrameEtcFilename(wd, v, style))); err != nil && !errors.Is(err, os.ErrNotExist) { - logx.Debugf("remove api etc file error: %s", err.Error()) - } + _ = os.Remove(filepath.Join(wd, "etc", desc.GetApiFrameEtcFilename(wd, v, style))) } } } @@ -149,14 +258,10 @@ func RemoveExtraFiles(wd, style string) { v = filepath.Base(v) fileBase := v[0 : len(v)-len(path.Ext(v))] if desc.GetProtoFrameMainGoFilename(fileBase, style) != "main.go" { - if err = os.Remove(filepath.Join(wd, desc.GetProtoFrameMainGoFilename(fileBase, style))); err != nil && !errors.Is(err, os.ErrNotExist) { - logx.Debugf("remove proto frame main go file error: %s", err.Error()) - } + _ = os.Remove(filepath.Join(wd, desc.GetProtoFrameMainGoFilename(fileBase, style))) } if desc.GetProtoFrameEtcFilename(fileBase, style) != "etc.yaml" { - if err = os.Remove(filepath.Join(wd, "etc", desc.GetProtoFrameEtcFilename(fileBase, style))); err != nil && !errors.Is(err, os.ErrNotExist) { - logx.Debugf("remove proto etc file error: %s", err.Error()) - } + _ = os.Remove(filepath.Join(wd, "etc", desc.GetProtoFrameEtcFilename(fileBase, style))) } } } @@ -168,8 +273,8 @@ func RemoveExtraFiles(wd, style string) { entries, err := os.ReadDir(etcDir) if err == nil && len(entries) == 0 { if err := os.Remove(etcDir); err != nil && !errors.Is(err, os.ErrNotExist) { - logx.Debugf("remove empty etc directory error: %s", err.Error()) } + } } } diff --git a/cmd/jzero/internal/command/gen/genapi/genapi.go b/cmd/jzero/internal/command/gen/genapi/genapi.go index c95ab31f2..8233202ff 100644 --- a/cmd/jzero/internal/command/gen/genapi/genapi.go +++ b/cmd/jzero/internal/command/gen/genapi/genapi.go @@ -11,7 +11,6 @@ import ( "github.com/rinchsan/gosimports" "github.com/samber/lo" "github.com/spf13/cast" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/api/format" "github.com/zeromicro/go-zero/tools/goctl/api/spec" "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser" @@ -22,7 +21,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" - "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/filex" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/gitstatus" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/osx" @@ -47,22 +46,14 @@ func (l RegisterLines) String() string { return "\n\t\t" + strings.Join(l, "\n\t\t") } -func (ja *JzeroApi) Gen() (map[string]*spec.ApiSpec, error) { +func (ja *JzeroApi) Gen(progressChan chan<- progress.Message) (map[string]*spec.ApiSpec, error) { if !pathx.FileExists(config.C.ApiDir()) { return nil, nil } - if !config.C.Quiet { - msg := "to generate api code from api files" - if config.C.Gen.GitChange && gitstatus.IsGitRepo(filepath.Join(config.C.Wd())) && len(config.C.Gen.Desc) == 0 { - msg += " (git-change mode)" - } - fmt.Printf("%s %s\n", console.Green("Start"), msg) - } - apiFiles, err := desc.FindRouteApiFiles(config.C.ApiDir()) if err != nil { - return nil, err + return nil, errors.Wrap(err, "find route api files") } apiSpecMap := make(map[string]*spec.ApiSpec, len(apiFiles)) @@ -175,7 +166,7 @@ func (ja *JzeroApi) Gen() (map[string]*spec.ApiSpec, error) { return apiSpecMap, nil } - err = ja.generateApiCode(apiFiles, apiSpecMap, genCodeApiFiles, genCodeApiSpecMap, currentRoutesMap, importedFiles) + err = ja.generateApiCode(apiFiles, apiSpecMap, genCodeApiFiles, genCodeApiSpecMap, currentRoutesMap, importedFiles, progressChan) if err != nil { return nil, err } @@ -186,13 +177,10 @@ func (ja *JzeroApi) Gen() (map[string]*spec.ApiSpec, error) { return nil, err } - if !config.C.Quiet { - fmt.Println(console.Green("Gen Api Done")) - } return apiSpecMap, nil } -func (ja *JzeroApi) generateApiCode(apiFiles []string, apiSpecMap map[string]*spec.ApiSpec, genCodeApiFiles []string, genCodeApiSpecMap map[string]*spec.ApiSpec, currentRoutesMap map[string][]spec.Route, importedFiles map[string]bool) error { +func (ja *JzeroApi) generateApiCode(apiFiles []string, apiSpecMap map[string]*spec.ApiSpec, genCodeApiFiles []string, genCodeApiSpecMap map[string]*spec.ApiSpec, currentRoutesMap map[string][]spec.Route, importedFiles map[string]bool, progressChan chan<- progress.Message) error { if err := ja.cleanHandlersDir(genCodeApiFiles, genCodeApiSpecMap); err != nil { return err } @@ -208,7 +196,7 @@ func (ja *JzeroApi) generateApiCode(apiFiles []string, apiSpecMap map[string]*sp return err } - if err := ja.generateCodeForApiFiles(genCodeApiFiles, apiSpecMap, importedFiles, templateDir); err != nil { + if err := ja.generateCodeForApiFiles(genCodeApiFiles, apiSpecMap, importedFiles, templateDir, progressChan); err != nil { return err } @@ -284,7 +272,6 @@ func (ja *JzeroApi) prepareTemplateDir() (string, error) { } } - logx.Debugf("goctl_home = %s", tempDir) return tempDir, nil } @@ -329,9 +316,10 @@ func (ja *JzeroApi) collectRoutesGoBody(apiFiles []string, apiSpecMap map[string } // generateCodeForApiFiles 为所有 API 文件生成代码 -func (ja *JzeroApi) generateCodeForApiFiles(genCodeApiFiles []string, apiSpecMap map[string]*spec.ApiSpec, importedFiles map[string]bool, templateDir string) error { - // 按 group 分组,同一 group 的文件串行处理,不同 group 并发处理 - groupToFiles := make(map[string][]string) +func (ja *JzeroApi) generateCodeForApiFiles(genCodeApiFiles []string, apiSpecMap map[string]*spec.ApiSpec, importedFiles map[string]bool, templateDir string, progressChan chan<- progress.Message) error { + // goctl api go 内部会并发执行 backup/sweep,多个进程并发跑时会踩到共享状态, + // 导致概率性 panic。这里按文件串行生成,避免同目录并发调用 goctl。 + var filesToGenerate []string for _, apiFile := range genCodeApiFiles { if importedFiles[apiFile] { continue @@ -339,52 +327,29 @@ func (ja *JzeroApi) generateCodeForApiFiles(genCodeApiFiles []string, apiSpecMap if len(apiSpecMap[apiFile].Service.Routes()) == 0 { continue } + filesToGenerate = append(filesToGenerate, apiFile) + } - // 收集该文件的所有 group - groups := make(map[string]struct{}) - for _, g := range apiSpecMap[apiFile].Service.Groups { - if groupAnnotation := g.GetAnnotation("group"); groupAnnotation != "" { - groups[groupAnnotation] = struct{}{} - } + for _, apiFile := range filesToGenerate { + if err := format.ApiFormatByPath(apiFile, false); err != nil { + return errors.Wrapf(err, "format api file: %s", apiFile) } - // 如果没有 group,使用默认分组 - if len(groups) == 0 { - groupToFiles[""] = append(groupToFiles[""], apiFile) - } else { - for group := range groups { - groupToFiles[group] = append(groupToFiles[group], apiFile) - } + command := fmt.Sprintf("goctl api go --api %s --dir %s --home %s --style %s", apiFile, ".", templateDir, config.C.Style) + if config.C.Debug { + progressChan <- progress.NewDebug(command) } - } - - // 并发处理不同 group - var eg errgroup.Group - for _, files := range groupToFiles { - currentFiles := files - eg.Go(func() error { - // 同一 group 内的文件串行处理 - for _, apiFile := range currentFiles { - if !config.C.Quiet { - fmt.Printf("%s api file %s \n", console.Green("Using"), apiFile) - } - if err := format.ApiFormatByPath(apiFile, false); err != nil { - return errors.Wrapf(err, "format api file: %s", apiFile) - } - - command := fmt.Sprintf("goctl api go --api %s --dir %s --home %s --style %s", apiFile, ".", templateDir, config.C.Style) - logx.Debugf("command: %s", command) + if _, err := execx.Run(command, config.C.Wd()); err != nil { + return errors.Wrapf(err, "api file: %s", apiFile) + } - if _, err := execx.Run(command, config.C.Wd()); err != nil { - return errors.Wrapf(err, "api file: %s", apiFile) - } - } - return nil - }) + if progressChan != nil { + progressChan <- progress.NewFile(apiFile) + } } - return eg.Wait() + return nil } // patchHandlerAndLogicFiles 并发 patch handler 和 logic 文件 @@ -476,10 +441,6 @@ func (ja *JzeroApi) generateRoutesGoFile(apiFiles []string, apiSpecMap map[strin // generateRoute2CodeFile 生成 route2code.go 文件 func (ja *JzeroApi) generateRoute2CodeFile(apiSpecMap map[string]*spec.ApiSpec, currentRoutesMap map[string][]spec.Route, importedFiles map[string]bool) error { - if !config.C.Quiet { - fmt.Printf("%s to generate internal/handler/route2code.go\n", console.Green("Start")) - } - route2CodeBytes, err := ja.genRoute2Code(apiSpecMap, currentRoutesMap, importedFiles) if err != nil { return err @@ -489,9 +450,5 @@ func (ja *JzeroApi) generateRoute2CodeFile(apiSpecMap map[string]*spec.ApiSpec, return err } - if !config.C.Quiet { - fmt.Printf("%s", console.Green("Gen Route2code Done\n")) - } - return nil } diff --git a/cmd/jzero/internal/command/gen/genapi/handler.go b/cmd/jzero/internal/command/gen/genapi/handler.go index 2fde11b6d..cf9e8a833 100644 --- a/cmd/jzero/internal/command/gen/genapi/handler.go +++ b/cmd/jzero/internal/command/gen/genapi/handler.go @@ -16,7 +16,6 @@ import ( "github.com/dave/dst/decorator" "github.com/rinchsan/gosimports" "github.com/spf13/cast" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/api/spec" "github.com/zeromicro/go-zero/tools/goctl/pkg/golang" "github.com/zeromicro/go-zero/tools/goctl/util" @@ -259,7 +258,7 @@ func (ja *JzeroApi) compactHandler(f *dst.File, fset *token.FileSet, file Handle return err } } - logx.Debugf("remove old handler file: %s", file.Path) + // Debug removed("remove old handler file: %s", file.Path) if err = os.Remove(file.Path); err != nil { return err } diff --git a/cmd/jzero/internal/command/gen/genmodel/genmodel.go b/cmd/jzero/internal/command/gen/genmodel/genmodel.go index 0294e8b2b..55cfe90d8 100644 --- a/cmd/jzero/internal/command/gen/genmodel/genmodel.go +++ b/cmd/jzero/internal/command/gen/genmodel/genmodel.go @@ -13,7 +13,6 @@ import ( "github.com/pkg/errors" "github.com/samber/lo" ddlparser "github.com/zeromicro/ddl-parser/parser" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/postgres" "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" @@ -22,7 +21,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" jzerodesc "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" - "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/dsn" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/filex" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/gitstatus" @@ -38,16 +37,17 @@ type Conn struct { SqlConn sqlx.SqlConn } -func (jm *JzeroModel) Gen() error { +func (jm *JzeroModel) Gen(progressChan chan<- progress.Message) ([]string, error) { var ( allTables []string err error genCodeTables []string conns []Conn + genFiles []string ) if !pathx.FileExists(config.C.SqlDir()) && !config.C.Gen.ModelDatasource { - return nil + return nil, nil } if config.C.Gen.ModelDriver == "postgres" { @@ -55,7 +55,7 @@ func (jm *JzeroModel) Gen() error { } if config.C.Gen.ModelDriver == "pgx" && !config.C.Gen.ModelDatasource { - return errors.New("postgres model only support datasource mode") + return nil, errors.New("postgres model only support datasource mode") } if config.C.Gen.ModelDatasource { @@ -64,7 +64,7 @@ func (jm *JzeroModel) Gen() error { for _, v := range config.C.Gen.ModelDatasourceUrl { meta, err := dsn.ParseDSN(config.C.Gen.ModelDriver, v) if err != nil { - return err + return nil, err } conns = append(conns, Conn{ Schema: meta[dsn.Database], @@ -75,7 +75,7 @@ func (jm *JzeroModel) Gen() error { for _, v := range config.C.Gen.ModelDatasourceUrl { meta, err := dsn.ParseDSN(config.C.Gen.ModelDriver, v) if err != nil { - return err + return nil, err } conns = append(conns, Conn{ Schema: meta[dsn.Database], @@ -83,19 +83,7 @@ func (jm *JzeroModel) Gen() error { }) } default: - return errors.Errorf("model driver %s not support", config.C.Gen.ModelDriver) - } - - if !config.C.Quiet { - fmt.Printf("%s to generate model from %s\n", console.Green("Start"), config.C.Gen.ModelDatasourceUrl) - } - } else { - if !config.C.Quiet { - msg := "to generate model code from sql files" - if config.C.Gen.GitChange && gitstatus.IsGitRepo(filepath.Join(config.C.Wd())) && len(config.C.Gen.Desc) == 0 { - msg += " (git-change mode)" - } - fmt.Printf("%s %s\n", console.Green("Start"), msg) + return nil, errors.Errorf("model driver %s not support", config.C.Gen.ModelDriver) } } @@ -103,14 +91,14 @@ func (jm *JzeroModel) Gen() error { var goctlHome string tempDir, err := os.MkdirTemp(os.TempDir(), "") if err != nil { - return err + return nil, err } defer os.RemoveAll(tempDir) // 先写入内置模板 err = embeded.WriteTemplateDir(filepath.Join("go-zero", "model"), filepath.Join(tempDir, "model")) if err != nil { - return err + return nil, err } // 如果用户自定义了模板,则复制覆盖 @@ -118,12 +106,11 @@ func (jm *JzeroModel) Gen() error { if pathx.FileExists(customTemplatePath) { err = filex.CopyDir(customTemplatePath, filepath.Join(tempDir, "model")) if err != nil { - return err + return nil, err } } goctlHome = tempDir - logx.Debugf("goctl_home = %s", goctlHome) var ( sqlFiles []string @@ -134,7 +121,7 @@ func (jm *JzeroModel) Gen() error { if !config.C.Gen.ModelDatasource { sqlFiles, err = jzerodesc.FindSqlFiles(config.C.SqlDir()) if err != nil { - return err + return nil, err } switch { @@ -152,7 +139,7 @@ func (jm *JzeroModel) Gen() error { } else { specifiedSqlFiles, err := jzerodesc.FindSqlFiles(v) if err != nil { - return err + return nil, err } genCodeSqlFiles = append(genCodeSqlFiles, specifiedSqlFiles...) } @@ -160,7 +147,7 @@ func (jm *JzeroModel) Gen() error { default: genCodeSqlFiles, err = jzerodesc.FindSqlFiles(config.C.SqlDir()) if err != nil { - return err + return nil, err } } @@ -178,7 +165,7 @@ func (jm *JzeroModel) Gen() error { } else { specifiedSqlFiles, err := jzerodesc.FindSqlFiles(v) if err != nil { - return err + return nil, err } for _, saf := range specifiedSqlFiles { genCodeSqlFiles = lo.Reject(genCodeSqlFiles, func(item string, _ int) bool { @@ -198,7 +185,7 @@ func (jm *JzeroModel) Gen() error { if len(config.C.Gen.ModelDatasourceTable) == 1 && config.C.Gen.ModelDatasourceTable[0] == "*" { allTables, err = getAllTables(conns, config.C.Gen.ModelDriver) if err != nil { - return err + return nil, err } } else { allTables = config.C.Gen.ModelDatasourceTable @@ -208,13 +195,22 @@ func (jm *JzeroModel) Gen() error { eg.SetLimit(len(allTables)) for _, tableName := range allTables { eg.Go(func() error { + if progressChan != nil { + progressChan <- progress.NewFile(tableName) + } return generateModelFromDatasource(tableName, goctlHome) }) } if err = eg.Wait(); err != nil { - return err + return nil, err + } + err = jm.GenRegister(allTables) + if err != nil { + return nil, err } - return jm.GenRegister(allTables) + // Add table names to genFiles for datasource mode + genFiles = append(genFiles, allTables...) + return genFiles, nil } else if len(genCodeSqlFiles) != 0 { var eg errgroup.Group for _, f := range sqlFiles { @@ -240,10 +236,10 @@ func (jm *JzeroModel) Gen() error { }) } if err = eg.Wait(); err != nil { - return err + return nil, err } } else { - return nil + return nil, nil } var eg errgroup.Group @@ -252,24 +248,32 @@ func (jm *JzeroModel) Gen() error { eg.Go(func() error { tableParser := genCodeSqlSpecMap[f] genCodeTables = append(genCodeTables, tableParser.Name) + if progressChan != nil { + displayName := fmt.Sprintf("%s (%s)", f, tableParser.Name) + progressChan <- progress.NewFile(displayName) + } return generateModelFromSqlFile(f, goctlHome) }) } if err = eg.Wait(); err != nil { - return err + return nil, err } + // Add SQL files to genFiles + genFiles = append(genFiles, genCodeSqlFiles...) + err = jm.GenRegister(allTables) if err != nil { - return err + return nil, err } - if !config.C.Quiet { - fmt.Println(console.Green("Gen Model Done")) + // Close progress channel to signal completion + if progressChan != nil { + close(progressChan) } - return nil + return genFiles, nil } func getAllTables(conns []Conn, driver string) ([]string, error) { @@ -335,10 +339,6 @@ func getIgnoreColumns(tableName string) []string { } func generateModelFromDatasource(tableName, goctlHome string) error { - if !config.C.Quiet { - fmt.Printf("%s table %s from datasource\n", console.Green("Generating"), tableName) - } - bf := tableName if strings.Contains(tableName, ".") { bf = strings.Split(tableName, ".")[1] @@ -377,7 +377,7 @@ func generateModelFromDatasource(tableName, goctlHome string) error { } cmd := exec.Command("goctl", "model", "pg", "datasource", "--url", datasourceUrl, "--schema", schema, "-t", bf, "--dir", modelDir, "--home", goctlHome, "--style", config.C.Style, "-i", strings.Join(getIgnoreColumns(bf), ","), "--cache="+fmt.Sprintf("%t", getIsCacheTable(bf)), "-p", config.C.Gen.ModelCachePrefix, "--strict="+fmt.Sprintf("%t", config.C.Gen.ModelStrict)) - logx.Debug(cmd.String()) + // Debug removed(cmd.String()) resp, err := cmd.CombinedOutput() if err != nil { return errors.Errorf("gen model code meet error. Err: %s:%s", err.Error(), resp) @@ -400,7 +400,7 @@ func generateModelFromDatasource(tableName, goctlHome string) error { } cmd := exec.Command("goctl", "model", "mysql", "datasource", "--url", datasourceUrl, "-t", bf, "--dir", modelDir, "--home", goctlHome, "--style", config.C.Style, "-i", strings.Join(getIgnoreColumns(bf), ","), "--cache="+fmt.Sprintf("%t", getIsCacheTable(bf)), "-p", config.C.Gen.ModelCachePrefix, "--strict="+fmt.Sprintf("%t", config.C.Gen.ModelStrict)) - logx.Debug(cmd.String()) + // Debug removed(cmd.String()) resp, err := cmd.CombinedOutput() if err != nil { return errors.Errorf("gen model code meet error. Err: %s:%s", err.Error(), resp) @@ -411,10 +411,6 @@ func generateModelFromDatasource(tableName, goctlHome string) error { } func generateModelFromSqlFile(sqlFile, goctlHome string) error { - if !config.C.Quiet { - fmt.Printf("%s sql file %s\n", console.Green("Using"), sqlFile) - } - bf := strings.TrimSuffix(filepath.Base(sqlFile), ".sql") var ( @@ -449,7 +445,7 @@ func generateModelFromSqlFile(sqlFile, goctlHome string) error { } cmd := exec.Command("goctl", "model", "mysql", "ddl", "--database", schema, "--src", sqlFile, "--dir", modelDir, "--home", goctlHome, "--style", config.C.Style, "-i", strings.Join(getIgnoreColumns(bf), ","), "--cache="+fmt.Sprintf("%t", getIsCacheTable(bf)), "-p", config.C.Gen.ModelCachePrefix, "--strict="+fmt.Sprintf("%t", config.C.Gen.ModelStrict)) - logx.Debug(cmd.String()) + // Debug removed(cmd.String()) resp, err := cmd.CombinedOutput() if err != nil { return errors.Errorf("gen model code meet error. Err: %s:%s", err.Error(), resp) diff --git a/cmd/jzero/internal/command/gen/genmodel/plugins.go b/cmd/jzero/internal/command/gen/genmodel/plugins.go index d3cf7fceb..ab8c27ca2 100644 --- a/cmd/jzero/internal/command/gen/genmodel/plugins.go +++ b/cmd/jzero/internal/command/gen/genmodel/plugins.go @@ -9,7 +9,6 @@ import ( "github.com/rinchsan/gosimports" "github.com/samber/lo" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/jzero-io/jzero/cmd/jzero/internal/config" @@ -32,7 +31,7 @@ type TableInfo struct { } func (jm *JzeroModel) GenRegister(tables []string) error { - logx.Debugf("get register tables: %v", tables) + // Debug removed("get register tables: %v", tables) slices.Sort(tables) @@ -53,7 +52,7 @@ func (jm *JzeroModel) GenRegister(tables []string) error { } mf := filepath.Join("internal", "model", strings.ToLower(tf)) if !pathx.FileExists(mf) { - logx.Debugf("%s table generated code not exists, skip", tf) + // Debug removed("%s table generated code not exists, skip", tf) continue } @@ -86,9 +85,9 @@ func (jm *JzeroModel) GenRegister(tables []string) error { } } - logx.Debugf("get register imports: %v", imports) - logx.Debugf("get register table packages: %v", tablePackages) - logx.Debugf("get register muti models: %v", mutiModels) + // Debug removed("get register imports: %v", imports) + // Debug removed("get register table packages: %v", tablePackages) + // Debug removed("get register muti models: %v", mutiModels) // Build cache expiry table maps - only when cache is enabled var modelExpiryTable map[string]int64 diff --git a/cmd/jzero/internal/command/gen/genmongo/genmongo.go b/cmd/jzero/internal/command/gen/genmongo/genmongo.go index 117285568..1cb438269 100644 --- a/cmd/jzero/internal/command/gen/genmongo/genmongo.go +++ b/cmd/jzero/internal/command/gen/genmongo/genmongo.go @@ -8,12 +8,10 @@ import ( "strings" "github.com/pkg/errors" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" - "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" ) type JzeroMongo struct { @@ -41,17 +39,8 @@ func (jm *JzeroMongo) Gen() error { } else { goctlHome = filepath.Join(config.C.Home, "go-zero") } - logx.Debugf("goctl_home = %s", goctlHome) - - if !config.C.Quiet { - fmt.Printf("%s to generate mongo model code from types.\n", console.Green("Start")) - } for _, mongoType := range config.C.Gen.MongoType { - if !config.C.Quiet { - fmt.Printf("%s mongo type %s\n", console.Green("Using"), mongoType) - } - // Support MutiModel with dot notation like "ntls_log.user" var typeName string if strings.Contains(mongoType, ".") { @@ -105,7 +94,6 @@ func (jm *JzeroMongo) Gen() error { args = append(args, fmt.Sprintf("--easy=%v", true)) cmd := exec.Command("goctl", args...) - logx.Debug(cmd.String()) resp, err := cmd.CombinedOutput() if err != nil { return errors.Errorf("gen mongo model code meet error. Err: %s:%s", err.Error(), resp) @@ -117,9 +105,5 @@ func (jm *JzeroMongo) Gen() error { return err } - if !config.C.Quiet { - fmt.Println(console.Green("Gen Mongo Done")) - } - return nil } diff --git a/cmd/jzero/internal/command/gen/genmongo/plugins.go b/cmd/jzero/internal/command/gen/genmongo/plugins.go index 3784bb490..7aaf45549 100644 --- a/cmd/jzero/internal/command/gen/genmongo/plugins.go +++ b/cmd/jzero/internal/command/gen/genmongo/plugins.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/rinchsan/gosimports" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" @@ -26,7 +25,7 @@ type NameWithAlias struct { } func (jm *JzeroMongo) GenRegister(types []string) error { - logx.Debugf("get register tables: %v", types) + // Debug removed("get register tables: %v", types) slices.Sort(types) @@ -46,7 +45,7 @@ func (jm *JzeroMongo) GenRegister(types []string) error { } mf := filepath.Join("internal", "mongo", strings.ToLower(tf)) if !pathx.FileExists(mf) { - logx.Debugf("%s mongo model generated code not exists, skip", t) + // Debug removed("%s mongo model generated code not exists, skip", t) continue } @@ -71,9 +70,9 @@ func (jm *JzeroMongo) GenRegister(types []string) error { } } - logx.Debugf("get register imports: %v", imports) - logx.Debugf("get register types packages: %v", typePackages) - logx.Debugf("get register muti models: %v", mutiModels) + // Debug removed("get register imports: %v", imports) + // Debug removed("get register types packages: %v", typePackages) + // Debug removed("get register muti models: %v", mutiModels) template, err := templatex.ParseTemplate(filepath.Join("mongo", "model.go.tpl"), map[string]any{ "Imports": imports, diff --git a/cmd/jzero/internal/command/gen/genrpc/genrpc.go b/cmd/jzero/internal/command/gen/genrpc/genrpc.go index b9840846a..5d870aa78 100644 --- a/cmd/jzero/internal/command/gen/genrpc/genrpc.go +++ b/cmd/jzero/internal/command/gen/genrpc/genrpc.go @@ -8,7 +8,6 @@ import ( "github.com/jhump/protoreflect/desc/protoparse" "github.com/samber/lo" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" rpcparser "github.com/zeromicro/go-zero/tools/goctl/rpc/parser" goctlconsole "github.com/zeromicro/go-zero/tools/goctl/util/console" @@ -16,7 +15,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" jzerodesc "github.com/jzero-io/jzero/cmd/jzero/internal/desc" - "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/filex" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/gitstatus" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/osx" @@ -41,7 +40,7 @@ func (l RegisterLines) String() string { return "\n\t\t" + strings.Join(l, "\n\t\t") } -func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { +func (jr *JzeroRpc) Gen(progressChan chan<- progress.Message) (map[string]*rpcparser.Proto, error) { var ( serverImports ImportLines pbImports ImportLines @@ -58,14 +57,6 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { return nil, nil } - if !config.C.Quiet { - msg := "to generate rpc code from proto files" - if config.C.Gen.GitChange && gitstatus.IsGitRepo(filepath.Join(config.C.Wd())) && len(config.C.Gen.Desc) == 0 { - msg += " (git-change mode)" - } - fmt.Printf("%s %s\n", console.Green("Start"), msg) - } - protoSpecMap := make(map[string]*rpcparser.Proto, len(protoFiles)) for _, v := range protoFiles { // parse proto @@ -176,13 +167,11 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { } goctlHome = tempDir - logx.Debugf("goctl_home = %s", goctlHome) excludeThirdPartyProtoFiles, err := jzerodesc.FindExcludeThirdPartyProtoFiles(config.C.ProtoDir()) if err != nil { return nil, err } - logx.Debugf("excludeThirdPartyProtoFiles = %s", excludeThirdPartyProtoFiles) // 获取 proto 的 go_package var protoParser protoparse.Parser @@ -208,9 +197,6 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { } if lo.Contains(genCodeProtoFiles, v) { - if !config.C.Quiet { - fmt.Printf("%s proto file %s\n", console.Green("Using"), v) - } zrpcOut := "." rel, err := filepath.Rel(protoDir, v) @@ -280,12 +266,18 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { command += fmt.Sprintf(" -I%s ", strings.Join(config.C.Gen.ProtoInclude, " -I")) } - logx.Debug(command) - _, err = execx.Run(command, config.C.Wd()) if err != nil { return nil, err } + + // Send proto file progress + if progressChan != nil { + progressChan <- progress.NewFile(v) + if config.C.Debug { + progressChan <- progress.NewDebug(command) + } + } } for _, file := range allServerFiles { @@ -332,7 +324,6 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { if len(config.C.Gen.ProtoInclude) > 0 { protocCommand += fmt.Sprintf(" -I%s", strings.Join(config.C.Gen.ProtoInclude, " -I")) } - logx.Debug(protocCommand) _, err = execx.Run(protocCommand, config.C.Wd()) if err != nil { return nil, err @@ -347,12 +338,6 @@ func (jr *JzeroRpc) Gen() (map[string]*rpcparser.Proto, error) { pbImports = append(pbImports, fmt.Sprintf(`"%s/internal/%s"`, jr.Module, strings.TrimPrefix(protoSpecMap[v].GoPackage, "./"))) } - if len(genCodeProtoFiles) > 0 { - if !config.C.Quiet { - fmt.Println(console.Green("Gen Rpc Done")) - } - } - if pathx.FileExists(config.C.ProtoDir()) { if err = jr.genServer(serverImports, pbImports, registerServers); err != nil { return nil, err diff --git a/cmd/jzero/internal/command/gen/genrpc/middleware.go b/cmd/jzero/internal/command/gen/genrpc/middleware.go index c7b5bdd02..a4e2050f3 100644 --- a/cmd/jzero/internal/command/gen/genrpc/middleware.go +++ b/cmd/jzero/internal/command/gen/genrpc/middleware.go @@ -147,10 +147,6 @@ func (jr *JzeroRpc) genApiMiddlewares(protoFiles []string) (err error) { return nil } - if !config.C.Quiet { - fmt.Printf("%s to generate internal/middleware/middleware_gen.go\n", console.Green("Start")) - } - for _, v := range httpMiddlewares { template, err := templatex.ParseTemplate(filepath.Join("rpc", "middleware_http.go.tpl"), map[string]any{ "Name": v.Name, diff --git a/cmd/jzero/internal/command/gen/genrpc/pb.go b/cmd/jzero/internal/command/gen/genrpc/pb.go index 3130261ea..486682e7f 100644 --- a/cmd/jzero/internal/command/gen/genrpc/pb.go +++ b/cmd/jzero/internal/command/gen/genrpc/pb.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/jhump/protoreflect/desc/protoparse" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" "github.com/jzero-io/jzero/cmd/jzero/internal/config" @@ -61,7 +60,7 @@ func (jr *JzeroRpc) genNoRpcServiceExcludeThirdPartyProto(protoDirPath string) e filepath.Join("."), jr.Module) - logx.Debug(command) + // Debug removed(command) _, err = execx.Run(command, config.C.Wd()) if err != nil { diff --git a/cmd/jzero/internal/command/gen/genrpc/server.go b/cmd/jzero/internal/command/gen/genrpc/server.go index 806f9d4a4..5b6352885 100644 --- a/cmd/jzero/internal/command/gen/genrpc/server.go +++ b/cmd/jzero/internal/command/gen/genrpc/server.go @@ -2,7 +2,6 @@ package genrpc import ( "bytes" - "fmt" "go/ast" goformat "go/format" goparser "go/parser" @@ -17,7 +16,6 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" - "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/templatex" ) @@ -28,9 +26,6 @@ type ServerFile struct { } func (jr *JzeroRpc) genServer(serverImports, pbImports ImportLines, registerServers RegisterLines) error { - if !config.C.Quiet { - fmt.Printf("%s to generate internal/server/server.go\n", console.Green("Start")) - } serverFile, err := templatex.ParseTemplate(filepath.Join("rpc", "server.go.tpl"), map[string]any{ "Module": jr.Module, "ServerImports": serverImports, @@ -44,9 +39,6 @@ func (jr *JzeroRpc) genServer(serverImports, pbImports ImportLines, registerServ if err != nil { return err } - if !config.C.Quiet { - fmt.Printf("%s", console.Green("Gen Server Done\n")) - } return nil } diff --git a/cmd/jzero/internal/command/gen/genswagger/genswagger.go b/cmd/jzero/internal/command/gen/genswagger/genswagger.go index 0f4f08056..abc574ff1 100644 --- a/cmd/jzero/internal/command/gen/genswagger/genswagger.go +++ b/cmd/jzero/internal/command/gen/genswagger/genswagger.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" "github.com/samber/lo" "github.com/spf13/cast" - "github.com/zeromicro/go-zero/core/logx" apiparser "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" @@ -21,444 +20,604 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/osx" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/stringx" "github.com/jzero-io/jzero/cmd/jzero/internal/serverless" ) func Gen() (err error) { - if pathx.FileExists(config.C.ApiDir()) { - _ = os.MkdirAll(config.C.Gen.Swagger.Output, 0o755) + showProgress := !config.C.Quiet + hasSwaggerInput := hasSwaggerSourceInput() + + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("swagger api"), + false, + showProgress, + runRegularAPISwagger, + ); err != nil { + return err + } + + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("swagger plugin api"), + false, + showProgress, + runPluginAPISwagger, + ); err != nil { + return err + } + + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("swagger proto"), + false, + showProgress, + runRegularProtoSwagger, + ); err != nil { + return err + } + + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("swagger plugin proto"), + false, + showProgress, + runPluginProtoSwagger, + ); err != nil { + return err + } - if !pathx.FileExists(config.C.Gen.Swagger.Output) { - _ = os.MkdirAll(config.C.Gen.Swagger.Output, 0o755) + if config.C.Gen.Swagger.Merge && hasSwaggerInput { + if err = executeStage( + console.Green("Merge")+" "+console.Yellow("swagger"), + showProgress && hasSwaggerInput, + showProgress, + runMergeSwagger, + ); err != nil { + return err } + } + + return nil +} - var files []string +func runRegularAPISwagger(progressChan chan<- progress.Message) error { + files, err := listSwaggerAPIFiles() + if err != nil { + return err + } + regularFiles, _ := splitPluginPaths(files) + return runAPISwagger(regularFiles, progressChan) +} - switch { - case len(config.C.Gen.Swagger.Desc) > 0: - for _, v := range config.C.Gen.Swagger.Desc { - if !osx.IsDir(v) { - if filepath.Ext(v) == ".api" { - files = append(files, v) - } - } else { - specifiedApiFiles, err := desc.FindApiFiles(v) - if err != nil { - return err - } - files = append(files, specifiedApiFiles...) +func runPluginAPISwagger(progressChan chan<- progress.Message) error { + files, err := listSwaggerAPIFiles() + if err != nil { + return err + } + _, pluginFiles := splitPluginPaths(files) + return runAPISwagger(pluginFiles, progressChan) +} + +func runRegularProtoSwagger(progressChan chan<- progress.Message) error { + files, err := listSwaggerProtoFiles() + if err != nil { + return err + } + regularFiles, _ := splitPluginPaths(files) + return runProtoSwagger(regularFiles, progressChan) +} + +func runPluginProtoSwagger(progressChan chan<- progress.Message) error { + files, err := listSwaggerProtoFiles() + if err != nil { + return err + } + _, pluginFiles := splitPluginPaths(files) + return runProtoSwagger(pluginFiles, progressChan) +} + +func executeStage(title string, headerShown, showProgress bool, fn func(chan<- progress.Message) error) error { + if !showProgress { + return fn(nil) + } + + progressChan := make(chan progress.Message, 10) + done := make(chan struct{}) + var stageErr error + + if headerShown { + fmt.Printf("%s\n", console.BoxHeader("", title)) + } + + go func() { + stageErr = fn(progressChan) + close(done) + }() + + state := progress.ConsumeStage(progressChan, done, title, false, headerShown) + progress.FinishStage(title, false, &state, stageErr) + + if stageErr != nil { + return console.MarkRenderedError(stageErr) + } + + return nil +} + +func runAPISwagger(files []string, progressChan chan<- progress.Message) error { + if err := ensureSwaggerOutputDir(); err != nil { + return err + } + if len(files) == 0 { + return nil + } + + var eg errgroup.Group + eg.SetLimit(len(files)) + for _, file := range files { + file := file + eg.Go(func() error { + if err := processSwaggerAPIFile(file); err != nil { + return errors.Wrapf(err, "swagger api file: %s", file) + } + if progressChan != nil { + progressChan <- progress.NewFile(file) + } + return nil + }) + } + + return eg.Wait() +} + +func listSwaggerAPIFiles() ([]string, error) { + var files []string + + switch { + case len(config.C.Gen.Swagger.Desc) > 0: + for _, v := range config.C.Gen.Swagger.Desc { + if !osx.IsDir(v) { + if filepath.Ext(v) == ".api" { + files = append(files, v) } + continue } - default: + + specifiedApiFiles, err := desc.FindApiFiles(v) + if err != nil { + return nil, err + } + files = append(files, specifiedApiFiles...) + } + default: + if pathx.FileExists(config.C.ApiDir()) { + var err error files, err = desc.FindRouteApiFiles(config.C.ApiDir()) if err != nil { - return err + return nil, err } + } - // 增加 plugins 的 api 文件 - plugins, err := serverless.GetPlugins() - if err == nil { - for _, p := range plugins { - if pathx.FileExists(filepath.Join(p.Path, "desc", "api")) { - pluginFiles, err := desc.FindRouteApiFiles(filepath.Join(p.Path, "desc", "api")) - if err != nil { - return err - } - files = append(files, pluginFiles...) + plugins, err := serverless.GetPlugins() + if err == nil { + for _, p := range plugins { + if pathx.FileExists(filepath.Join(p.Path, "desc", "api")) { + pluginFiles, err := desc.FindRouteApiFiles(filepath.Join(p.Path, "desc", "api")) + if err != nil { + return nil, err } + files = append(files, pluginFiles...) } } } + } - for _, v := range config.C.Gen.Swagger.DescIgnore { - if !osx.IsDir(v) { - if filepath.Ext(v) == ".api" { - files = lo.Reject(files, func(item string, _ int) bool { - return item == v - }) - } - } else { - specifiedApiFiles, err := desc.FindApiFiles(v) - if err != nil { - return err - } - for _, saf := range specifiedApiFiles { - files = lo.Reject(files, func(item string, _ int) bool { - return item == saf - }) - } + for _, v := range config.C.Gen.Swagger.DescIgnore { + if !osx.IsDir(v) { + if filepath.Ext(v) == ".api" { + files = lo.Reject(files, func(item string, _ int) bool { + return item == v + }) } + continue } - var eg errgroup.Group - eg.SetLimit(len(files)) - for _, v := range files { - eg.Go(func() error { - parse, err := apiparser.Parse(v, nil) - if err != nil { - return err - } + specifiedApiFiles, err := desc.FindApiFiles(v) + if err != nil { + return nil, err + } + for _, saf := range specifiedApiFiles { + files = lo.Reject(files, func(item string, _ int) bool { + return item == saf + }) + } + } - // 保持目录结构,生成到 desc/swagger 下 - var relPath string - - // 检查是否是插件文件 - pluginName := getPluginNameFromFilePath(v) - if pluginName != "" { - // 插件文件处理:找到 desc/api 在路径中的位置 - descApiPath := filepath.Join("desc", "api") + string(filepath.Separator) - descApiIndex := strings.Index(v, descApiPath) - var pluginApiDir string - if descApiIndex == -1 { - // 如果找不到 desc/api 模式,尝试查找路径末尾是否以 desc/api 结尾 - if strings.HasSuffix(filepath.Dir(v), filepath.Join("desc", "api")) { - pluginApiDir = filepath.Dir(v) - } else { - return fmt.Errorf("invalid plugin api path: %s", v) - } - } else { - pluginApiDir = v[:descApiIndex+len(descApiPath)] - } + return files, nil +} - var relErr error - relPath, relErr = filepath.Rel(pluginApiDir, v) - if relErr != nil { - return relErr - } - // 在插件目录下保持结构 - relPath = filepath.Join("plugins", pluginName, relPath) - } else { - // 普通 API 文件处理 - relPath, err = filepath.Rel(config.C.ApiDir(), v) - if err != nil { - return err - } - } +func processSwaggerAPIFile(apiPath string) error { + parse, err := apiparser.Parse(apiPath, nil) + if err != nil { + return err + } - // 将 .api 扩展名替换为 .swagger - swaggerFileName := strings.TrimSuffix(relPath, ".api") + ".swagger" + var relPath string - // 创建输出目录结构 - outputDir := filepath.Join(config.C.Gen.Swagger.Output, filepath.Dir(swaggerFileName)) - if err := os.MkdirAll(outputDir, 0o755); err != nil { - return err - } + pluginName := getPluginNameFromFilePath(apiPath) + if pluginName != "" { + descApiPath := filepath.Join("desc", "api") + string(filepath.Separator) + descApiIndex := strings.Index(apiPath, descApiPath) + var pluginAPIDir string + if descApiIndex == -1 { + if strings.HasSuffix(filepath.Dir(apiPath), filepath.Join("desc", "api")) { + pluginAPIDir = filepath.Dir(apiPath) + } else { + return fmt.Errorf("invalid plugin api path: %s", apiPath) + } + } else { + pluginAPIDir = apiPath[:descApiIndex+len(descApiPath)] + } - apiFile := filepath.Base(swaggerFileName) - goPackage := parse.Info.Properties["go_package"] + relPath, err = filepath.Rel(pluginAPIDir, apiPath) + if err != nil { + return err + } + relPath = filepath.Join("plugins", pluginName, relPath) + } else { + relPath, err = filepath.Rel(config.C.ApiDir(), apiPath) + if err != nil { + return err + } + } - cmd := exec.Command("goctl", "api", "swagger", "--api", v, "--filename", apiFile, "--dir", outputDir) + swaggerFileName := strings.TrimSuffix(relPath, ".api") + ".swagger" + outputDir := filepath.Join(config.C.Gen.Swagger.Output, filepath.Dir(swaggerFileName)) + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return err + } - logx.Debug(cmd.String()) - resp, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, strings.TrimRight(string(resp), "\r\n")) - } - if strings.TrimRight(string(resp), "\r\n") != "" { - fmt.Println(strings.TrimRight(string(resp), "\r\n")) - } + apiFile := filepath.Base(swaggerFileName) + goPackage := parse.Info.Properties["go_package"] - // 兼容处理 - file, err := os.ReadFile(filepath.Join(outputDir, apiFile+".json")) - if err != nil { - return err - } - g, err := genius.NewFromRawJSON(file) - if err != nil { - return err - } + cmd := exec.Command("goctl", "api", "swagger", "--api", apiPath, "--filename", apiFile, "--dir", outputDir) + resp, err := cmd.CombinedOutput() + if err != nil { + return errors.Wrap(err, strings.TrimRight(string(resp), "\r\n")) + } - // 处理 host 值 - if cast.ToString(g.Get("host")) == "127.0.0.1" { - _ = g.Set("host", "") - } + file, err := os.ReadFile(filepath.Join(outputDir, apiFile+".json")) + if err != nil { + return err + } + g, err := genius.NewFromRawJSON(file) + if err != nil { + return err + } - /* - "x-date": "", - "x-description": "This is a goctl generated swagger file.", - "x-github": "https://github.com/zeromicro/go-zero", - "x-go-zero-doc": "https://go-zero.dev/", - "x-goctl-version": "1.9.0" - */ - - // 删除 x-date - g.Del("x-date") - g.Del("x-description") - g.Del("x-github") - g.Del("x-go-zero-doc") - g.Del("x-goctl-version") - - // 处理 securityDefinitions 值 - if g.Get("securityDefinitions") == nil { - _ = g.Set("securityDefinitions", map[string]any{ - "apiKey": map[string]any{ - "type": "apiKey", - "description": "Enter Authorization", - "name": "Authorization", - "in": "header", - }, - }) - } + if cast.ToString(g.Get("host")) == "127.0.0.1" { + _ = g.Set("host", "") + } + + g.Del("x-date") + g.Del("x-description") + g.Del("x-github") + g.Del("x-go-zero-doc") + g.Del("x-goctl-version") + + if g.Get("securityDefinitions") == nil { + _ = g.Set("securityDefinitions", map[string]any{ + "apiKey": map[string]any{ + "type": "apiKey", + "description": "Enter Authorization", + "name": "Authorization", + "in": "header", + }, + }) + } + + if len(cast.ToStringSlice(g.Get("schemes"))) == 1 && cast.ToStringSlice(g.Get("schemes"))[0] == "https" { + _ = g.Set("schemes", []string{"http", "https"}) + } + + pathMaps := cast.ToStringMap(g.Get("paths")) + for pmk := range pathMaps { + pathMethodsMap := cast.ToStringMap(pathMaps[pmk]) + for pmmk := range pathMethodsMap { + for _, group := range parse.Service.Groups { + for _, route := range group.Routes { + if group.GetAnnotation("prefix") != "" { + route.Path = group.GetAnnotation("prefix") + route.Path + } + if route.Method == pmmk && route.Path == adjustHttpPath(pmk) && group.GetAnnotation("group") != "" { + h := strings.TrimSuffix(route.Handler, "Handler") + groupName := group.GetAnnotation("group") - // 处理 schemes 值 - if len(cast.ToStringSlice(g.Get("schemes"))) == 1 && cast.ToStringSlice(g.Get("schemes"))[0] == "https" { - _ = g.Set("schemes", []string{"http", "https"}) + if config.C.Gen.Swagger.Route2Code || config.C.Gen.Route2Code { + _ = g.Set(fmt.Sprintf("paths.%s.%s.description", pmk, pmmk), "接口权限编码"+":"+stringx.FirstLower(strings.ReplaceAll(groupName, "/", ":"))+":"+stringx.FirstLower(h)) + } + } } + } - pathMaps := cast.ToStringMap(g.Get("paths")) - for pmk := range pathMaps { - pathMethodsMap := cast.ToStringMap(pathMaps[pmk]) - for pmmk := range pathMethodsMap { + tags := cast.ToStringSlice(g.Get(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk))) + pluginName = getPluginNameFromFilePath(apiPath) + + if pluginName != "" { + if len(tags) > 0 && !(len(tags) == 1 && tags[0] == "") { + var newTags []string + for _, tag := range tags { + if tag != "" { + newTags = append(newTags, "plugins/"+pluginName+"/"+tag) + } + } + if len(newTags) > 0 { + _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), newTags) + } + } else { + if goPackage != "" { + tagValue := "plugins/" + pluginName + "/" + goPackage + _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{tagValue}) + } else { for _, group := range parse.Service.Groups { for _, route := range group.Routes { - logx.Debugf("get route prefix: %s", route.GetAnnotation("prefix")) if group.GetAnnotation("prefix") != "" { route.Path = group.GetAnnotation("prefix") + route.Path } if route.Method == pmmk && route.Path == adjustHttpPath(pmk) && group.GetAnnotation("group") != "" { - h := strings.TrimSuffix(route.Handler, "Handler") - groupName := group.GetAnnotation("group") - - if config.C.Gen.Swagger.Route2Code || config.C.Gen.Route2Code { - _ = g.Set(fmt.Sprintf("paths.%s.%s.description", pmk, pmmk), "接口权限编码"+":"+stringx.FirstLower(strings.ReplaceAll(groupName, "/", ":"))+":"+stringx.FirstLower(h)) - } - } - } - } - - // 处理 tags - tags := cast.ToStringSlice(g.Get(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk))) - pluginName = getPluginNameFromFilePath(v) - - if pluginName != "" { - // 插件文件:处理已存在的 tags,为每个 tag 添加插件前缀 - if len(tags) > 0 && !(len(tags) == 1 && tags[0] == "") { - var newTags []string - for _, tag := range tags { - if tag != "" { - newTags = append(newTags, "plugins/"+pluginName+"/"+tag) - } - } - if len(newTags) > 0 { - _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), newTags) - } - } else { - // 如果没有 tags,设置默认 tags - if goPackage != "" { - tagValue := "plugins/" + pluginName + "/" + goPackage + tagValue := "plugins/" + pluginName + "/" + group.GetAnnotation("group") _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{tagValue}) - } else { - for _, group := range parse.Service.Groups { - for _, route := range group.Routes { - logx.Debugf("get route prefix: %s", route.GetAnnotation("prefix")) - if group.GetAnnotation("prefix") != "" { - route.Path = group.GetAnnotation("prefix") + route.Path - } - if route.Method == pmmk && route.Path == adjustHttpPath(pmk) && group.GetAnnotation("group") != "" { - tagValue := "plugins/" + pluginName + "/" + group.GetAnnotation("group") - _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{tagValue}) - break - } - } - } - } - } - } else { - // 普通文件:只在没有 tags 时设置默认值 - if len(tags) == 0 || (len(tags) == 1 && tags[0] == "") { - if goPackage != "" { - _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{goPackage}) - } else { - for _, group := range parse.Service.Groups { - for _, route := range group.Routes { - logx.Debugf("get route prefix: %s", route.GetAnnotation("prefix")) - if group.GetAnnotation("prefix") != "" { - route.Path = group.GetAnnotation("prefix") + route.Path - } - if route.Method == pmmk && route.Path == adjustHttpPath(pmk) && group.GetAnnotation("group") != "" { - _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{group.GetAnnotation("group")}) - break - } - } - } + break } } } - - // 处理 operationId - pluginName = getPluginNameFromFilePath(v) - if pluginName != "" { - operationId := cast.ToString(g.Get(fmt.Sprintf("paths.%s.%s.operationId", pmk, pmmk))) - if operationId != "" { - newOperationId := "plugins/" + pluginName + "/" + operationId - _ = g.Set(fmt.Sprintf("paths.%s.%s.operationId", pmk, pmmk), newOperationId) + } + } + } else if len(tags) == 0 || (len(tags) == 1 && tags[0] == "") { + if goPackage != "" { + _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{goPackage}) + } else { + for _, group := range parse.Service.Groups { + for _, route := range group.Routes { + if group.GetAnnotation("prefix") != "" { + route.Path = group.GetAnnotation("prefix") + route.Path + } + if route.Method == pmmk && route.Path == adjustHttpPath(pmk) && group.GetAnnotation("group") != "" { + _ = g.Set(fmt.Sprintf("paths.%s.%s.tags", pmk, pmmk), []string{group.GetAnnotation("group")}) + break } - } - - // 处理 security - /* - "security": [ - { - "apiKey": [] - } - ], - */ - if g.Get(fmt.Sprintf("paths.%s.%s.security", pmk, pmmk)) == nil { - _ = g.Set(fmt.Sprintf("paths.%s.%s.security", pmk, pmmk), []map[string][]any{ - { - "apiKey": []any{}, - }, - }) } } } + } - encodeToJSON, err := g.EncodeToPrettyJSON() - if err != nil { - return err - } - err = os.WriteFile(filepath.Join(outputDir, apiFile+".json"), encodeToJSON, 0o644) - if err != nil { - return err + pluginName = getPluginNameFromFilePath(apiPath) + if pluginName != "" { + operationID := cast.ToString(g.Get(fmt.Sprintf("paths.%s.%s.operationId", pmk, pmmk))) + if operationID != "" { + _ = g.Set(fmt.Sprintf("paths.%s.%s.operationId", pmk, pmmk), "plugins/"+pluginName+"/"+operationID) } - return nil - }) + } - if err = eg.Wait(); err != nil { - return err + if g.Get(fmt.Sprintf("paths.%s.%s.security", pmk, pmmk)) == nil { + _ = g.Set(fmt.Sprintf("paths.%s.%s.security", pmk, pmmk), []map[string][]any{ + { + "apiKey": []any{}, + }, + }) } } } - if pathx.FileExists(config.C.ProtoDir()) { - _ = os.MkdirAll(config.C.Gen.Swagger.Output, 0o755) + encodeToJSON, err := g.EncodeToPrettyJSON() + if err != nil { + return err + } - var files []string + return os.WriteFile(filepath.Join(outputDir, apiFile+".json"), encodeToJSON, 0o644) +} - switch { - case len(config.C.Gen.Swagger.Desc) > 0: - for _, v := range config.C.Gen.Swagger.Desc { - if !osx.IsDir(v) { - if filepath.Ext(v) == ".proto" { - files = append(files, v) - } - } else { - specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) - if err != nil { - return err - } - files = append(files, specifiedProtoFiles...) +func runProtoSwagger(files []string, progressChan chan<- progress.Message) error { + if err := ensureSwaggerOutputDir(); err != nil { + return err + } + if len(files) == 0 { + return nil + } + + var eg errgroup.Group + eg.SetLimit(len(files)) + for _, protoPath := range files { + protoPath := protoPath + eg.Go(func() error { + if err := processSwaggerProtoFile(protoPath); err != nil { + return errors.Wrapf(err, "swagger proto file: %s", protoPath) + } + if progressChan != nil { + progressChan <- progress.NewFile(protoPath) + } + return nil + }) + } + + return eg.Wait() +} + +func listSwaggerProtoFiles() ([]string, error) { + var files []string + + switch { + case len(config.C.Gen.Swagger.Desc) > 0: + for _, v := range config.C.Gen.Swagger.Desc { + if !osx.IsDir(v) { + if filepath.Ext(v) == ".proto" { + files = append(files, v) } + continue + } + + specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) + if err != nil { + return nil, err } - default: + files = append(files, specifiedProtoFiles...) + } + default: + if pathx.FileExists(config.C.ProtoDir()) { + var err error files, err = desc.FindRpcServiceProtoFiles(config.C.ProtoDir()) if err != nil { - return err + return nil, err } + } - // 增加 plugins 的 proto 文件 - plugins, err := serverless.GetPlugins() - if err == nil { - for _, p := range plugins { - if pathx.FileExists(filepath.Join(p.Path, "desc", "proto")) { - pluginFiles, err := desc.FindRpcServiceProtoFiles(filepath.Join(p.Path, "desc", "proto")) - if err != nil { - return err - } - files = append(files, pluginFiles...) + plugins, err := serverless.GetPlugins() + if err == nil { + for _, p := range plugins { + if pathx.FileExists(filepath.Join(p.Path, "desc", "proto")) { + pluginFiles, err := desc.FindRpcServiceProtoFiles(filepath.Join(p.Path, "desc", "proto")) + if err != nil { + return nil, err } + files = append(files, pluginFiles...) } } } + } - for _, v := range config.C.Gen.Swagger.DescIgnore { - if !osx.IsDir(v) { - if filepath.Ext(v) == ".proto" { - files = lo.Reject(files, func(item string, _ int) bool { - return item == v - }) - } - } else { - specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) - if err != nil { - return err - } - for _, saf := range specifiedProtoFiles { - files = lo.Reject(files, func(item string, _ int) bool { - return item == saf - }) - } + for _, v := range config.C.Gen.Swagger.DescIgnore { + if !osx.IsDir(v) { + if filepath.Ext(v) == ".proto" { + files = lo.Reject(files, func(item string, _ int) bool { + return item == v + }) } + continue + } + + specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) + if err != nil { + return nil, err + } + for _, saf := range specifiedProtoFiles { + files = lo.Reject(files, func(item string, _ int) bool { + return item == saf + }) } + } - for _, path := range files { - // 检查是否是插件文件 - pluginName := getPluginNameFromFilePath(path) - var pluginProtoDir string - var outputDir string + return files, nil +} - if pluginName != "" { - // 插件文件处理:找到 proto 目录在路径中的位置 - protoPath := filepath.Join("", config.C.ProtoDir()) + string(filepath.Separator) - descProtoIndex := strings.Index(path, protoPath) - if descProtoIndex == -1 { - // 如果找不到 proto 路径模式,尝试查找路径末尾是否以 proto 目录结尾 - if strings.HasSuffix(filepath.Dir(path), filepath.Join("", config.C.ProtoDir())) { - pluginProtoDir = filepath.Dir(path) - } else { - return fmt.Errorf("invalid plugin proto path: %s", path) - } - } else { - pluginProtoDir = path[:descProtoIndex+len(protoPath)] - } - // 插件文件直接生成到 plugins/{插件名}/ 目录下 - outputDir = filepath.Join(config.C.Gen.Swagger.Output, "plugins", pluginName) +func processSwaggerProtoFile(protoPath string) error { + pluginName := getPluginNameFromFilePath(protoPath) + var pluginProtoDir string + var outputDir string + + if pluginName != "" { + protoDirPrefix := filepath.Join("", config.C.ProtoDir()) + string(filepath.Separator) + descProtoIndex := strings.Index(protoPath, protoDirPrefix) + if descProtoIndex == -1 { + if strings.HasSuffix(filepath.Dir(protoPath), filepath.Join("", config.C.ProtoDir())) { + pluginProtoDir = filepath.Dir(protoPath) } else { - // 普通 Proto 文件直接生成到根目录下 - outputDir = config.C.Gen.Swagger.Output + return fmt.Errorf("invalid plugin proto path: %s", protoPath) } + } else { + pluginProtoDir = protoPath[:descProtoIndex+len(protoDirPrefix)] + } + outputDir = filepath.Join(config.C.Gen.Swagger.Output, "plugins", pluginName) + } else { + outputDir = config.C.Gen.Swagger.Output + } - // 创建输出目录结构 - if err := os.MkdirAll(outputDir, 0o755); err != nil { - return err - } + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return err + } - // 为插件文件添加插件路径到 protoc 的 -I 参数 - var includeArgs []string - if pluginName != "" { - includeArgs = append(includeArgs, "-I"+pluginProtoDir) - // 还需要包含插件的 third_party 目录 - pluginThirdParty := filepath.Join(pluginProtoDir, "third_party") - if pathx.FileExists(pluginThirdParty) { - includeArgs = append(includeArgs, "-I"+pluginThirdParty) - } + var includeArgs []string + if pluginName != "" { + includeArgs = append(includeArgs, "-I"+pluginProtoDir) + pluginThirdParty := filepath.Join(pluginProtoDir, "third_party") + if pathx.FileExists(pluginThirdParty) { + includeArgs = append(includeArgs, "-I"+pluginThirdParty) + } + } + includeArgs = append(includeArgs, "-I"+config.C.ProtoDir()) + includeArgs = append(includeArgs, "-I"+filepath.Join(config.C.ProtoDir(), "third_party")) + + command := fmt.Sprintf("protoc %s %s --openapiv2_out=%s", + strings.Join(includeArgs, " "), + protoPath, + outputDir, + ) + _, err := execx.Run(command, config.C.Wd()) + return err +} + +func runMergeSwagger(progressChan chan<- progress.Message) error { + outputFile := filepath.Join(config.C.Gen.Swagger.Output, "swagger.json") + if err := mergeSwaggerFiles(); err != nil { + return errors.Wrapf(err, "merge swagger file: %s", outputFile) + } + if progressChan != nil { + progressChan <- progress.NewFile(outputFile) + } + return nil +} + +func ensureSwaggerOutputDir() error { + return os.MkdirAll(config.C.Gen.Swagger.Output, 0o755) +} + +func hasSwaggerSourceInput() bool { + if pathx.FileExists(config.C.ApiDir()) { + if files, err := desc.FindApiFiles(config.C.ApiDir()); err == nil && len(files) > 0 { + return true + } + } + + if pathx.FileExists(config.C.ProtoDir()) { + if files, err := desc.FindExcludeThirdPartyProtoFiles(config.C.ProtoDir()); err == nil && len(files) > 0 { + return true + } + } + + plugins, err := serverless.GetPlugins() + if err != nil { + return false + } + + for _, p := range plugins { + pluginAPIDir := filepath.Join(p.Path, "desc", "api") + if pathx.FileExists(pluginAPIDir) { + if files, err := desc.FindApiFiles(pluginAPIDir); err == nil && len(files) > 0 { + return true } - includeArgs = append(includeArgs, "-I"+config.C.ProtoDir()) - includeArgs = append(includeArgs, "-I"+filepath.Join(config.C.ProtoDir(), "third_party")) - - command := fmt.Sprintf("protoc %s %s --openapiv2_out=%s", - strings.Join(includeArgs, " "), - path, - outputDir, - ) - _, err = execx.Run(command, config.C.Wd()) - if err != nil { - return err + } + + pluginProtoDir := filepath.Join(p.Path, "desc", "proto") + if pathx.FileExists(pluginProtoDir) { + if files, err := desc.FindExcludeThirdPartyProtoFiles(pluginProtoDir); err == nil && len(files) > 0 { + return true } } } - // 统一的 merge 处理,合并所有生成的 swagger 文件 - if config.C.Gen.Swagger.Merge { - err = mergeSwaggerFiles() - if err != nil { - return err + return false +} + +func splitPluginPaths(files []string) (regular []string, plugin []string) { + for _, file := range files { + if getPluginNameFromFilePath(file) != "" { + plugin = append(plugin, file) + continue } + regular = append(regular, file) } - return nil + return regular, plugin } // mergeSwaggerFiles 递归扫描并合并所有的 swagger 文件 @@ -488,13 +647,13 @@ func mergeSwaggerFiles() error { file, err := os.ReadFile(filePath) if err != nil { - logx.Errorf("failed to read swagger file %s: %v", filePath, err) + // Error removed("failed to read swagger file %s: %v", filePath, err) continue } g, err := genius.NewFromRawJSON(file) if err != nil { - logx.Errorf("failed to parse swagger file %s: %v", filePath, err) + // Error removed("failed to parse swagger file %s: %v", filePath, err) continue } diff --git a/cmd/jzero/internal/command/gen/genzrpcclient/genzrpcclient.go b/cmd/jzero/internal/command/gen/genzrpcclient/genzrpcclient.go index ce552f118..f3603fd94 100644 --- a/cmd/jzero/internal/command/gen/genzrpcclient/genzrpcclient.go +++ b/cmd/jzero/internal/command/gen/genzrpcclient/genzrpcclient.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/rinchsan/gosimports" "github.com/samber/lo" - "github.com/zeromicro/go-zero/core/logx" conf "github.com/zeromicro/go-zero/tools/goctl/config" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" "github.com/zeromicro/go-zero/tools/goctl/rpc/generator" @@ -22,6 +21,8 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console/progress" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/mod" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/osx" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/templatex" @@ -36,6 +37,11 @@ type DirContext struct { Output string } +type pluginProtoGroup struct { + Plugin serverless.Plugin + Files []string +} + func (d DirContext) GetCall() generator.Dir { fileName := filepath.Join(d.Output, "typed", d.Resource) return generator.Dir{ @@ -101,8 +107,92 @@ func (d DirContext) SetPbDir(pbDir, grpcDir string) { } func Generate(genModule bool) (err error) { - g := generator.NewGenerator(config.C.Style, false) + showProgress := !config.C.Quiet + files, err := listZRPCClientProtoFiles() + if err != nil { + return err + } + + pluginGroups, err := listZRPCClientPluginGroups() + if err != nil { + return err + } + + if len(files) > 0 { + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("zrpcclient"), + showProgress, + showProgress, + func(progressChan chan<- progress.Message) error { + return generateMainZRPCClient(genModule, files, len(pluginGroups) > 0, progressChan) + }, + ); err != nil { + return err + } + } + + if len(pluginGroups) > 0 { + if err = executeStage( + console.Green("Gen")+" "+console.Yellow("zrpcclient plugin"), + showProgress, + showProgress, + func(progressChan chan<- progress.Message) error { + return generatePluginFiles(pluginGroups, progressChan) + }, + ); err != nil { + return err + } + } + return nil +} + +func HasInput() (bool, error) { + files, err := listZRPCClientProtoFiles() + if err != nil { + return false, err + } + if len(files) > 0 { + return true, nil + } + + pluginGroups, err := listZRPCClientPluginGroups() + if err != nil { + return false, err + } + + return len(pluginGroups) > 0, nil +} + +func executeStage(title string, headerShown, showProgress bool, fn func(chan<- progress.Message) error) error { + if !showProgress { + return fn(nil) + } + + progressChan := make(chan progress.Message, 10) + done := make(chan struct{}) + var stageErr error + + if headerShown { + fmt.Printf("%s\n", console.BoxHeader("", title)) + } + + go func() { + stageErr = fn(progressChan) + close(done) + }() + + state := progress.ConsumeStage(progressChan, done, title, false, headerShown) + progress.FinishStage(title, false, &state, stageErr) + + if stageErr != nil { + return console.MarkRenderedError(stageErr) + } + + return nil +} + +func listZRPCClientProtoFiles() ([]string, error) { var files []string switch { @@ -112,18 +202,22 @@ func Generate(genModule bool) (err error) { if filepath.Ext(v) == ".proto" { files = append(files, v) } - } else { - specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) - if err != nil { - return err - } - files = append(files, specifiedProtoFiles...) + continue + } + + specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) + if err != nil { + return nil, err } + files = append(files, specifiedProtoFiles...) } default: - files, err = desc.FindRpcServiceProtoFiles(config.C.ProtoDir()) - if err != nil { - return err + if pathx.FileExists(config.C.ProtoDir()) { + var err error + files, err = desc.FindRpcServiceProtoFiles(config.C.ProtoDir()) + if err != nil { + return nil, err + } } } @@ -134,31 +228,66 @@ func Generate(genModule bool) (err error) { return item == v }) } - } else { - specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) - if err != nil { - return err - } - for _, saf := range specifiedProtoFiles { - files = lo.Reject(files, func(item string, _ int) bool { - return item == saf - }) - } + continue + } + + specifiedProtoFiles, err := desc.FindRpcServiceProtoFiles(v) + if err != nil { + return nil, err + } + for _, saf := range specifiedProtoFiles { + files = lo.Reject(files, func(item string, _ int) bool { + return item == saf + }) } } + files = lo.Reject(files, func(item string, _ int) bool { + return getPluginNameFromFilePath(item) != "" + }) + + return files, nil +} + +func listZRPCClientPluginGroups() ([]pluginProtoGroup, error) { + plugins, _ := serverless.GetPlugins() + var groups []pluginProtoGroup + + for _, p := range plugins { + pluginProtoDir := filepath.Join(p.Path, "desc", "proto") + if !pathx.FileExists(pluginProtoDir) { + continue + } + + pluginProtoFiles, err := desc.FindRpcServiceProtoFiles(pluginProtoDir) + if err != nil { + return nil, err + } + if len(pluginProtoFiles) == 0 { + continue + } + + groups = append(groups, pluginProtoGroup{ + Plugin: p, + Files: pluginProtoFiles, + }) + } + + return groups, nil +} + +func generateMainZRPCClient(genModule bool, files []string, hasPlugins bool, progressChan chan<- progress.Message) error { + g := generator.NewGenerator(config.C.Style, false) + wd, err := os.Getwd() if err != nil { return err } - plugins, _ := serverless.GetPlugins() - excludeThirdPartyProtoFiles, err := desc.FindExcludeThirdPartyProtoFiles(config.C.ProtoDir()) if err != nil { return err } - logx.Debugf("excludeThirdPartyProtoFiles: %v", excludeThirdPartyProtoFiles) var services []string for _, fp := range files { @@ -177,10 +306,9 @@ func Generate(genModule bool) (err error) { services = append(services, service.Name) _ = os.MkdirAll(filepath.Join(dirContext.GetCall().Filename, strings.ToLower(service.Name)), 0o755) } + pbDir := filepath.Join(config.C.Gen.Zrpcclient.Output, "model") - // gen pb model - err = os.MkdirAll(pbDir, 0o755) - if err != nil { + if err = os.MkdirAll(pbDir, 0o755); err != nil { return err } @@ -221,10 +349,8 @@ func Generate(genModule bool) (err error) { } module := config.C.Gen.Zrpcclient.GoModule - if !genModule { - if config.C.Gen.Zrpcclient.Output != "." { - module = getMod.Path - } + if !genModule && config.C.Gen.Zrpcclient.Output != "." { + module = getMod.Path } protocCmd := fmt.Sprintf("protoc %s -I%s -I%s --go_out=%s --go-grpc_out=%s", @@ -266,7 +392,6 @@ func Generate(genModule bool) (err error) { if strings.HasPrefix(goPackage, module) { return goPackage } - if genModule { return filepath.ToSlash(filepath.Join(module, "model", goPackage)) } @@ -275,7 +400,6 @@ func Generate(genModule bool) (err error) { if strings.HasPrefix(goPackage, module) { return goPackage } - if genModule { return filepath.ToSlash(filepath.Join(module, "model", goPackage)) } @@ -287,7 +411,6 @@ func Generate(genModule bool) (err error) { protocCmd += fmt.Sprintf(" -I%s ", strings.Join(config.C.Gen.Zrpcclient.ProtoInclude, " -I")) } - logx.Debugf(protocCmd) resp, err := execx.Run(protocCmd, wd) if err != nil { return errors.Errorf("err: [%v], resp: [%s]", err, resp) @@ -302,20 +425,12 @@ func Generate(genModule bool) (err error) { if err != nil { return err } - } - hasPlugins := len(plugins) > 0 - for _, p := range plugins { - if pathx.FileExists(filepath.Join(p.Path, "desc", "proto")) { - pluginProtoFiles, err := desc.FindRpcServiceProtoFiles(filepath.Join(p.Path, "desc", "proto")) - if err == nil && len(pluginProtoFiles) > 0 { - hasPlugins = true - break - } + if progressChan != nil { + progressChan <- progress.NewFile(fp) } } - // gen clientset template, err := templatex.ParseTemplate(filepath.ToSlash(filepath.Join("client", "zrpcclient-go", "clientset.go.tpl")), map[string]any{ "Module": config.C.Gen.Zrpcclient.GoModule, "Package": config.C.Gen.Zrpcclient.GoPackage, @@ -330,12 +445,10 @@ func Generate(genModule bool) (err error) { if err != nil { return errors.Errorf("format go file %s %s meet error: %v", filepath.Join(config.C.Gen.Zrpcclient.Output, "clientset.go"), template, err) } - err = os.WriteFile(filepath.Join(config.C.Gen.Zrpcclient.Output, "clientset.go"), formated, 0o644) - if err != nil { + if err = os.WriteFile(filepath.Join(config.C.Gen.Zrpcclient.Output, "clientset.go"), formated, 0o644); err != nil { return err } - // if set --module flag if genModule { goVersion, err := mod.GetGoVersion() if err != nil { @@ -353,27 +466,16 @@ func Generate(genModule bool) (err error) { if err != nil { return err } - err = os.WriteFile(filepath.Join(config.C.Gen.Zrpcclient.Output, "go.mod"), template, 0o644) - if err != nil { + if err = os.WriteFile(filepath.Join(config.C.Gen.Zrpcclient.Output, "go.mod"), template, 0o644); err != nil { return err } } - err = generatePluginFiles(plugins) - if err != nil { - return err - } - - err = genNoRpcServiceExcludeThirdPartyProto(genModule, config.C.Gen.Zrpcclient.GoModule) - if err != nil { - return err - } - - return nil + return genNoRpcServiceExcludeThirdPartyProto(genModule, config.C.Gen.Zrpcclient.GoModule, progressChan) } -func generatePluginFiles(plugins []serverless.Plugin) error { - if len(plugins) == 0 { +func generatePluginFiles(groups []pluginProtoGroup, progressChan chan<- progress.Message) error { + if len(groups) == 0 { return nil } @@ -384,16 +486,13 @@ func generatePluginFiles(plugins []serverless.Plugin) error { var pluginNames []string - for _, p := range plugins { + for _, group := range groups { + p := group.Plugin pluginProtoDir := filepath.Join(p.Path, "desc", "proto") pluginThirdPartyProtoDir := filepath.Join(p.Path, "desc", "proto", "third_party") - if !pathx.FileExists(pluginProtoDir) { - continue - } - - pluginProtoFiles, err := desc.FindRpcServiceProtoFiles(pluginProtoDir) - if err != nil || len(pluginProtoFiles) == 0 { + pluginProtoFiles := group.Files + if len(pluginProtoFiles) == 0 { continue } @@ -484,7 +583,7 @@ func generatePluginFiles(plugins []serverless.Plugin) error { protocCmd += fmt.Sprintf(" -I%s ", strings.Join(config.C.Gen.Zrpcclient.ProtoInclude, " -I")) } - logx.Debugf(protocCmd) + // Debug removed(protocCmd) resp, err := execx.Run(protocCmd, wd) if err != nil { return errors.Errorf("err: [%v], resp: [%s]", err, resp) @@ -524,6 +623,10 @@ func generatePluginFiles(plugins []serverless.Plugin) error { if err != nil { return err } + + if progressChan != nil { + progressChan <- progress.NewFile(fp) + } } pluginNames = append(pluginNames, p.Name) @@ -578,12 +681,8 @@ func generatePluginFiles(plugins []serverless.Plugin) error { return err } - for _, p := range plugins { - if !pathx.FileExists(filepath.Join(p.Path, "desc", "proto")) { - continue - } - - err := genPluginNoRpcServiceExcludeThirdPartyProto(p, config.C.Gen.Zrpcclient.GoModule, config.C.Gen.Zrpcclient.Output) + for _, group := range groups { + err := genPluginNoRpcServiceExcludeThirdPartyProto(group.Plugin, config.C.Gen.Zrpcclient.GoModule, config.C.Gen.Zrpcclient.Output, progressChan) if err != nil { return err } @@ -592,7 +691,7 @@ func generatePluginFiles(plugins []serverless.Plugin) error { return nil } -func genPluginNoRpcServiceExcludeThirdPartyProto(plugin serverless.Plugin, goModule, output string) error { +func genPluginNoRpcServiceExcludeThirdPartyProto(plugin serverless.Plugin, goModule, output string, progressChan chan<- progress.Message) error { pluginProtoDir := filepath.Join(plugin.Path, "desc", "proto") pluginThirdPartyProtoDir := filepath.Join(plugin.Path, "desc", "proto", "third_party") @@ -657,17 +756,20 @@ func genPluginNoRpcServiceExcludeThirdPartyProto(plugin serverless.Plugin, goMod command += fmt.Sprintf(" -I%s ", strings.Join(config.C.Gen.Zrpcclient.ProtoInclude, " -I")) } - logx.Debug(command) + // Debug removed(command) _, err = execx.Run(command, config.C.Wd()) if err != nil { return err } + if progressChan != nil { + progressChan <- progress.NewFile(v) + } } return nil } -func genNoRpcServiceExcludeThirdPartyProto(genModule bool, module string) error { +func genNoRpcServiceExcludeThirdPartyProto(genModule bool, module string, progressChan chan<- progress.Message) error { excludeThirdPartyProtoFiles, err := desc.FindNoRpcServiceExcludeThirdPartyProtoFiles(config.C.ProtoDir()) if err != nil { return err @@ -756,12 +858,27 @@ func genNoRpcServiceExcludeThirdPartyProto(genModule bool, module string) error }(), ) - logx.Debug(command) + // Debug removed(command) _, err = execx.Run(command, config.C.Wd()) if err != nil { return err } + if progressChan != nil { + progressChan <- progress.NewFile(v) + } } return nil } + +func getPluginNameFromFilePath(filePath string) string { + if strings.Contains(filePath, "plugins"+string(filepath.Separator)) { + parts := strings.Split(filePath, string(filepath.Separator)) + for i, part := range parts { + if part == "plugins" && i+1 < len(parts) { + return parts[i+1] + } + } + } + return "" +} diff --git a/cmd/jzero/internal/command/serverless/serverless.go b/cmd/jzero/internal/command/serverless/serverless.go index d4123f11e..3ebb31b85 100644 --- a/cmd/jzero/internal/command/serverless/serverless.go +++ b/cmd/jzero/internal/command/serverless/serverless.go @@ -5,12 +5,15 @@ Copyright © 2024 jaronnie package serverless import ( + "fmt" + "github.com/spf13/cobra" "github.com/jzero-io/jzero/cmd/jzero/internal/command/serverless/serverlessbuild" "github.com/jzero-io/jzero/cmd/jzero/internal/command/serverless/serverlessdelete" "github.com/jzero-io/jzero/cmd/jzero/internal/config" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" ) // serverlessCmd represents the serverless command @@ -25,8 +28,10 @@ var serverlessBuildCmd = &cobra.Command{ Long: `jzero serverless build.`, RunE: func(cmd *cobra.Command, args []string) error { embeded.Home = config.C.Home - return serverlessbuild.Run() + return runServerlessStage("build", serverlessbuild.Run) }, + SilenceUsage: true, + SilenceErrors: true, } var serverlessDeleteCmd = &cobra.Command{ @@ -35,8 +40,10 @@ var serverlessDeleteCmd = &cobra.Command{ Long: `jzero serverless delete.`, RunE: func(cmd *cobra.Command, args []string) error { embeded.Home = config.C.Home - return serverlessdelete.Run() + return runServerlessStage("delete", serverlessdelete.Run) }, + SilenceUsage: true, + SilenceErrors: true, } func GetCommand() *cobra.Command { @@ -47,3 +54,39 @@ func GetCommand() *cobra.Command { return serverlessCmd } + +func runServerlessStage(kind string, fn func() ([]string, error)) error { + items, err := fn() + if config.C.Quiet { + return err + } + + title := console.Green(stringsTitle(kind)) + " " + console.Yellow("serverless") + fmt.Printf("%s\n", console.BoxHeader("", title)) + + for _, item := range items { + fmt.Printf("%s\n", console.BoxItem(item)) + } + + if err != nil { + for _, line := range console.NormalizeErrorLines(err.Error()) { + fmt.Printf("%s\n", console.BoxDetailItem(line)) + } + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + return console.MarkRenderedError(err) + } + + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) + return nil +} + +func stringsTitle(kind string) string { + switch kind { + case "build": + return "Build" + case "delete": + return "Delete" + default: + return kind + } +} diff --git a/cmd/jzero/internal/command/serverless/serverlessbuild/serverlessbuild.go b/cmd/jzero/internal/command/serverless/serverlessbuild/serverlessbuild.go index 45f550b7d..76f59b6ad 100644 --- a/cmd/jzero/internal/command/serverless/serverlessbuild/serverlessbuild.go +++ b/cmd/jzero/internal/command/serverless/serverlessbuild/serverlessbuild.go @@ -17,19 +17,23 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/serverless" ) -func Run() error { +func Run() ([]string, error) { wd, _ := os.Getwd() + var items []string plugins, err := serverless.GetPlugins() if err != nil { - return err + return nil, err + } + for _, p := range plugins { + items = appendUnique(items, p.Path) } if _, err := os.Stat("go.work"); err == nil { goWork, _ := os.ReadFile("go.work") work, err := modfile.ParseWork("", goWork, nil) if err != nil { - return err + return items, err } for _, p := range plugins { if !p.Mono { @@ -37,7 +41,7 @@ func Run() error { p.Path = "./" + p.Path } if err = work.DropUse(p.Path); err != nil { - return err + return items, err } } } @@ -47,13 +51,14 @@ func Run() error { p.Path = "./" + p.Path } if err = work.AddUse(p.Path, ""); err != nil { - return err + return items, err } } } if err = os.WriteFile("go.work", modfile.Format(work.Syntax), 0o644); err != nil { - return err + return items, err } + items = appendUnique(items, "go.work") } else { initArgs := []string{"work", "init", "."} var pluginArgs []string @@ -67,21 +72,22 @@ func Run() error { ec.Dir = wd output, err := ec.CombinedOutput() if err != nil { - return errors.Wrapf(err, "go work init meet error %s", string(output)) + return items, errors.Wrapf(err, "go work init meet error %s", string(output)) } + items = appendUnique(items, "go.work") } } // write plugins/plugins.go goMod, err := mod.GetGoMod(wd) if err != nil { - return err + return items, err } for i := 0; i < len(plugins); i++ { if !plugins[i].Mono { pluginGoMod, err := mod.GetGoMod(filepath.Join(wd, plugins[i].Path)) if err != nil { - return err + return items, err } plugins[i].Module = pluginGoMod.Path } else { @@ -91,7 +97,7 @@ func Run() error { frameType, err := desc.GetFrameType() if err != nil { - return err + return items, err } pluginsGoBytes, err := templatex.ParseTemplate(filepath.ToSlash(filepath.Join("api", "serverless_plugins.go.tpl")), map[string]any{ @@ -99,15 +105,24 @@ func Run() error { "Module": goMod.Path, }, embeded.ReadTemplateFile(filepath.ToSlash(filepath.Join(frameType, "serverless_plugins.go.tpl")))) if err != nil { - return err + return items, err } gosimports.LocalPrefix = goMod.Path pluginsGoFormatBytes, err := gosimports.Process("", pluginsGoBytes, nil) if err != nil { - return err + return items, err } if err := os.WriteFile(filepath.Join("plugins", "plugins.go"), pluginsGoFormatBytes, 0o644); err != nil { - return err + return items, err + } + return items, nil +} + +func appendUnique(items []string, item string) []string { + for _, existing := range items { + if existing == item { + return items + } } - return nil + return append(items, item) } diff --git a/cmd/jzero/internal/command/serverless/serverlessdelete/serverlessdelete.go b/cmd/jzero/internal/command/serverless/serverlessdelete/serverlessdelete.go index 953b74579..f6969372a 100644 --- a/cmd/jzero/internal/command/serverless/serverlessdelete/serverlessdelete.go +++ b/cmd/jzero/internal/command/serverless/serverlessdelete/serverlessdelete.go @@ -17,12 +17,13 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/serverless" ) -func Run() error { +func Run() ([]string, error) { wd, _ := os.Getwd() + var items []string plugins, err := serverless.GetPlugins() if err != nil { - return err + return nil, err } deletePlugins := plugins @@ -36,12 +37,15 @@ func Run() error { return item.Path != filepath.ToSlash(filepath.Join("plugins", p)) }) } + for _, p := range deletePlugins { + items = appendUnique(items, p.Path) + } if _, err := os.Stat("go.work"); err == nil { goWork, _ := os.ReadFile("go.work") work, err := modfile.ParseWork("", goWork, nil) if err != nil { - return err + return items, err } for _, p := range deletePlugins { if !p.Mono { @@ -49,18 +53,19 @@ func Run() error { p.Path = "./" + p.Path } if err = work.DropUse(p.Path); err != nil { - return err + return items, err } } } if err = os.WriteFile("go.work", modfile.Format(work.Syntax), 0o644); err != nil { - return err + return items, err } + items = appendUnique(items, "go.work") // reread goWork, _ = os.ReadFile("go.work") work, err = modfile.ParseWork("", goWork, nil) if err != nil { - return err + return items, err } if (len(work.Use) == 0) || (len(work.Use) == 1 && work.Use[0].Path == ".") { _ = os.Remove("go.work") @@ -71,14 +76,14 @@ func Run() error { // write plugins/plugins.go goMod, err := mod.GetGoMod(wd) if err != nil { - return err + return items, err } for i := 0; i < len(remainingPlugins); i++ { if !plugins[i].Mono { pluginGoMod, err := mod.GetGoMod(filepath.Join(wd, remainingPlugins[i].Path)) if err != nil { - return err + return items, err } remainingPlugins[i].Module = pluginGoMod.Path } else { @@ -88,7 +93,7 @@ func Run() error { frameType, err := desc.GetFrameType() if err != nil { - return err + return items, err } pluginsGoBytes, err := templatex.ParseTemplate(filepath.ToSlash(filepath.Join("api", "serverless_plugins.go.tpl")), map[string]any{ @@ -96,15 +101,24 @@ func Run() error { "Module": goMod.Path, }, embeded.ReadTemplateFile(filepath.ToSlash(filepath.Join(frameType, "serverless_plugins.go.tpl")))) if err != nil { - return err + return items, err } gosimports.LocalPrefix = goMod.Path formatBytes, err := gosimports.Process("", pluginsGoBytes, nil) if err != nil { - return err + return items, err } if err := os.WriteFile(filepath.Join("plugins", "plugins.go"), formatBytes, 0o644); err != nil { - return err + return items, err + } + return items, nil +} + +func appendUnique(items []string, item string) []string { + for _, existing := range items { + if existing == item { + return items + } } - return nil + return append(items, item) } diff --git a/cmd/jzero/internal/config/config.go b/cmd/jzero/internal/config/config.go index a838f52d3..fa3ebb311 100644 --- a/cmd/jzero/internal/config/config.go +++ b/cmd/jzero/internal/config/config.go @@ -13,7 +13,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/pkg/protoc" "github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengo" "github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengogrpc" @@ -269,26 +268,21 @@ type ToolVersion struct { func (c *Config) ToolVersion() ToolVersion { ToolVersionOnce.Do(func() { goctlVersionResp, _ := execx.Run("goctl -v", "") - logx.Debugf("goctl version: %s", goctlVersionResp) versionInfo := strings.Split(goctlVersionResp, " ") if len(versionInfo) >= 3 { ToolVersionValue.GoctlVersion, _ = version.NewVersion(strings.TrimPrefix(versionInfo[2], "v")) } protocVersionResp, _ := protoc.Version() - logx.Debugf("protoc version: %s", protocVersionResp) ToolVersionValue.ProtocVersion, _ = version.NewVersion(strings.TrimPrefix(protocVersionResp, "libprotoc ")) protocGenGoVersionResp, _ := protocgengo.Version() - logx.Debugf("protoc-gen-go version: %s", protocGenGoVersionResp) ToolVersionValue.ProtocGenGoVersion, _ = version.NewVersion(strings.TrimPrefix(protocGenGoVersionResp, "v")) protocGenGoGrpcVersionResp, _ := protocgengogrpc.Version() - logx.Debugf("protoc-gen-go-grpc version: %s", protocGenGoGrpcVersionResp) ToolVersionValue.ProtocGenGoGrpcVersion, _ = version.NewVersion(strings.TrimPrefix(protocGenGoGrpcVersionResp, "v")) protocGenOpenapiv2VersionResp, _ := execx.Run("protoc-gen-openapiv2 --version", "") - logx.Debugf("protoc-gen-openapiv2 version: %s", protocGenOpenapiv2VersionResp) versionInfo = strings.Split(protocGenOpenapiv2VersionResp, " ") if len(versionInfo) >= 2 { ToolVersionValue.ProtocGenOpenapiv2Version, _ = version.NewVersion(strings.TrimSuffix(strings.TrimPrefix(versionInfo[1], "v"), ",")) diff --git a/cmd/jzero/internal/desc/desc.go b/cmd/jzero/internal/desc/desc.go index 9ffabd8fa..9f1ef9f8b 100644 --- a/cmd/jzero/internal/desc/desc.go +++ b/cmd/jzero/internal/desc/desc.go @@ -35,8 +35,8 @@ func GetFrameType() (string, error) { if isGatewayProject() { frameType = "gateway" } else { - // 获取全量 proto 文件 - protoFiles, err := FindRpcServiceProtoFiles(config.C.ProtoDir()) + // 对 frame type 只做尽力判断,不因 desc 语法错误提前中断真正的 gen 流程 + protoFiles, err := FindExcludeThirdPartyProtoFiles(config.C.ProtoDir()) if err != nil { return "", err } @@ -47,7 +47,7 @@ func GetFrameType() (string, error) { var parse rpcparser.Proto parse, err = protoParser.Parse(v, true) if err != nil { - return "", err + continue } if IsNeedGenProtoDescriptor(&parse) { frameType = "gateway" diff --git a/cmd/jzero/internal/hooks/hooks.go b/cmd/jzero/internal/hooks/hooks.go index 349deca4c..4d580fee7 100644 --- a/cmd/jzero/internal/hooks/hooks.go +++ b/cmd/jzero/internal/hooks/hooks.go @@ -5,10 +5,10 @@ import ( "os" "os/exec" "strconv" + "strings" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/zeromicro/go-zero/core/logx" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/execx" @@ -28,27 +28,62 @@ func Run(cmd *cobra.Command, hookAction, hooksName string, hooks []string) error } if len(hooks) > 0 && !quiet { - fmt.Printf("%s\n", console.Green(fmt.Sprintf("Start %s %s hooks", hookAction, hooksName))) + var icon string + var title string + + if hookAction == "Before" && hooksName == "global" { + icon = "" + title = console.Green("Executing") + " " + console.Yellow("Before Global Hooks") + } else if hookAction == "After" && hooksName == "global" { + icon = "" + title = console.Green("Executing") + " " + console.Yellow("After Global Hooks") + } else if hookAction == "Before" { + icon = "" + capitalName := strings.ToUpper(hooksName[:1]) + hooksName[1:] + title = console.Green("Executing") + " " + console.Yellow("Before "+capitalName+" Command Hooks") + } else if hookAction == "After" { + icon = "" + capitalName := strings.ToUpper(hooksName[:1]) + hooksName[1:] + title = console.Green("Executing") + " " + console.Yellow("After "+capitalName+" Command Hooks") + } + + fmt.Printf("%s\n", console.BoxHeader(icon, title)) } for _, v := range hooks { + output, err := execx.RunOutput(v, wd, "JZERO_HOOK_TRIGGERED=true") if !quiet { - fmt.Printf("%s command %s\n", console.Green("Run"), v) + printHookCommand(v, err == nil) } - err := execx.Run(v, wd, "JZERO_HOOK_TRIGGERED=true") if err != nil { - return err + lines := console.NormalizeErrorLines(output) + if len(lines) == 0 { + lines = console.NormalizeErrorLines(err.Error()) + } + if !quiet { + for _, line := range lines { + fmt.Printf("%s\n", console.BoxDetailItem(line)) + } + } + if !quiet { + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + } + if quiet { + return err + } + return console.MarkRenderedError(err) + } + if !quiet && output != "" { + printHookOutput(output) } } if len(hooks) > 0 && !quiet { - fmt.Printf("%s\n", console.Green("Done")) + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) } // fork 一个子进程来运行后续的指令 if len(hooks) > 0 && hookAction == "Before" && hooksName == "global" { - logx.Debugf("Before hooks executed, forking a new process to continue") - // 获取当前可执行文件路径 executable, err := os.Executable() if err != nil { @@ -85,3 +120,47 @@ func Run(cmd *cobra.Command, hookAction, hooksName string, hooks []string) error return nil } + +func printHookCommand(command string, success bool) { + if strings.Contains(command, "\n") { + lines := strings.Split(command, "\n") + if success { + fmt.Printf("%s\n", console.BoxItem(console.Cyan("Executing"))) + } else { + fmt.Printf("%s\n", console.BoxErrorItem(console.Cyan("Executing"))) + } + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if trimmedLine != "" { + fmt.Printf("│ │ %s\n", trimmedLine) + } + } + return + } + + item := fmt.Sprintf("%s %s", console.Cyan("Executing"), command) + if success { + fmt.Printf("%s\n", console.BoxItem(item)) + return + } + + fmt.Printf("%s\n", console.BoxErrorItem(item)) +} + +func printHookOutput(output string) { + lines := strings.Split(strings.TrimRight(output, "\r\n"), "\n") + if len(lines) == 0 { + return + } + + fmt.Printf("│ ╭─ %s\n", console.Cyan("Output")) + for _, line := range lines { + line = strings.TrimRight(line, "\r") + if line == "" { + fmt.Print("│ │\n") + continue + } + fmt.Printf("│ │ %s\n", line) + } + fmt.Printf("│ ╰─ %s\n", console.Cyan("Complete")) +} diff --git a/cmd/jzero/internal/pkg/console/console.go b/cmd/jzero/internal/pkg/console/console.go index 7efa80bce..02c4e726e 100644 --- a/cmd/jzero/internal/pkg/console/console.go +++ b/cmd/jzero/internal/pkg/console/console.go @@ -1,6 +1,8 @@ package console -import "fmt" +import ( + "fmt" +) const consoleColorTag = 0x1B @@ -18,3 +20,63 @@ func Yellow(txt string) string { func Red(txt string) string { return fmt.Sprintf("%c[31m%s%c[0m", consoleColorTag, txt, consoleColorTag) } + +// Cyan 控制台青色字符 +func Cyan(txt string) string { + return fmt.Sprintf("%c[2m%c[36m%s%c[0m", consoleColorTag, consoleColorTag, txt, consoleColorTag) +} + +// Bold 控制台粗体字符 +func Bold(txt string) string { + return fmt.Sprintf("%c[1m%s%c[0m", consoleColorTag, txt, consoleColorTag) +} + +// DimCyan 控制台淡青色字符 +func DimCyan(txt string) string { + return fmt.Sprintf("%c[90m%s%c[0m", consoleColorTag, txt, consoleColorTag) +} + +// DimCheckMark 控制台淡色对勾符号 +func DimCheckMark() string { + return fmt.Sprintf("%c[36m✓%c[0m", consoleColorTag, consoleColorTag) +} + +// CheckMark 带颜色的对勾符号 +func CheckMark() string { + return Green("✓") +} + +// CrossMark 带颜色的错误符号 +func CrossMark() string { + return Red("❌") +} + +// BoxHeader 创建box样式的顶部标题 +func BoxHeader(icon, title string) string { + return fmt.Sprintf("┌─ %s %s", icon, title) +} + +// BoxItem 创建box样式的条目 +func BoxItem(item string) string { + return fmt.Sprintf("│ %s %s", CheckMark(), item) +} + +// BoxErrorItem 创建box样式的错误条目 +func BoxErrorItem(item string) string { + return fmt.Sprintf("│ %s %s", CrossMark(), item) +} + +// BoxDetailItem 创建box样式的详情条目 +func BoxDetailItem(item string) string { + return fmt.Sprintf("│ │ %s", item) +} + +// BoxSuccessFooter 创建成功状态的底部 +func BoxSuccessFooter() string { + return "└─ " + Cyan("✓") + " " + Cyan("Complete") +} + +// BoxErrorFooter 创建失败状态的底部 +func BoxErrorFooter() string { + return "└─ " + CrossMark() + " " + Red("Abort") +} diff --git a/cmd/jzero/internal/pkg/console/console_test.go b/cmd/jzero/internal/pkg/console/console_test.go new file mode 100644 index 000000000..da58de0da --- /dev/null +++ b/cmd/jzero/internal/pkg/console/console_test.go @@ -0,0 +1,21 @@ +package console + +import "testing" + +func TestBoxErrorItem(t *testing.T) { + got := BoxErrorItem("desc/api/user.api") + want := "│ " + CrossMark() + " desc/api/user.api" + if got != want { + t.Fatalf("BoxErrorItem() = %q, want %q", got, want) + } +} + +func TestBoxFooters(t *testing.T) { + if got, want := BoxSuccessFooter(), "└─ "+Cyan("✓")+" "+Cyan("Complete"); got != want { + t.Fatalf("BoxSuccessFooter() = %q, want %q", got, want) + } + + if got, want := BoxErrorFooter(), "└─ "+CrossMark()+" "+Red("Abort"); got != want { + t.Fatalf("BoxErrorFooter() = %q, want %q", got, want) + } +} diff --git a/cmd/jzero/internal/pkg/console/error.go b/cmd/jzero/internal/pkg/console/error.go new file mode 100644 index 000000000..7e8f0aabe --- /dev/null +++ b/cmd/jzero/internal/pkg/console/error.go @@ -0,0 +1,69 @@ +package console + +import ( + "errors" + "strings" +) + +// RenderedError marks an error as already rendered in the console UI. +type RenderedError struct { + Err error +} + +func (e *RenderedError) Error() string { + if e == nil || e.Err == nil { + return "" + } + return e.Err.Error() +} + +func (e *RenderedError) Unwrap() error { + if e == nil { + return nil + } + return e.Err +} + +// MarkRenderedError wraps an error so the root command won't print it again. +func MarkRenderedError(err error) error { + if err == nil { + return nil + } + + var rendered *RenderedError + if errors.As(err, &rendered) { + return err + } + + return &RenderedError{Err: err} +} + +// IsRenderedError reports whether the error has already been rendered. +func IsRenderedError(err error) bool { + var rendered *RenderedError + return errors.As(err, &rendered) +} + +// NormalizeErrorLines converts free-form error output into unique display lines. +func NormalizeErrorLines(text string) []string { + text = strings.ReplaceAll(text, "\r\n", "\n") + + lines := strings.Split(text, "\n") + seen := make(map[string]struct{}, len(lines)) + result := make([]string, 0, len(lines)) + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || line == "exit status 1" { + continue + } + line = strings.TrimPrefix(line, "Error: ") + if _, ok := seen[line]; ok { + continue + } + seen[line] = struct{}{} + result = append(result, line) + } + + return result +} diff --git a/cmd/jzero/internal/pkg/console/error_test.go b/cmd/jzero/internal/pkg/console/error_test.go new file mode 100644 index 000000000..e27594400 --- /dev/null +++ b/cmd/jzero/internal/pkg/console/error_test.go @@ -0,0 +1,39 @@ +package console + +import ( + "errors" + "testing" +) + +func TestMarkRenderedError(t *testing.T) { + err := errors.New("boom") + rendered := MarkRenderedError(err) + + if !IsRenderedError(rendered) { + t.Fatalf("expected rendered error marker") + } + + if rendered.Error() != err.Error() { + t.Fatalf("rendered error = %q, want %q", rendered.Error(), err.Error()) + } +} + +func TestNormalizeErrorLines(t *testing.T) { + input := "Error: parse api file: desc/api/user.api\nError: parse api file: desc/api/user.api\n\nexit status 1\nError: find route api files: parse api file: desc/api/user.api" + got := NormalizeErrorLines(input) + + want := []string{ + "parse api file: desc/api/user.api", + "find route api files: parse api file: desc/api/user.api", + } + + if len(got) != len(want) { + t.Fatalf("NormalizeErrorLines() len = %d, want %d; got=%v", len(got), len(want), got) + } + + for i := range want { + if got[i] != want[i] { + t.Fatalf("NormalizeErrorLines()[%d] = %q, want %q", i, got[i], want[i]) + } + } +} diff --git a/cmd/jzero/internal/pkg/console/logo.go b/cmd/jzero/internal/pkg/console/logo.go new file mode 100644 index 000000000..30def982b --- /dev/null +++ b/cmd/jzero/internal/pkg/console/logo.go @@ -0,0 +1,29 @@ +package console + +import ( + "fmt" + "os" + "strings" +) + +// DisplayLogo 显示jzero的logo和版本信息 +func DisplayLogo(version string, toolVersion []string) { + wd, _ := os.Getwd() + + fmt.Println(Red(" ") + Red("_") + Red(" ") + Red("_") + Red("oo") + Red(" ") + Red("wWw") + Red(" ()_()") + Red(" ") + Red(".-.") + Red(" ")) + fmt.Println(Red(" ") + Red("_||") + Red("\\>") + Red("-(") + Red("_") + Red(" \\ ") + Red("(O)_(O o)") + Red(" ") + Red("c(O_O)c") + Red(" ")) + fmt.Println(Red(" ") + Red("(_'\\") + Red(" / ") + Red("_") + Red("/ / ") + Red("__)|^_\\") + Red(" ") + Red(",'.") + Red("---") + Red(".`,") + Red("")) + fmt.Println(Red(" ") + Red("( | / ") + Red("/ / ") + Red("( |(_))") + Red("/ /|_|_|\\ \\") + Red(" ") + Bold(" jzero") + " " + version + Red(" ")) + + if toolVersion != nil { + fmt.Println(Red(" ") + Red("\\ | / (") + Red(" ( _)") + Red(" | /") + Red(" | ") + Red("\\_____/") + Red(" |") + " └─ " + strings.Join(toolVersion, " . ") + Red(" ")) + fmt.Println(Red("") + Red("(\\__)|(") + Red(" `-.") + Red("\\ \\_") + Red(" ") + Red(")|\\ ") + Red(" '. `---' .`") + Red(" ") + wd) + fmt.Println(Red(" ") + Red("`--.) ") + Red("`--.._)") + Red("\\__)(/") + Red(" \\)") + Red(" `-...-'") + Red(" ")) + } else { + fmt.Println(Red(" ") + Red("\\ | / (") + Red(" ( _)") + Red(" | /") + Red(" | ") + Red("\\_____/") + Red(" |") + Red(" ") + wd) + fmt.Println(Red("") + Red("(\\__)|(") + Red(" `-.") + Red("\\ \\_") + Red(" ") + Red(")|\\ ") + Red(" '. `---' .`") + Red(" ")) + fmt.Println(Red(" ") + Red("`--.) ") + Red("`--.._)") + Red("\\__)(/") + Red(" \\)") + Red(" `-...-'") + Red(" ")) + } + + fmt.Println() +} diff --git a/cmd/jzero/internal/pkg/console/progress/box.go b/cmd/jzero/internal/pkg/console/progress/box.go new file mode 100644 index 000000000..c067f94fa --- /dev/null +++ b/cmd/jzero/internal/pkg/console/progress/box.go @@ -0,0 +1,103 @@ +package progress + +import ( + "fmt" + + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" +) + +// StageState tracks the console state for a progress stage. +type StageState struct { + shown bool + hasErrorItem bool +} + +// ConsumeStage renders progress messages until done. +func ConsumeStage(progressChan <-chan Message, done <-chan struct{}, title string, quiet, headerShown bool) StageState { + state := StageState{ + shown: headerShown, + } + + for { + select { + case msg, ok := <-progressChan: + if !ok { + return state + } + renderMessage(msg, title, quiet, &state) + case <-done: + for { + select { + case msg, ok := <-progressChan: + if !ok { + return state + } + renderMessage(msg, title, quiet, &state) + default: + return state + } + } + } + } +} + +// FinishStage renders the footer and any error detail lines. +func FinishStage(title string, quiet bool, state *StageState, err error) { + if quiet { + return + } + + if err != nil { + if !state.hasErrorItem { + if item := ItemFromError(err); item != "" { + if !state.shown { + fmt.Printf("%s\n", console.BoxHeader("", title)) + state.shown = true + } + fmt.Printf("%s\n", console.BoxErrorItem(item)) + state.hasErrorItem = true + } + } + + if !state.shown { + fmt.Printf("%s\n", console.BoxHeader("", title)) + state.shown = true + } + + for _, line := range console.NormalizeErrorLines(err.Error()) { + fmt.Printf("%s\n", console.BoxDetailItem(line)) + } + } + + if !state.shown { + return + } + + if err != nil { + fmt.Printf("%s\n\n", console.BoxErrorFooter()) + return + } + + fmt.Printf("%s\n\n", console.BoxSuccessFooter()) +} + +func renderMessage(msg Message, title string, quiet bool, state *StageState) { + if quiet { + return + } + + if !state.shown { + fmt.Printf("%s\n", console.BoxHeader("", title)) + state.shown = true + } + + switch msg.Type { + case TypeFile: + fmt.Printf("%s\n", console.BoxItem(msg.Value)) + case TypeError: + fmt.Printf("%s\n", console.BoxErrorItem(msg.Value)) + state.hasErrorItem = true + case TypeDebug: + // Debug command lines are intentionally not rendered in progress boxes. + } +} diff --git a/cmd/jzero/internal/pkg/console/progress/display.go b/cmd/jzero/internal/pkg/console/progress/display.go new file mode 100644 index 000000000..01bf8dee0 --- /dev/null +++ b/cmd/jzero/internal/pkg/console/progress/display.go @@ -0,0 +1,23 @@ +package progress + +import ( + "path/filepath" + "regexp" + "strings" +) + +var errorItemPattern = regexp.MustCompile(`([[:alnum:]_./-]+\.(?:api|sql|proto|go|json))`) + +// ItemFromError extracts the first file-like item from an error for progress display. +func ItemFromError(err error) string { + if err == nil { + return "" + } + + match := errorItemPattern.FindString(err.Error()) + if match == "" { + return "" + } + + return filepath.ToSlash(strings.TrimRight(match, ",:")) +} diff --git a/cmd/jzero/internal/pkg/console/progress/display_test.go b/cmd/jzero/internal/pkg/console/progress/display_test.go new file mode 100644 index 000000000..2623df3be --- /dev/null +++ b/cmd/jzero/internal/pkg/console/progress/display_test.go @@ -0,0 +1,13 @@ +package progress + +import ( + "errors" + "testing" +) + +func TestItemFromError(t *testing.T) { + err := errors.New("find route api files: parse api file: desc/api/user.api, err: user.api 12:2 syntax error") + if got, want := ItemFromError(err), "desc/api/user.api"; got != want { + t.Fatalf("ItemFromError() = %q, want %q", got, want) + } +} diff --git a/cmd/jzero/internal/pkg/console/progress/types.go b/cmd/jzero/internal/pkg/console/progress/types.go new file mode 100644 index 000000000..49079d15d --- /dev/null +++ b/cmd/jzero/internal/pkg/console/progress/types.go @@ -0,0 +1,37 @@ +package progress + +// Type represents the type of progress message +type Type int + +const ( + // TypeFile indicates a file is being generated + TypeFile Type = iota + + // TypeDebug indicates debug information + TypeDebug + + // TypeError indicates an error occurred with a file + TypeError +) + +// Message represents a message sent through the progress channel +type Message struct { + Type Type + Value string +} + +// NewFile creates a new file generation progress message +func NewFile(file string) Message { + return Message{ + Type: TypeFile, + Value: file, + } +} + +// NewDebug creates a new debug information progress message +func NewDebug(debug string) Message { + return Message{ + Type: TypeDebug, + Value: debug, + } +} diff --git a/cmd/jzero/internal/pkg/execx/exec.go b/cmd/jzero/internal/pkg/execx/exec.go index f80c554af..7e43e8ea1 100644 --- a/cmd/jzero/internal/pkg/execx/exec.go +++ b/cmd/jzero/internal/pkg/execx/exec.go @@ -2,7 +2,6 @@ package execx import ( "fmt" - "io" "os" "os/exec" "runtime" @@ -10,7 +9,8 @@ import ( "github.com/zeromicro/go-zero/tools/goctl/vars" ) -func Run(arg, dir string, env ...string) error { +// RunOutput runs a command and returns its output +func RunOutput(arg, dir string, env ...string) (string, error) { goos := runtime.GOOS var cmd *exec.Cmd switch goos { @@ -19,7 +19,7 @@ func Run(arg, dir string, env ...string) error { case vars.OsWindows: cmd = exec.Command("cmd.exe", "/c", arg) default: - return fmt.Errorf("unexpected os: %v", goos) + return "", fmt.Errorf("unexpected os: %v", goos) } if len(dir) > 0 { @@ -30,31 +30,6 @@ func Run(arg, dir string, env ...string) error { cmd.Env = append(os.Environ(), env...) } - stdout, err := cmd.StdoutPipe() - if err != nil { - return err - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - - if err := cmd.Start(); err != nil { - return err - } - - go func() { - _, _ = io.Copy(os.Stdout, stdout) - }() - - go func() { - _, _ = io.Copy(os.Stderr, stderr) - }() - - if err := cmd.Wait(); err != nil { - return err - } - - return nil + output, err := cmd.CombinedOutput() + return string(output), err } diff --git a/cmd/jzero/main.go b/cmd/jzero/main.go index 974063839..ec0197b55 100644 --- a/cmd/jzero/main.go +++ b/cmd/jzero/main.go @@ -15,6 +15,8 @@ import ( "strconv" "time" + goversion "github.com/hashicorp/go-version" + "github.com/samber/lo" "github.com/spf13/cobra" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" @@ -35,6 +37,7 @@ import ( "github.com/jzero-io/jzero/cmd/jzero/internal/desc" "github.com/jzero-io/jzero/cmd/jzero/internal/embeded" "github.com/jzero-io/jzero/cmd/jzero/internal/hooks" + "github.com/jzero-io/jzero/cmd/jzero/internal/pkg/console" "github.com/jzero-io/jzero/cmd/jzero/internal/plugin" ) @@ -67,6 +70,27 @@ var rootCmd = &cobra.Command{ Short: `Used to create project by templates and generate server/client code by api/proto/sql file. `, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // Display logo + if os.Getenv("JZERO_HOOK_TRIGGERED") != "true" && os.Getenv("JZERO_FORKED") != "true" && !config.C.Quiet { + console.DisplayLogo(version, lo.If(config.C.Debug, func() []string { + var toolVersion []string + tv := config.C.ToolVersion() + + toolVersion = appendToolVersion(toolVersion, "goctl", tv.GoctlVersion) + + frameType, err := desc.GetFrameType() + cobra.CheckErr(err) + + if frameType == "rpc" || frameType == "gateway" { + toolVersion = appendToolVersion(toolVersion, "protoc", tv.ProtocVersion) + toolVersion = appendToolVersion(toolVersion, "protoc-gen-go", tv.ProtocGenGoVersion) + toolVersion = appendToolVersion(toolVersion, "protoc-gen-go-grpc", tv.ProtocGenGoGrpcVersion) + toolVersion = appendToolVersion(toolVersion, "protoc-gen-openapiv2", tv.ProtocGenOpenapiv2Version) + } + return toolVersion + }()).Else(nil)) + } + // Run environment check first if cmd.Name() != check.GetCommand().Use && cmd.Name() != versioncmd.GetCommand().Use { frameType, err := desc.GetFrameType() @@ -124,7 +148,20 @@ func Execute() { } } } - cobra.CheckErr(rootCmd.Execute()) + if err := rootCmd.Execute(); err != nil { + if console.IsRenderedError(err) { + os.Exit(1) + } + cobra.CheckErr(err) + } +} + +func appendToolVersion(items []string, name string, v *goversion.Version) []string { + if v == nil { + return items + } + + return append(items, fmt.Sprintf("%s v%s", name, v.String())) } func init() { @@ -184,17 +221,9 @@ func InitConfig() { } cobra.CheckErr(config.InitConfig(rootCmd)) + + logx.Disable() if config.C.Debug { - logx.MustSetup(logx.LogConf{Encoding: "plain"}) - logx.SetLevel(logx.DebugLevel) - if config.C.DebugSleepTime > 0 { - logx.Debugf("using jzero frame debug mode, please wait time.Sleep(time.Second * %d)", config.C.DebugSleepTime) - } else { - logx.Debugf("using jzero frame debug mode") - } time.Sleep(time.Duration(config.C.DebugSleepTime) * time.Second) - logx.Debugf("get config: %#v", config.C) - } else { - logx.Disable() } }