package main import ( "bytes" "embed" _ "embed" "fmt" "github.com/urfave/cli/v2" "golang.org/x/text/cases" "golang.org/x/text/language" "os" "os/exec" "runtime" "strings" "text/template" ) //go:embed templates/proto/application.proto.tmpl var protoTemplate string //go:embed templates/backend/main.go.tmpl var mainGoTemplate string //go:embed proto_include/* var protoInclude embed.FS func createCmd() *cli.Command { return &cli.Command{ Name: "create", Aliases: []string{"c"}, Usage: "Create a new app in a directory with the given name", Description: "This command will create a new folder with the given name and generate a new app in that folder.", Category: "generator", Flags: []cli.Flag{ &cli.StringFlag{ Name: "name", Usage: "The name of the app to create", Required: true, Aliases: []string{"n"}, }, }, Action: func(c *cli.Context) error { applicationName := c.String("name") fmt.Printf("Creating app: %v\n", applicationName) // make a directory with the given name from the working directory err := os.Mkdir(applicationName, 0755) if err != nil { return fmt.Errorf("error creating app directory | %w", err) } // generate the app in the new directory // cd into the new directory err = os.Chdir(applicationName) if err != nil { return fmt.Errorf("error changing directory | %w", err) } // initialize a go module cmd := exec.Command("go", "mod", "init", applicationName) err = cmd.Run() if err != nil { return fmt.Errorf("error initializing go module | %w", err) } // create a directory to proto files err = os.Mkdir("proto", 0755) if err != nil { return fmt.Errorf("error creating proto directory | %w", err) } // create a directory to store the generated code err = os.Mkdir("gen", 0755) if err != nil { return fmt.Errorf("error creating gen directory | %w", err) } // create a directory for generated go code err = os.Mkdir("gen/go", 0755) if err != nil { return fmt.Errorf("error creating gen/go directory | %w", err) } // create a directory for generated openapi code err = os.Mkdir("gen/openapi", 0755) if err != nil { return fmt.Errorf("error creating gen/openapi directory | %w", err) } // create a main.go file mainFile, err := os.Create("main.go") if err != nil { return fmt.Errorf("error creating main.go file | %w", err) } defer mainFile.Close() titleMaker := cases.Title(language.English) // render the main.go file from the template goTemplate := template.Must(template.New("main").Parse(mainGoTemplate)) err = goTemplate.Execute(mainFile, map[string]string{"AppName": strings.ToLower(applicationName), "AppNameCaps": titleMaker.String(applicationName)}) if err != nil { return fmt.Errorf("error rendering main.go file | %w", err) } // create a proto file protoFile, err := os.Create("proto/service.proto") if err != nil { return fmt.Errorf("error creating proto file | %w", err) } defer protoFile.Close() // render the proto file from the template t := template.Must(template.New("proto").Parse(protoTemplate)) err = t.Execute(protoFile, map[string]string{"AppName": strings.ToLower(applicationName), "AppNameCaps": titleMaker.String(applicationName), "ObjName": "Product"}) if err != nil { return fmt.Errorf("error rendering proto file | %w", err) } err = os.CopyFS("./", protoInclude) if err != nil { return fmt.Errorf("error copying proto include files | %w", err) } // set up the webapp err = setupWebapp("webapp") // since the app is already in its own named folder, we name it webapp if err != nil { return fmt.Errorf("error setting up webapp | %w", err) } return nil }, } } func generateCmd() *cli.Command { return &cli.Command{ Name: "generate", Aliases: []string{"g"}, Usage: "Generate code from proto files", Category: "generator", Description: "This command will generate code from the proto files in the proto directory and place them in a language folder in the gen folder.", Action: func(c *cli.Context) error { fmt.Println("Generating code...") protocArgs := []string{ "-I", ".", "--go_out", "gen/go", "--go-grpc_out", "gen/go", "--go-grpc_opt=require_unimplemented_servers=false", "--gorm_out", "gen/go", "--grpc-gateway_out", "gen/go", "--grpc-gateway_opt", "logtostderr=true", "--openapiv2_out", "gen/openapi", "--openapiv2_opt", "logtostderr=true", "--proto_path=./proto_include", "proto/*.proto", } // generate go code cmd := exec.Command( "protoc", protocArgs..., ) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { var buff bytes.Buffer cmd.Stderr = &buff fmt.Println(buff.String()) return fmt.Errorf("error generating go code | %w", err) } // Generate ts code // if webapp folder is present, generate typescript code if _, err := os.Stat("webapp"); err == nil { err = os.Chdir("webapp") if err != nil { return fmt.Errorf("error changing directory to webapp | %w", err) } cmd = exec.Command("npx", "openapi-typescript-codegen", "--input", "../gen/openapi/proto/service.swagger.json", "--output", "src/generated", "--client", "fetch", ) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return fmt.Errorf("error generating typescript code | %w", err) } // if on windows if runtime.GOOS == "windows" { //cmd = exec.Command("npx", "protoc", "--plugin=protoc-gen-ts_proto=.\\node_modules\\.bin\\protoc-gen-ts_proto.cmd", "--ts_proto_out=./src/generated", "--ts_proto_opt=outputServices=generic,esModuleInterop=true", "--proto_path=../proto", "--proto_path=../include", "../proto/service.proto") //cmd.Stdout = os.Stdout //cmd.Stderr = os.Stderr //err = cmd.Run() //if err != nil { // return fmt.Errorf("error generating typescript code | %w", err) //} } else { //cmd = exec.Command("npx", "protoc", "--plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto", "--ts_proto_out=./src/generated", "--ts_proto_opt=outputServices=generic,esModuleInterop=true", "--proto_path=../proto", "--proto_path=../include", "../proto/service.proto") //cmd.Stdout = os.Stdout //cmd.Stderr = os.Stderr //err = cmd.Run() //if err != nil { // return fmt.Errorf("error generating typescript code | %w", err) //} } err = os.Chdir("..") if err != nil { return fmt.Errorf("error changing directory back to root | %w", err) } } // TODO: update typescript code gen to use this command `npx openapi-typescript-codegen --input ../gen/openapi/proto/service.swagger.json --output src/generated/ts-client --client fetch` return nil }, } } func webappCmd() *cli.Command { return &cli.Command{ Name: "webapp", Aliases: []string{"w"}, Flags: []cli.Flag{ &cli.StringFlag{ Name: "name", Aliases: []string{"n"}, Usage: "The name of the webapp to create, this will be the name of the directory", Required: true, }, }, Usage: "Set up a webapp", Description: "This command will set up a webapp ", Category: "generator", Action: func(c *cli.Context) error { fmt.Println("Setting up webapp named: ", c.String("name")) name := c.String("name") err := setupWebapp(name) if err != nil { return fmt.Errorf("error setting up webapp | %w", err) } return nil }, } } func tailwindCmd() *cli.Command { return &cli.Command{ Name: "tailwind", Aliases: []string{"t"}, Usage: "Set up tailwindcss in a vite webapp, if you built using masonry then this is already done for you", Action: func(c *cli.Context) error { fmt.Println("Setting up tailwindcss") err := setupTailwind() if err != nil { return fmt.Errorf("error setting up tailwindcss | %w", err) } return nil }, } }