package cmd import ( "fmt" "io/fs" "os" "path/filepath" "strings" "git.fernandovideira.com/FernandoJVideira/draft/assets" "github.com/spf13/cobra" ) func init() { newCmd.AddCommand(apiCmd) } var apiCmd = &cobra.Command{ Use: "api", Short: "Create a new Draft API project", Long: `Create a new Draft API project with the necessary structure and files.`, Run: genAPI, } func genAPI(cmd *cobra.Command, args []string) { runNewProject(scaffoldRecipe{label: "API"}) } func scaffoldAPI(projectDir, destSubdir, moduleName, serviceName string, useGRPC bool) error { destRoot := filepath.Join(projectDir, destSubdir) err := os.MkdirAll(destRoot, 0755) if err != nil { return fmt.Errorf("error creating project directory: %w", err) } root := "embedded/api" err = fs.WalkDir(assets.EmbedFS, root, func(path string, d fs.DirEntry, err error) error { relPath, _ := filepath.Rel(root, path) if !useGRPC { // If User chose HTTP: Remove gRPC stuff if strings.HasPrefix(relPath, "pb") || strings.HasPrefix(relPath, "proto") || strings.Contains(relPath, "handlers/grpc") || strings.HasPrefix(relPath, "docs/statik") || strings.HasPrefix(relPath, "docs/swagger") { return nil } } else { // If User chose gRPC: Remove HTTP stuff if strings.Contains(relPath, "handlers/http") { return nil } } if strings.HasSuffix(relPath, "cmd/api/main.go") { return nil } if d.IsDir() { return nil } contentBytes, err := assets.EmbedFS.ReadFile(path) if err != nil { return fmt.Errorf("error reading embedded file %s: %w", path, err) } content := string(contentBytes) targetPath := filepath.Join(destRoot, relPath) targetPath = strings.TrimSuffix(targetPath, ".tmpl") if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { return fmt.Errorf("error creating directory %s: %w", filepath.Dir(targetPath), err) } newContent := strings.ReplaceAll(content, oldModuleName, moduleName) newContent = strings.ReplaceAll(newContent, "{{ .ModuleName }}", moduleName) newContent = strings.ReplaceAll(newContent, "AppTmpl", serviceName) return os.WriteFile(targetPath, []byte(newContent), 0644) }) if err != nil { return fmt.Errorf("error copying template files: %w", err) } if useGRPC { if err := regenerateProto(destRoot, serviceName); err != nil { return fmt.Errorf("error regenerating proto: %w", err) } } // Generate main.go from template return genFile( "templates/main.go.tpl", "main", filepath.Join(destRoot, "cmd/api", "main.go"), MainTemplateData{ ModuleName: moduleName, ServiceName: serviceName, UseGRPC: useGRPC, }, ) } func regenerateProto(projectDir, serviceName string) error { protoPath := filepath.Join(projectDir, "proto", fmt.Sprintf("service_%s.proto", oldModuleName)) content, err := os.ReadFile(protoPath) if err != nil { return fmt.Errorf("error reading service proto: %w", err) } newContent := strings.Replace(string(content), "service AppTmpl {", "service "+serviceName+" {", 1) if err := os.WriteFile(protoPath, []byte(newContent), 0644); err != nil { return fmt.Errorf("error rewriting service proto: %w", err) } protoFiles, err := filepath.Glob(filepath.Join(projectDir, "proto", "*.proto")) if err != nil { return fmt.Errorf("error globbing proto files: %w", err) } args := []string{ "--proto_path=proto", "--go_out=pb", "--go_opt=paths=source_relative", "--go-grpc_out=pb", "--go-grpc_opt=paths=source_relative", "--grpc-gateway_out=pb", "--grpc-gateway_opt=paths=source_relative", "--openapiv2_out=docs/swagger", "--openapiv2_opt=allow_merge=true,merge_file_name=" + filepath.Base(projectDir), } for _, f := range protoFiles { rel, _ := filepath.Rel(projectDir, f) args = append(args, rel) } if err := runCommand(projectDir, "protoc", args...); err != nil { return fmt.Errorf("error running protoc: %w", err) } return runCommand(projectDir, "statik", "-src=./docs/swagger", "-dest=./docs") } func installTools(useGrpc bool) error { tools := []string{ "github.com/air-verse/air@latest", "github.com/golangci/golangci-lint/cmd/golangci-lint@latest", "go.uber.org/mock/mockgen@latest", "github.com/sqlc-dev/sqlc/cmd/sqlc@latest", "github.com/golang-migrate/migrate/v4/cmd/migrate@latest", } if useGrpc { tools = append(tools, "google.golang.org/protobuf/cmd/protoc-gen-go@latest", "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest", "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest", "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest", ) } else { tools = append(tools, "github.com/swaggo/swag/cmd/swag@latest", ) } for _, tool := range tools { if err := runCommand("", "go", "install", tool); err != nil { return fmt.Errorf("error installing tool %s: %w", tool, err) } } return nil }