From 05f6bf2a8658cc6153c7864848321f32eb0e8a67 Mon Sep 17 00:00:00 2001 From: Saurav Upadhyay Date: Fri, 5 Dec 2025 16:57:12 +0000 Subject: [PATCH 1/8] add: yamlParsing --- cmd/new.go | 134 +++++++++++++++-- go.mod | 41 ++++- go.sum | 84 ++++++++++- pkg/addons/dbAddOne.go | 113 ++++++++++++++ pkg/framework/framework.go | 140 ++++++++++++++++++ pkg/parser/yamlParsing.go | 31 ++++ pkg/plugins/plugins.go | 1 + project.yaml | 13 ++ templates/common/Makefile.tmpl | 2 +- templates/common/env.tmpl | 8 + templates/common/golang-ci.yml.tmpl | 12 ++ templates/db/database/database.go.tmpl | 80 ++++++++++ templates/db/mongo/db.go.tmpl | 34 ----- templates/db/mongo/docker-compose.yml.tmpl | 15 ++ templates/db/mysql/db.go.tmpl | 27 ---- templates/db/mysql/docker-compose.yml.tmpl | 17 +++ templates/db/postgres/db.go.tmpl | 27 ---- templates/db/postgres/docker-compose.yml.tmpl | 15 ++ templates/db/sqllite/db.go.tmpl | 26 ---- templates/embed.go | 7 +- templates/rest/chi/cmd/main.go.tmpl | 28 ---- templates/rest/chi/go.mod.tmpl | 5 - .../rest/chi/internal/config/config.go.tmpl | 20 --- .../chi/internal/handler/user_handler.go.tmpl | 16 -- .../rest/chi/internal/router/routes.go.tmpl | 11 -- templates/rest/clean/cmd/main.go.tmpl | 58 ++++++++ templates/rest/clean/docker-compose.yml.tmpl | 15 ++ templates/rest/{gin => clean}/go.mod.tmpl | 0 .../internal/config/config.go.tmpl | 0 .../internal/handler/example_handler.go.tmpl | 19 +++ .../rest/clean/internal/server/routes.go.tmpl | 28 ++++ .../rest/clean/internal/server/server.go.tmpl | 37 +++++ templates/rest/gin/cmd/main.go.tmpl | 36 ----- .../gin/internal/handler/user_handler.go.tmpl | 32 ---- .../rest/gin/internal/router/routes.go.tmpl | 15 -- 35 files changed, 846 insertions(+), 301 deletions(-) create mode 100644 pkg/addons/dbAddOne.go create mode 100644 pkg/framework/framework.go create mode 100644 pkg/parser/yamlParsing.go create mode 100644 pkg/plugins/plugins.go create mode 100644 project.yaml create mode 100644 templates/common/env.tmpl create mode 100644 templates/common/golang-ci.yml.tmpl create mode 100644 templates/db/database/database.go.tmpl delete mode 100644 templates/db/mongo/db.go.tmpl create mode 100644 templates/db/mongo/docker-compose.yml.tmpl delete mode 100644 templates/db/mysql/db.go.tmpl create mode 100644 templates/db/mysql/docker-compose.yml.tmpl delete mode 100644 templates/db/postgres/db.go.tmpl create mode 100644 templates/db/postgres/docker-compose.yml.tmpl delete mode 100644 templates/db/sqllite/db.go.tmpl delete mode 100644 templates/rest/chi/cmd/main.go.tmpl delete mode 100644 templates/rest/chi/go.mod.tmpl delete mode 100644 templates/rest/chi/internal/config/config.go.tmpl delete mode 100644 templates/rest/chi/internal/handler/user_handler.go.tmpl delete mode 100644 templates/rest/chi/internal/router/routes.go.tmpl create mode 100644 templates/rest/clean/cmd/main.go.tmpl create mode 100644 templates/rest/clean/docker-compose.yml.tmpl rename templates/rest/{gin => clean}/go.mod.tmpl (100%) rename templates/rest/{gin => clean}/internal/config/config.go.tmpl (100%) create mode 100644 templates/rest/clean/internal/handler/example_handler.go.tmpl create mode 100644 templates/rest/clean/internal/server/routes.go.tmpl create mode 100644 templates/rest/clean/internal/server/server.go.tmpl delete mode 100644 templates/rest/gin/cmd/main.go.tmpl delete mode 100644 templates/rest/gin/internal/handler/user_handler.go.tmpl delete mode 100644 templates/rest/gin/internal/router/routes.go.tmpl diff --git a/cmd/new.go b/cmd/new.go index 4af1e67..f1005ad 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -14,8 +14,11 @@ import ( "path/filepath" "strings" "text/template" + "unicode" "github.com/spf13/cobra" + "github.com/upsaurav12/bootstrap/pkg/addons" + "github.com/upsaurav12/bootstrap/pkg/framework" "github.com/upsaurav12/bootstrap/templates" ) @@ -46,6 +49,42 @@ var projectType string var projectPort string var projectRouter string var DBType string +var YAMLPath string +var Entitys string + +type TemplateData struct { + ModuleName string + PortName string + DBType string + Imports string + Start string + Entity string + ContextName string + ContextType string + Router string + Bind string + JSON string + LowerEntity string + OtherImports string + ApiGroup func(entity string, get string, lowerentity string) string + Get string + FullContext string + ToTheClient string + Response string + ImportHandler string + ImportRouter string + ServiceName string + Image string + Environment string + Port string + Volume string + VolumeName string + DBName string + DBEnvPrefix string + Import string + Driver string + DSN string +} func init() { // Add the new command to the rootCmd @@ -56,6 +95,8 @@ func init() { newCmd.Flags().StringVar(&projectPort, "port", "", "port of the project") newCmd.Flags().StringVar(&projectRouter, "router", "", "router of the project") newCmd.Flags().StringVar(&DBType, "db", "", "data type of the project") + newCmd.Flags().StringVar(&YAMLPath, "yaml", "", "yaml file path") + newCmd.Flags().StringVar(&Entitys, "entity", "", "entity") } func createNewProject(projectName string, projectRouter string, template string, out io.Writer) { @@ -64,7 +105,8 @@ func createNewProject(projectName string, projectRouter string, template string, fmt.Fprintf(out, "Error creating directory: %v\n", err) return } - // Print the template that was passed + + os.MkdirAll(filepath.Join(projectName, "internal"), 0755) // Always add README + Makefile from common renderTemplateDir("common", projectName, TemplateData{ @@ -72,10 +114,37 @@ func createNewProject(projectName string, projectRouter string, template string, PortName: projectPort, }) - renderTemplateDir("rest"+"/"+projectRouter, projectName, TemplateData{ - ModuleName: projectName, - PortName: projectPort, - DBType: DBType, + var uppercase string + if len(Entitys) > 0 { + runes := []rune(Entitys) + runes[0] = unicode.ToUpper(runes[0]) + + uppercase = string(runes) + } + + frameworkConfig := framework.FrameworkRegistory[projectRouter] + + renderTemplateDir("rest/clean", projectName, TemplateData{ + ModuleName: projectName, + PortName: projectPort, + DBType: DBType, + Imports: frameworkConfig.Imports, + Start: frameworkConfig.Start, + ContextName: frameworkConfig.ContextName, + ContextType: frameworkConfig.ContextType, + Entity: uppercase, + Router: frameworkConfig.Router, + Bind: frameworkConfig.Bind, + JSON: frameworkConfig.JSON, + LowerEntity: Entitys, + OtherImports: frameworkConfig.OtherImports, + ApiGroup: frameworkConfig.ApiGroup, + Get: frameworkConfig.Get, + FullContext: frameworkConfig.FullContext, + ToTheClient: frameworkConfig.ToTheClient, + Response: frameworkConfig.Response, + ImportHandler: frameworkConfig.ImportHandler, + ImportRouter: frameworkConfig.ImportRouter, }) if err != nil { @@ -84,12 +153,31 @@ func createNewProject(projectName string, projectRouter string, template string, } if DBType != "" { + dbConfig := addons.DbRegistory[DBType] + dbTemplatePath := "db/" + DBType - err := renderTemplateDir(dbTemplatePath, filepath.Join(projectName, "internal", "db"), TemplateData{ + + err := renderTemplateDir(dbTemplatePath, filepath.Join(projectName), TemplateData{ ModuleName: projectName, PortName: projectPort, DBType: DBType, }) + err = renderTemplateDir("db/database", filepath.Join(projectName, "internal", "db"), TemplateData{ + ModuleName: projectName, + PortName: projectPort, + DBType: DBType, + ServiceName: dbConfig.ServiceName, + DBName: dbConfig.DBName, + DBEnvPrefix: dbConfig.DBEnvPrefix, + Port: dbConfig.Port, + DSN: dbConfig.DSN, + Driver: dbConfig.Driver, + Import: dbConfig.Import, + Image: dbConfig.Image, + Environment: dbConfig.Environment, + Volume: dbConfig.Volume, + VolumeName: dbConfig.VolumeName, + }) if err != nil { fmt.Fprintf(out, "Error rendering DB templates: %v\n", err) @@ -102,10 +190,10 @@ func createNewProject(projectName string, projectRouter string, template string, fmt.Fprintf(out, "✓ Created '%s' successfully\n", projectName) } -type TemplateData struct { - ModuleName string - PortName string - DBType string +func IsHidden(path string) (bool, error) { + // Unix hidden check + name := filepath.Base(path) + return strings.HasPrefix(name, "."), nil } func renderTemplateDir(templatePath, destinationPath string, data TemplateData) error { @@ -114,28 +202,44 @@ func renderTemplateDir(templatePath, destinationPath string, data TemplateData) return err } - // Compute relative path (remove the base templatePath) relPath, _ := filepath.Rel(templatePath, path) - targetPath := filepath.Join(destinationPath, strings.TrimSuffix(relPath, ".tmpl")) if d.IsDir() { - return os.MkdirAll(targetPath, 0755) + return os.MkdirAll(filepath.Join(destinationPath, relPath), 0755) } - // ✅ Important: use full `path` for ReadFile + // Read template file content, err := templates.FS.ReadFile(path) if err != nil { return err } + // Compute dynamic output filename + fileName := strings.TrimSuffix(relPath, ".tmpl") + + if templatePath == "common" { + base := filepath.Base(fileName) + if base == "env" || base == "golang-ci.yml" { + fileName = "." + fileName + } + } + // fmt.Println("patg: ", path) + + // 🔥 dynamic entity replacement + fileName = strings.ReplaceAll(fileName, "example", strings.ToLower(data.Entity)) + // fileName = strings.ReplaceAll(fileName, "Example", upperFirst(data.Entity)) + + targetPath := filepath.Join(destinationPath, fileName) + // Parse template tmpl, err := template.New(filepath.Base(path)).Parse(string(content)) if err != nil { return err } - // Write file + // Write output file outFile, err := os.Create(targetPath) + // fmt.Println("targetPath : ", targetPath, data) if err != nil { return err } diff --git a/go.mod b/go.mod index 2b5bc97..b25ff21 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,46 @@ module github.com/upsaurav12/bootstrap go 1.23.6 require ( + github.com/gin-gonic/gin v1.11.0 + github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/stretchr/testify v1.10.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect ) diff --git a/go.sum b/go.sum index 725c027..2be9636 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,97 @@ +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/addons/dbAddOne.go b/pkg/addons/dbAddOne.go new file mode 100644 index 0000000..98247bd --- /dev/null +++ b/pkg/addons/dbAddOne.go @@ -0,0 +1,113 @@ +package addons + +type DbAddOneConfig struct { + ServiceName string + Image string + Environment string + Port string + Volume string + VolumeName string + DBName string + DBEnvPrefix string + Import string + Driver string + DSN string + OutputFile string +} + +var DbRegistory = map[string]DbAddOneConfig{ + "postgres": { + ServiceName: "postgres_bp", + Image: "postgres:latest", + Environment: ` POSTGRES_DB: ${GONE_DB_DATABASE} + POSTGRES_USER: ${GONE_DB_USERNAME} + POSTGRES_PASSWORD: ${GONE_DB_PASSWORD}`, + Port: "5432", + Volume: "postgres_volume_bp:/var/lib/postgresql/data", + VolumeName: "postgres_volume_bp", + DBName: "PostgreSQL", + DBEnvPrefix: "BLUEPRINT", + Import: `_ "github.com/jackc/pgx/v5/stdlib"`, + Driver: "pgx", + DSN: "postgres://%s:%s@%s:%s/%s?sslmode=disable&search_path=%s", + }, + + "mysql": { + ServiceName: "mysql_bp", + Image: "mysql:8", + Environment: ` + MYSQL_DATABASE: ${GONE_DB_DATABASE} + MYSQL_USER: ${GONE_DB_USERNAME} + MYSQL_PASSWORD: ${GONE_DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${GONE_DB_PASSWORD}`, + Port: "3306", + Volume: "mysql_volume_bp:/var/lib/mysql", + VolumeName: "mysql_volume_bp", + DBName: "MySQL", + DBEnvPrefix: "BLUEPRINT", + Import: `_ "github.com/go-sql-driver/mysql"`, + Driver: "mysql", + DSN: "%s:%s@tcp(%s:%s)/%s", + OutputFile: "mysql.go", + }, + + "mongodb": { + ServiceName: "mongo_bp", + Image: "mongo:latest", + Environment: ` + MONGO_INITDB_DATABASE: ${GONE_DB_DATABASE} + MONGO_INITDB_ROOT_USERNAME: ${GONE_DB_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${GONE_DB_PASSWORD}`, + Port: "27017", + Volume: "mongo_volume_bp:/data/db", + VolumeName: "mongo_volume_bp", + }, + + "sqlite": { + ServiceName: "sqlite_bp", + Image: "alpine:latest", + Environment: ` + # SQLite has no environment variables`, + Port: "0", // no port needed + Volume: "sqlite_volume_bp:/data", + VolumeName: "sqlite_volume_bp", + DBName: "SQLite", + DBEnvPrefix: "BLUEPRINT", + Import: `_ "modernc.org/sqlite"`, + Driver: "sqlite", + DSN: "file:%s.db?_pragma=journal_mode(WAL)", + }, + + "cockroachdb": { + ServiceName: "cockroach_bp", + Image: "cockroachdb/cockroach:latest", + Environment: ` + # CockroachDB has no env vars needed in insecure mode`, + Port: "26257", + Volume: "cockroach_volume_bp:/cockroach/cockroach-data", + VolumeName: "cockroach_volume_bp", + DBName: "CockroachDB", + DBEnvPrefix: "BLUEPRINT", + Import: `_ "github.com/jackc/pgx/v5/stdlib"`, + Driver: "pgx", + DSN: "postgres://%s:%s@%s:%s/%s?sslmode=disable", + }, + + "mariadb": { + ServiceName: "mariadb_bp", + Image: "mariadb:latest", + Environment: ` + MARIADB_DATABASE: ${GONE_DB_DATABASE} + MARIADB_USER: ${GONE_DB_USERNAME} + MARIADB_PASSWORD: ${GONE_DB_PASSWORD} + MARIADB_ROOT_PASSWORD: ${GONE_DB_PASSWORD}`, + Port: "3306", + Volume: "mariadb_volume_bp:/var/lib/mysql", + VolumeName: "mariadb_volume_bp", + DBName: "MariaDB", + DBEnvPrefix: "BLUEPRINT", + Import: `_ "github.com/go-sql-driver/mysql"`, + Driver: "mysql", + DSN: "%s:%s@tcp(%s:%s)/%s", + }, +} diff --git a/pkg/framework/framework.go b/pkg/framework/framework.go new file mode 100644 index 0000000..119c7ba --- /dev/null +++ b/pkg/framework/framework.go @@ -0,0 +1,140 @@ +package framework + +import "fmt" + +type Framework interface { + Name() string + AddImports() error + Generate() error +} + +type FrameworkConfig struct { + Imports string + Entity string + ContextName string + ContextType string + Bind string + JSON string + Router string + Start string + OtherImports string + ApiGroup func(entity string, get string, lowerentity string) string + Get string + FullContext string + ToTheClient string + Response string + ImportRouter string + ImportHandler string +} + +var FrameworkRegistory = map[string]FrameworkConfig{ + "gin": { + Imports: `"github.com/gin-gonic/gin"`, + ContextName: "c", + ContextType: "*gin.Context", + Bind: "c.BindJSON", // func(obj any) error + JSON: "c.JSON", // func(code int, obj any) + Router: "*gin.Engine", + Start: "gin.Default()", + OtherImports: `"net/http"`, + FullContext: "c *gin.Context", + ApiGroup: func(entity string, get string, lowerentity string) string { + apiGroup := fmt.Sprintf(` + api := r.Group("/api/v1") + { + %s := api.Group("/%s") + { + %s.%s("", handler.Get%ss) + } + } + `, lowerentity, lowerentity, lowerentity, get, entity) + + return apiGroup + }, + Get: "GET", + ToTheClient: "c.JSON(http.StatusOK, ", + Response: "(http.StatusOK,", + ImportRouter: ` + "net/http" + "github.com/gin-gonic/gin" + `, + ImportHandler: ` + "github.com/gin-gonic/gin" + "net/http" + + `, + }, + + "chi": { + Imports: `"github.com/go-chi/chi/v5"`, + ContextName: "r", + ContextType: "http.ResponseWriter, *http.Request", // chi passes both + Bind: "json.NewDecoder(r.Body).Decode", // need encoding/json + JSON: "render.JSON", // from go-chi/render + Router: "chi.Router", + Start: "chi.NewRouter()", + OtherImports: ` + "encoding/json" + "github.com/go-chi/render" + "net/http" + `, + ApiGroup: func(entity string, get string, lowerentity string) string { + apiGroup := fmt.Sprintf(` + r.Group(func(r chi.Router) { + r.%s("/%s", handler.Get%ss) + }) + `, get, lowerentity, entity) + + return apiGroup + }, + Get: "Get", + FullContext: "w http.ResponseWriter, r *http.Request", + ToTheClient: "json.NewEncoder(w).Encode(", + Response: "(w, r,", + ImportRouter: ` + "encoding/json" + "net/http" + "github.com/go-chi/chi/v5" + `, + ImportHandler: ` + "net/http" + "github.com/go-chi/render" + `, + }, + + "echo": { + Imports: `"github.com/labstack/echo/v4"`, + ContextName: "c", + ContextType: "*echo.Context", + Bind: "c.Bind", + JSON: "c.JSON", + Router: "*echo.Echo", + Start: "echo.New()", + OtherImports: `"net/http"`, + }, + + "fiber": { + Imports: `"github.com/gofiber/fiber/v2"`, + ContextName: "c", + ContextType: "*fiber.Ctx", + Bind: "c.BodyParser", // BodyParser(obj) + JSON: "c.JSON", // JSON(obj) + Router: "*fiber.App", + Start: "fiber.New()", + OtherImports: "", + }, + + "mux": { + Imports: `"github.com/gorilla/mux"`, + ContextName: "w, r", + ContextType: "http.ResponseWriter, *http.Request", + Bind: "json.NewDecoder(r.Body).Decode", + JSON: `json.NewEncoder(w).Encode`, + Router: "*mux.Router", + Start: "mux.NewRouter()", + OtherImports: ` + "encoding/json" + "net/http" + `, + }, +} diff --git a/pkg/parser/yamlParsing.go b/pkg/parser/yamlParsing.go new file mode 100644 index 0000000..a76a97d --- /dev/null +++ b/pkg/parser/yamlParsing.go @@ -0,0 +1,31 @@ +package parser + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +type ProjectYAML struct { + Name string `json:"name" yaml:"name"` + Layers []string `json:"layers" yaml:"layers"` + Features []string `json:"features" yaml:"features"` +} + +func ReadYAML(yamlPath string) (*ProjectYAML, error) { + yamlByte, err := os.ReadFile(yamlPath) + if err != nil { + return &ProjectYAML{}, err + } + + var yamlProject ProjectYAML + + err = yaml.Unmarshal(yamlByte, &yamlProject) + if err != nil { + fmt.Println("error: ", err) + return &ProjectYAML{}, err + } + + return &yamlProject, nil +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go new file mode 100644 index 0000000..d5c343e --- /dev/null +++ b/pkg/plugins/plugins.go @@ -0,0 +1 @@ +package plugins diff --git a/project.yaml b/project.yaml new file mode 100644 index 0000000..eb6dc58 --- /dev/null +++ b/project.yaml @@ -0,0 +1,13 @@ + +layers: + - domain + - repository + - service + - http + - config + +features: + - jwt + - redis-cache + - metrics + - tracing diff --git a/templates/common/Makefile.tmpl b/templates/common/Makefile.tmpl index 2c7bea6..af53a03 100644 --- a/templates/common/Makefile.tmpl +++ b/templates/common/Makefile.tmpl @@ -1,5 +1,5 @@ # Project variables -APP_NAME := {.ModuleName} +APP_NAME := {{.ModuleName}} BIN_DIR := bin MAIN_FILE := ./cmd/main.go PKG := ./... diff --git a/templates/common/env.tmpl b/templates/common/env.tmpl new file mode 100644 index 0000000..6163151 --- /dev/null +++ b/templates/common/env.tmpl @@ -0,0 +1,8 @@ +PORT={{.PortName}} +APP_ENV=local +GONE_DB_HOST=localhost +GONE_DB_PORT=5431 +GONE_DB_DATABASE=gone +GONE_DB_USERNAME=example_username +GONE_DB_PASSWORD=password1234 +GONE_DB_SCHEMA=public \ No newline at end of file diff --git a/templates/common/golang-ci.yml.tmpl b/templates/common/golang-ci.yml.tmpl new file mode 100644 index 0000000..abd171c --- /dev/null +++ b/templates/common/golang-ci.yml.tmpl @@ -0,0 +1,12 @@ +run: + timeout: 5m + tests: true + +linters: + enable: + - govet + - errcheck + - staticcheck + - gofmt + - ineffassign + - unused diff --git a/templates/db/database/database.go.tmpl b/templates/db/database/database.go.tmpl new file mode 100644 index 0000000..466c59a --- /dev/null +++ b/templates/db/database/database.go.tmpl @@ -0,0 +1,80 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + "log" + "os" + "strconv" + "time" + + {{.Import}} + _ "github.com/joho/godotenv/autoload" +) + +// Service represents a service that interacts with a database. +type Service interface { + Health() map[string]string + Close() error +} + +type service struct { + db *sql.DB +} + +var ( + database = os.Getenv("BLUEPRINT_DB_DATABASE") + password = os.Getenv("BLUEPRINT_DB_PASSWORD") + username = os.Getenv("BLUEPRINT_DB_USERNAME") + port = os.Getenv("BLUEPRINT_DB_PORT") + host = os.Getenv("BLUEPRINT_DB_HOST") + schema = os.Getenv("BLUEPRINT_DB_SCHEMA") + dbInstance *service +) + + +func New() Service { + if dbInstance != nil { + return dbInstance + } + + connStr := fmt.Sprintf("{{.DSN}}", username, password, host, port, database, schema) + + db, err := sql.Open("{{.Driver}}", connStr) + if err != nil { + log.Fatal(err) + } + + dbInstance = &service{db: db} + return dbInstance +} + +func (s *service) Health() map[string]string { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + stats := make(map[string]string) + + err := s.db.PingContext(ctx) + if err != nil { + stats["status"] = "down" + stats["error"] = fmt.Sprintf("db down: %v", err) + return stats + } + + stats["status"] = "up" + stats["message"] = "healthy" + + dbStats := s.db.Stats() + stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) + stats["in_use"] = strconv.Itoa(dbStats.InUse) + stats["idle"] = strconv.Itoa(dbStats.Idle) + + return stats +} + +func (s *service) Close() error { + log.Printf("Disconnected from {{.DBName}} database: %s", database) + return s.db.Close() +} diff --git a/templates/db/mongo/db.go.tmpl b/templates/db/mongo/db.go.tmpl deleted file mode 100644 index ec2a96f..0000000 --- a/templates/db/mongo/db.go.tmpl +++ /dev/null @@ -1,34 +0,0 @@ -package db - -import ( - "context" - "log" - "time" - - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var Client *mongo.Client - -func Connect() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) - if err != nil { - log.Fatalf("✖ Failed to connect to MongoDB: %v", err) - } - - Client = client - log.Println("✓ Connected to MongoDB") -} - -func Close() { - if Client != nil { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - Client.Disconnect(ctx) - log.Println("🔌 MongoDB connection closed") - } -} diff --git a/templates/db/mongo/docker-compose.yml.tmpl b/templates/db/mongo/docker-compose.yml.tmpl new file mode 100644 index 0000000..e07d532 --- /dev/null +++ b/templates/db/mongo/docker-compose.yml.tmpl @@ -0,0 +1,15 @@ +services: + mongo_bp: + image: mongo:latest + restart: unless-stopped + environment: + MONGO_INITDB_DATABASE: ${GONE_DB_DATABASE} + MONGO_INITDB_ROOT_USERNAME: ${GONE_DB_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${GONE_DB_PASSWORD} + ports: + - "${GONE_DB_PORT}:27017" + volumes: + - mongo_volume_bp:/data/db + +volumes: + mongo_volume_bp: \ No newline at end of file diff --git a/templates/db/mysql/db.go.tmpl b/templates/db/mysql/db.go.tmpl deleted file mode 100644 index 31260de..0000000 --- a/templates/db/mysql/db.go.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -package db - -import ( - "database/sql" - "log" - - _ "github.com/go-sql-driver/mysql" -) - -var DB *sql.DB - -func Connect() { - connStr := "user:password@tcp(localhost:3306)/app_db" - var err error - DB, err = sql.Open("mysql", connStr) - if err != nil { - log.Fatalf("✖ Failed to connect to MySQL: %v", err) - } - log.Println("✓ Connected to MySQL database") -} - -func Close() { - if DB != nil { - DB.Close() - log.Println("🔌 MySQL connection closed") - } -} diff --git a/templates/db/mysql/docker-compose.yml.tmpl b/templates/db/mysql/docker-compose.yml.tmpl new file mode 100644 index 0000000..85b5bda --- /dev/null +++ b/templates/db/mysql/docker-compose.yml.tmpl @@ -0,0 +1,17 @@ +services: + mysql_bp: + image: mysql:8 + restart: unless-stopped + environment: + MYSQL_DATABASE: ${GONE_DB_DATABASE} + MYSQL_USER: ${GONE_DB_USERNAME} + MYSQL_PASSWORD: ${GONE_DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${GONE_DB_PASSWORD} + ports: + - "${GONE_DB_PORT}:3306" + command: ["--default-authentication-plugin=mysql_native_password"] + volumes: + - mysql_volume_bp:/var/lib/mysql + +volumes: + mysql_volume_bp: diff --git a/templates/db/postgres/db.go.tmpl b/templates/db/postgres/db.go.tmpl deleted file mode 100644 index 3422984..0000000 --- a/templates/db/postgres/db.go.tmpl +++ /dev/null @@ -1,27 +0,0 @@ -package db - -import ( - "database/sql" - "log" - - _ "github.com/lib/pq" -) - -var DB *sql.DB - -func Connect() { - connStr := "user=postgres password=postgres dbname=app_db sslmode=disable" - var err error - DB, err = sql.Open("postgres", connStr) - if err != nil { - log.Fatalf("✖ Failed to connect to PostgreSQL: %v", err) - } - log.Println("✓ Connected to PostgreSQL database") -} - -func Close() { - if DB != nil { - DB.Close() - log.Println("🔌 PostgreSQL connection closed") - } -} diff --git a/templates/db/postgres/docker-compose.yml.tmpl b/templates/db/postgres/docker-compose.yml.tmpl new file mode 100644 index 0000000..311b70c --- /dev/null +++ b/templates/db/postgres/docker-compose.yml.tmpl @@ -0,0 +1,15 @@ +services: + psql_bp: + image: postgres:latest + restart: unless-stopped + environment: + POSTGRES_DB: ${GONE_DB_DATABASE} + POSTGRES_USER: ${GONE_DB_USERNAME} + POSTGRES_PASSWORD: ${GONE_DB_PASSWORD} + ports: + - "${GONE_DB_PORT}:5432" + volumes: + - psql_volume_bp:/var/lib/postgresql + +volumes: + psql_volume_bp: \ No newline at end of file diff --git a/templates/db/sqllite/db.go.tmpl b/templates/db/sqllite/db.go.tmpl deleted file mode 100644 index 2693850..0000000 --- a/templates/db/sqllite/db.go.tmpl +++ /dev/null @@ -1,26 +0,0 @@ -package db - -import ( - "database/sql" - "log" - - _ "github.com/mattn/go-sqlite3" -) - -var DB *sql.DB - -func Connect() { - var err error - DB, err = sql.Open("sqlite3", "app.db") - if err != nil { - log.Fatalf("✖ Failed to connect to SQLite: %v", err) - } - log.Println("✓ Connected to SQLite database") -} - -func Close() { - if DB != nil { - DB.Close() - log.Println("🔌 SQLite connection closed") - } -} diff --git a/templates/embed.go b/templates/embed.go index 5d7a80e..068db6d 100644 --- a/templates/embed.go +++ b/templates/embed.go @@ -7,7 +7,8 @@ import "embed" // Since this file is located in the 'templates' directory, // the paths 'common' and 'rest' correctly refer to the template folders. // -//go:embed common -//go:embed rest -//go:embed db + +//go:embed common/** +//go:embed rest/** +//go:embed db/** var FS embed.FS diff --git a/templates/rest/chi/cmd/main.go.tmpl b/templates/rest/chi/cmd/main.go.tmpl deleted file mode 100644 index 933fb29..0000000 --- a/templates/rest/chi/cmd/main.go.tmpl +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "log" - "net/http" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/router" - - "github.com/go-chi/chi/v5" -) - -func main() { - cfg := config.Load() - - r := chi.NewRouter() - router.RegisterRoutes(r) - - port := cfg.Port - if port == "" { - port = "8080" - } - - log.Printf("Starting server on :%s", port) - if err := http.ListenAndServe(":"+port, r); err != nil { - log.Fatal(err) - } -} diff --git a/templates/rest/chi/go.mod.tmpl b/templates/rest/chi/go.mod.tmpl deleted file mode 100644 index 84b2fb3..0000000 --- a/templates/rest/chi/go.mod.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -module {{.ModuleName}} - -go 1.22 - -require github.com/go-chi/chi/v5 v5.0.11 diff --git a/templates/rest/chi/internal/config/config.go.tmpl b/templates/rest/chi/internal/config/config.go.tmpl deleted file mode 100644 index 326241b..0000000 --- a/templates/rest/chi/internal/config/config.go.tmpl +++ /dev/null @@ -1,20 +0,0 @@ -package config - -import "os" - -type Config struct { - Port string -} - -func Load() Config { - return Config{ - Port: getEnv("APP_PORT", "8080"), - } -} - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return fallback -} diff --git a/templates/rest/chi/internal/handler/user_handler.go.tmpl b/templates/rest/chi/internal/handler/user_handler.go.tmpl deleted file mode 100644 index 6c5ca19..0000000 --- a/templates/rest/chi/internal/handler/user_handler.go.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" -) - -func GetUser(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") - json.NewEncoder(w).Encode(map[string]string{ - "id": id, - "name": "John Doe", - }) -} diff --git a/templates/rest/chi/internal/router/routes.go.tmpl b/templates/rest/chi/internal/router/routes.go.tmpl deleted file mode 100644 index 9cd014d..0000000 --- a/templates/rest/chi/internal/router/routes.go.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -package router - -import ( - handler "{{.ModuleName}}/internal/handler" - - "github.com/go-chi/chi/v5" -) - -func RegisterRoutes(r *chi.Mux) { - r.Get("/users/{id}", handler.GetUser) -} diff --git a/templates/rest/clean/cmd/main.go.tmpl b/templates/rest/clean/cmd/main.go.tmpl new file mode 100644 index 0000000..1008f94 --- /dev/null +++ b/templates/rest/clean/cmd/main.go.tmpl @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os/signal" + "syscall" + "time" + "{{.ModuleName}}/internal/server" +) + +func gracefulShutdown(apiServer *http.Server, done chan bool) { + // Create context that listens for the interrupt signal from the OS. + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + // Listen for the interrupt signal. + <-ctx.Done() + + log.Println("shutting down gracefully, press Ctrl+C again to force") + stop() // Allow Ctrl+C to force shutdown + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := apiServer.Shutdown(ctx); err != nil { + log.Printf("Server forced to shutdown with error: %v", err) + } + + log.Println("Server exiting") + + // Notify the main goroutine that the shutdown is complete + done <- true +} + +func main() { + + server := router.NewServer() + + // Create a done channel to signal when the shutdown is complete + done := make(chan bool, 1) + + // Run graceful shutdown in a separate goroutine + go gracefulShutdown(server, done) + + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + panic(fmt.Sprintf("http server error: %s", err)) + } + + // Wait for the graceful shutdown to complete + <-done + log.Println("Graceful shutdown complete.") +} + diff --git a/templates/rest/clean/docker-compose.yml.tmpl b/templates/rest/clean/docker-compose.yml.tmpl new file mode 100644 index 0000000..1a16578 --- /dev/null +++ b/templates/rest/clean/docker-compose.yml.tmpl @@ -0,0 +1,15 @@ +services: + {{.ServiceName}}: + image: {{.Image}} + restart: unless-stopped + + environment: | + + ports: + - "${GONE_DB_PORT}:5432" + + volumes: + - {{.Volume}} + +volumes: + {{.VolumeName}}: diff --git a/templates/rest/gin/go.mod.tmpl b/templates/rest/clean/go.mod.tmpl similarity index 100% rename from templates/rest/gin/go.mod.tmpl rename to templates/rest/clean/go.mod.tmpl diff --git a/templates/rest/gin/internal/config/config.go.tmpl b/templates/rest/clean/internal/config/config.go.tmpl similarity index 100% rename from templates/rest/gin/internal/config/config.go.tmpl rename to templates/rest/clean/internal/config/config.go.tmpl diff --git a/templates/rest/clean/internal/handler/example_handler.go.tmpl b/templates/rest/clean/internal/handler/example_handler.go.tmpl new file mode 100644 index 0000000..81f5db4 --- /dev/null +++ b/templates/rest/clean/internal/handler/example_handler.go.tmpl @@ -0,0 +1,19 @@ +package handler + +import ( + {{.ImportHandler}} +) + +type {{.Entity}} struct { + ID int `json:"id"` + Name string `json:"name"` +} + +var {{.LowerEntity}}s = []{{.Entity}}{ + {ID: 1, Name: "Example 1"}, + {ID: 2, Name: "Example 2"}, +} + +func Get{{.Entity}}s( {{.FullContext}} ) { + {{ .JSON }}{{.Response}}{{.LowerEntity}}s) +} \ No newline at end of file diff --git a/templates/rest/clean/internal/server/routes.go.tmpl b/templates/rest/clean/internal/server/routes.go.tmpl new file mode 100644 index 0000000..21cb033 --- /dev/null +++ b/templates/rest/clean/internal/server/routes.go.tmpl @@ -0,0 +1,28 @@ +package router + +import ( + "{{.ModuleName}}/internal/handler" + {{.ImportRouter}} +) + +func (s *Server) RegisterRoutes() http.Handler { + r := {{.Start}} + + r.{{.Get}}("/", s.HelloWorldHandler) + + r.{{.Get}}("/health", s.healthHandler) + + {{call .ApiGroup .Entity .Get .LowerEntity}} + return r +} + +func (s *Server) HelloWorldHandler({{.FullContext}}) { + resp := make(map[string]string) + resp["message"] = "Hello World" + + {{.ToTheClient}} resp) +} + +func (s *Server) healthHandler({{.FullContext}}) { + {{.ToTheClient}} s.db.Health()) +} diff --git a/templates/rest/clean/internal/server/server.go.tmpl b/templates/rest/clean/internal/server/server.go.tmpl new file mode 100644 index 0000000..b9db1f8 --- /dev/null +++ b/templates/rest/clean/internal/server/server.go.tmpl @@ -0,0 +1,37 @@ +package router + +import ( + "fmt" + "net/http" + "os" + "strconv" + {{- if .DBType }} + database "{{.ModuleName}}/internal/db" + {{- end }} + "time" +) + +type Server struct { + port int + db database.Service +} + +func NewServer() *http.Server { + port, _ := strconv.Atoi(os.Getenv("PORT")) + NewServer := &Server{ + port: port, + + db: database.New(), + } + + // Declare Server config + server := &http.Server{ + Addr: fmt.Sprintf(":%d", NewServer.port), + Handler: NewServer.RegisterRoutes(), + IdleTimeout: time.Minute, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + } + + return server +} diff --git a/templates/rest/gin/cmd/main.go.tmpl b/templates/rest/gin/cmd/main.go.tmpl deleted file mode 100644 index f321e65..0000000 --- a/templates/rest/gin/cmd/main.go.tmpl +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "log" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/router" - - "github.com/gin-gonic/gin" - - {{- if .DBType }} - "{{.ModuleName}}/internal/db" - {{- end }} -) - -func main() { - cfg := config.New() - - {{- if .DBType }} - db.Connect() - defer db.Close() - {{- end }} - - r := gin.Default() - router.RegisterRoutes(r) - - port := cfg.Port - if port == "" { - port = "{{.PortName}}" - } - - log.Printf("🚀 starting server on :%s", port) - if err := r.Run(":" + port); err != nil { - log.Fatal(err) - } -} diff --git a/templates/rest/gin/internal/handler/user_handler.go.tmpl b/templates/rest/gin/internal/handler/user_handler.go.tmpl deleted file mode 100644 index 07af570..0000000 --- a/templates/rest/gin/internal/handler/user_handler.go.tmpl +++ /dev/null @@ -1,32 +0,0 @@ -package handler - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -type User struct { - ID int `json:"id"` - Name string `json:"name"` -} - -var users = []User{ - {ID: 1, Name: "Alice"}, - {ID: 2, Name: "Bob"}, -} - -func GetUsers(c *gin.Context) { - c.JSON(http.StatusOK, users) -} - -func CreateUser(c *gin.Context) { - var newUser User - if err := c.BindJSON(&newUser); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - newUser.ID = len(users) + 1 - users = append(users, newUser) - c.JSON(http.StatusCreated, newUser) -} diff --git a/templates/rest/gin/internal/router/routes.go.tmpl b/templates/rest/gin/internal/router/routes.go.tmpl deleted file mode 100644 index 50a950e..0000000 --- a/templates/rest/gin/internal/router/routes.go.tmpl +++ /dev/null @@ -1,15 +0,0 @@ -package router - -import ( - "{{ .ModuleName }}/internal/handler" - - "github.com/gin-gonic/gin" -) - -func RegisterRoutes(r *gin.Engine) { - v1 := r.Group("/api/v1") - { - v1.GET("/users", handler.GetUsers) - v1.POST("/users", handler.CreateUser) - } -} From 71696cc8614bc353d0704d241f986c3dc4281794 Mon Sep 17 00:00:00 2001 From: Saurav Upadhyay Date: Fri, 5 Dec 2025 17:47:58 +0000 Subject: [PATCH 2/8] add: improve --- cmd/new.go | 124 ++++++++++++++++++++----------------- pkg/addons/addon.go | 1 + pkg/framework/framework.go | 6 -- pkg/plugins/plugins.go | 1 - project.yaml | 32 ++++++---- 5 files changed, 87 insertions(+), 77 deletions(-) create mode 100644 pkg/addons/addon.go delete mode 100644 pkg/plugins/plugins.go diff --git a/cmd/new.go b/cmd/new.go index f1005ad..2173a03 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -86,6 +86,11 @@ type TemplateData struct { DSN string } +type TemplateJob struct { + TemplateDir string + DestDir string +} + func init() { // Add the new command to the rootCmd rootCmd.AddCommand(newCmd) @@ -99,32 +104,13 @@ func init() { newCmd.Flags().StringVar(&Entitys, "entity", "", "entity") } -func createNewProject(projectName string, projectRouter string, template string, out io.Writer) { - err := os.Mkdir(projectName, 0755) - if err != nil { - fmt.Fprintf(out, "Error creating directory: %v\n", err) - return - } - - os.MkdirAll(filepath.Join(projectName, "internal"), 0755) +func buildTemplateData(projectName string, + projectPort string, + frameworkConfig framework.FrameworkConfig, + dbConfig *addons.DbAddOneConfig, // optional + entity string, uppercase string) TemplateData { - // Always add README + Makefile from common - renderTemplateDir("common", projectName, TemplateData{ - ModuleName: projectName, - PortName: projectPort, - }) - - var uppercase string - if len(Entitys) > 0 { - runes := []rune(Entitys) - runes[0] = unicode.ToUpper(runes[0]) - - uppercase = string(runes) - } - - frameworkConfig := framework.FrameworkRegistory[projectRouter] - - renderTemplateDir("rest/clean", projectName, TemplateData{ + data := TemplateData{ ModuleName: projectName, PortName: projectPort, DBType: DBType, @@ -145,44 +131,66 @@ func createNewProject(projectName string, projectRouter string, template string, Response: frameworkConfig.Response, ImportHandler: frameworkConfig.ImportHandler, ImportRouter: frameworkConfig.ImportRouter, - }) + } + + if dbConfig != nil { + data.ServiceName = dbConfig.ServiceName + data.DBName = dbConfig.DBName + data.DBEnvPrefix = dbConfig.DBEnvPrefix + data.Port = dbConfig.Port + data.DSN = dbConfig.DSN + data.Driver = dbConfig.Driver + data.Import = dbConfig.Import + data.Image = dbConfig.Image + data.Environment = dbConfig.Environment + data.Volume = dbConfig.Volume + data.VolumeName = dbConfig.VolumeName + } - if err != nil { - fmt.Fprintf(out, "Error rendering templates: %v\n", err) - return + return data +} + +func returnUppercase() string { + var uppercase string + if len(Entitys) > 0 { + runes := []rune(Entitys) + runes[0] = unicode.ToUpper(runes[0]) + + uppercase = string(runes) } + return uppercase +} + +func createNewProject(projectName, projectRouter, template string, out io.Writer) { + os.Mkdir(projectName, 0755) + os.MkdirAll(filepath.Join(projectName, "internal"), 0755) + + // Prepare configs + frameworkConfig := framework.FrameworkRegistory[projectRouter] + var dbConfig *addons.DbAddOneConfig if DBType != "" { - dbConfig := addons.DbRegistory[DBType] - - dbTemplatePath := "db/" + DBType - - err := renderTemplateDir(dbTemplatePath, filepath.Join(projectName), TemplateData{ - ModuleName: projectName, - PortName: projectPort, - DBType: DBType, - }) - err = renderTemplateDir("db/database", filepath.Join(projectName, "internal", "db"), TemplateData{ - ModuleName: projectName, - PortName: projectPort, - DBType: DBType, - ServiceName: dbConfig.ServiceName, - DBName: dbConfig.DBName, - DBEnvPrefix: dbConfig.DBEnvPrefix, - Port: dbConfig.Port, - DSN: dbConfig.DSN, - Driver: dbConfig.Driver, - Import: dbConfig.Import, - Image: dbConfig.Image, - Environment: dbConfig.Environment, - Volume: dbConfig.Volume, - VolumeName: dbConfig.VolumeName, - }) - if err != nil { + cfg := addons.DbRegistory[DBType] + dbConfig = &cfg + } - fmt.Fprintf(out, "Error rendering DB templates: %v\n", err) - return - } + uppercase := returnUppercase() + + // Build TemplateData once + data := buildTemplateData( + projectName, + projectPort, + frameworkConfig, + dbConfig, + Entitys, uppercase) + + // Render templates + renderTemplateDir("common", projectName, data) + renderTemplateDir("rest/clean", projectName, data) + + if DBType != "" { + renderTemplateDir("db/"+DBType, projectName, data) + renderTemplateDir("db/database", filepath.Join(projectName, "internal/db"), data) fmt.Fprintf(out, "✓ Added database support for '%s'\n", DBType) } diff --git a/pkg/addons/addon.go b/pkg/addons/addon.go new file mode 100644 index 0000000..1890561 --- /dev/null +++ b/pkg/addons/addon.go @@ -0,0 +1 @@ +package addons diff --git a/pkg/framework/framework.go b/pkg/framework/framework.go index 119c7ba..81090e0 100644 --- a/pkg/framework/framework.go +++ b/pkg/framework/framework.go @@ -2,12 +2,6 @@ package framework import "fmt" -type Framework interface { - Name() string - AddImports() error - Generate() error -} - type FrameworkConfig struct { Imports string Entity string diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go deleted file mode 100644 index d5c343e..0000000 --- a/pkg/plugins/plugins.go +++ /dev/null @@ -1 +0,0 @@ -package plugins diff --git a/project.yaml b/project.yaml index eb6dc58..f619f50 100644 --- a/project.yaml +++ b/project.yaml @@ -1,13 +1,21 @@ +project: + name: "test1" + port: 8080 + arch: "clean" + + +entity: +- user +- product +- payments + + +add_one: +- db +- auth +- cache +- queue +- k8s + + -layers: - - domain - - repository - - service - - http - - config - -features: - - jwt - - redis-cache - - metrics - - tracing From 625a2ec3b65eade7ded29d235c24290f20096d10 Mon Sep 17 00:00:00 2001 From: Saurav Upadhyay Date: Fri, 5 Dec 2025 18:00:26 +0000 Subject: [PATCH 3/8] fix: db error --- cmd/new.go | 21 ++++++++++++++++++--- templates/db/database/database.go.tmpl | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/new.go b/cmd/new.go index 2173a03..a055842 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -169,6 +169,11 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer frameworkConfig := framework.FrameworkRegistory[projectRouter] var dbConfig *addons.DbAddOneConfig + jobs := []TemplateJob{ + {"common", projectName}, + {"rest/clean", projectName}, + } + if DBType != "" { cfg := addons.DbRegistory[DBType] dbConfig = &cfg @@ -189,10 +194,20 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer renderTemplateDir("rest/clean", projectName, data) if DBType != "" { - renderTemplateDir("db/"+DBType, projectName, data) - renderTemplateDir("db/database", filepath.Join(projectName, "internal/db"), data) + jobs = append(jobs, + TemplateJob{"db/" + DBType, projectName}, + TemplateJob{"db/database", filepath.Join(projectName, "internal", "db")}, + ) + + // fmt.Fprintf(out, "✓ Added database support for '%s'\n", DBType) + } - fmt.Fprintf(out, "✓ Added database support for '%s'\n", DBType) + for _, job := range jobs { + if err := renderTemplateDir(job.TemplateDir, job.DestDir, data); err != nil { + fmt.Fprintf(out, "Error rendering template %s → %s: %v\n", + job.TemplateDir, job.DestDir, err) + return + } } fmt.Fprintf(out, "✓ Created '%s' successfully\n", projectName) diff --git a/templates/db/database/database.go.tmpl b/templates/db/database/database.go.tmpl index 466c59a..b824337 100644 --- a/templates/db/database/database.go.tmpl +++ b/templates/db/database/database.go.tmpl @@ -39,7 +39,7 @@ func New() Service { return dbInstance } - connStr := fmt.Sprintf("{{.DSN}}", username, password, host, port, database, schema) + connStr := fmt.Sprintf("{{.DSN}}", username, password, host, port, database) db, err := sql.Open("{{.Driver}}", connStr) if err != nil { From 06dea85788eea43c1a87849b3cb29148cb261dbd Mon Sep 17 00:00:00 2001 From: Saurav Upadhyay Date: Wed, 10 Dec 2025 19:10:19 +0000 Subject: [PATCH 4/8] add: yaml parsing --- cmd/new.go | 235 ++++++++++++++---- cmd/root.go | 2 +- main.go | 2 +- pkg/framework/framework.go | 99 +++++++- pkg/parser/yamlParsing.go | 35 ++- templates/db/database/database.go.tmpl | 71 +++--- templates/db/mysql/docker-compose.yml.tmpl | 1 - .../internal/handler/example_handler.go.tmpl | 22 +- .../internal/model/example_model.go.tmpl | 15 ++ .../clean/internal/model/registory.go.tmpl | 7 + .../internal/repository/example_repo.go.tmpl | 24 ++ .../rest/clean/internal/server/routes.go.tmpl | 30 ++- .../rest/clean/internal/server/server.go.tmpl | 35 ++- .../internal/service/example_service.go.tmpl | 18 ++ 14 files changed, 459 insertions(+), 137 deletions(-) create mode 100644 templates/rest/clean/internal/model/example_model.go.tmpl create mode 100644 templates/rest/clean/internal/model/registory.go.tmpl create mode 100644 templates/rest/clean/internal/repository/example_repo.go.tmpl create mode 100644 templates/rest/clean/internal/service/example_service.go.tmpl diff --git a/cmd/new.go b/cmd/new.go index a055842..1f0a6bc 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -1,6 +1,6 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Saurav Upadhyay sauravup041103@gmail.com */ @@ -10,6 +10,7 @@ import ( "fmt" "io" "io/fs" + "log" "os" "path/filepath" "strings" @@ -19,6 +20,7 @@ import ( "github.com/spf13/cobra" "github.com/upsaurav12/bootstrap/pkg/addons" "github.com/upsaurav12/bootstrap/pkg/framework" + "github.com/upsaurav12/bootstrap/pkg/parser" "github.com/upsaurav12/bootstrap/templates" ) @@ -28,18 +30,28 @@ var newCmd = &cobra.Command{ Short: "command for creating a new project.", Long: `command for creating a new project.`, Run: func(cmd *cobra.Command, args []string) { + + var dirName string + if len(args) < 1 && YAMLPath != "" { + yamlConfig, err := parser.ReadYAML(YAMLPath) + if err != nil { + fmt.Println("error while creating project using yaml: ", err) + return + } + + dirName = yamlConfig.Project.Name + } else { + dirName = args[0] + } + // Check if the project name is provided - if len(args) < 1 { + if len(args) < 1 && YAMLPath == "" { fmt.Fprintln(cmd.OutOrStdout(), "Error: project name is required") return } - // Get the template flag value from the command context tmpl, _ := cmd.Flags().GetString("type") - // Get the project name (first argument) - dirName := args[0] - // Create the new project createNewProject(dirName, projectRouter, tmpl, cmd.OutOrStdout()) }, @@ -51,14 +63,18 @@ var projectRouter string var DBType string var YAMLPath string var Entitys string +var Entities []string +var yamlFile string type TemplateData struct { + Name string ModuleName string PortName string DBType string Imports string Start string Entity string + Entities []string ContextName string ContextType string Router string @@ -66,6 +82,7 @@ type TemplateData struct { JSON string LowerEntity string OtherImports string + UpperEntity []string ApiGroup func(entity string, get string, lowerentity string) string Get string FullContext string @@ -84,6 +101,9 @@ type TemplateData struct { Import string Driver string DSN string + Returnable string + ReturnKeyword string + HTTPHandler string } type TemplateJob struct { @@ -102,23 +122,25 @@ func init() { newCmd.Flags().StringVar(&DBType, "db", "", "data type of the project") newCmd.Flags().StringVar(&YAMLPath, "yaml", "", "yaml file path") newCmd.Flags().StringVar(&Entitys, "entity", "", "entity") + newCmd.Flags().StringSliceVar(&Entities, "entities", nil, "different entities") } func buildTemplateData(projectName string, projectPort string, frameworkConfig framework.FrameworkConfig, dbConfig *addons.DbAddOneConfig, // optional - entity string, uppercase string) TemplateData { + yamlConfig *parser.Config, uppercase []string) TemplateData { data := TemplateData{ - ModuleName: projectName, - PortName: projectPort, - DBType: DBType, - Imports: frameworkConfig.Imports, - Start: frameworkConfig.Start, - ContextName: frameworkConfig.ContextName, - ContextType: frameworkConfig.ContextType, - Entity: uppercase, + Name: frameworkConfig.Name, + ModuleName: projectName, + PortName: projectPort, + DBType: DBType, + Imports: frameworkConfig.Imports, + Start: frameworkConfig.Start, + ContextName: frameworkConfig.ContextName, + ContextType: frameworkConfig.ContextType, + // Entity: uppercase, Router: frameworkConfig.Router, Bind: frameworkConfig.Bind, JSON: frameworkConfig.JSON, @@ -131,6 +153,42 @@ func buildTemplateData(projectName string, Response: frameworkConfig.Response, ImportHandler: frameworkConfig.ImportHandler, ImportRouter: frameworkConfig.ImportRouter, + Returnable: frameworkConfig.Returnable, + ReturnKeyword: frameworkConfig.ReturnKeyword, + HTTPHandler: frameworkConfig.HTTPHandler, + Entities: Entities, + // UpperEntity: , + } + + if yamlConfig != nil { + data = TemplateData{ + Name: frameworkConfig.Name, + ModuleName: projectName, + PortName: projectPort, + DBType: yamlConfig.Project.Database, + Imports: frameworkConfig.Imports, + Start: frameworkConfig.Start, + ContextName: frameworkConfig.ContextName, + ContextType: frameworkConfig.ContextType, + // Entity: uppercase, + Router: frameworkConfig.Router, + Bind: frameworkConfig.Bind, + JSON: frameworkConfig.JSON, + LowerEntity: Entitys, + UpperEntity: nil, + OtherImports: frameworkConfig.OtherImports, + ApiGroup: frameworkConfig.ApiGroup, + Get: frameworkConfig.Get, + FullContext: frameworkConfig.FullContext, + ToTheClient: frameworkConfig.ToTheClient, + Response: frameworkConfig.Response, + ImportHandler: frameworkConfig.ImportHandler, + ImportRouter: frameworkConfig.ImportRouter, + Returnable: frameworkConfig.Returnable, + ReturnKeyword: frameworkConfig.ReturnKeyword, + HTTPHandler: frameworkConfig.HTTPHandler, + Entities: yamlConfig.Entities, + } } if dbConfig != nil { @@ -150,23 +208,33 @@ func buildTemplateData(projectName string, return data } -func returnUppercase() string { - var uppercase string - if len(Entitys) > 0 { - runes := []rune(Entitys) - runes[0] = unicode.ToUpper(runes[0]) - - uppercase = string(runes) +func returnUppercase(entity string) string { + if entity == "" { + return "" } - return uppercase + + runes := []rune(entity) + runes[0] = unicode.ToUpper(runes[0]) + + return string(runes) } func createNewProject(projectName, projectRouter, template string, out io.Writer) { - os.Mkdir(projectName, 0755) - os.MkdirAll(filepath.Join(projectName, "internal"), 0755) + err := os.Mkdir(projectName, 0755) + if err != nil { + log.Fatalf("error occured while creating a new project %s: ", err) + } + err = os.MkdirAll(filepath.Join(projectName, "internal"), 0755) + + if err != nil { + log.Fatalf("error occured while creating a new project %s: ", projectName) + } // Prepare configs - frameworkConfig := framework.FrameworkRegistory[projectRouter] + + var frameworkConfig framework.FrameworkConfig + frameworkConfig = framework.FrameworkRegistory[projectRouter] + var dbConfig *addons.DbAddOneConfig jobs := []TemplateJob{ @@ -174,24 +242,70 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer {"rest/clean", projectName}, } + // if DBType != "" { + // cfg := addons.DbRegistory[DBType] + // dbConfig = &cfg + // } + + var data TemplateData + + yamlConfig, err := parser.ReadYAML(YAMLPath) + if err != nil { + fmt.Printf("error while reading yaml file: %s", err) + return + } + + // this is subjected to change as we would have more things to add in + // in this project. if DBType != "" { cfg := addons.DbRegistory[DBType] dbConfig = &cfg } + if DBType == "" && yamlConfig != nil { + DBType = yamlConfig.Project.Database + cfg := addons.DbRegistory[DBType] + dbConfig = &cfg + } + + if yamlConfig != nil { + + frameworkConfig = framework.FrameworkRegistory[yamlConfig.Project.Router] + Entities = yamlConfig.Entities - uppercase := returnUppercase() + if yamlConfig.Project.Database != "" { - // Build TemplateData once - data := buildTemplateData( + cfg := addons.DbRegistory[yamlConfig.Project.Database] + dbConfig = &cfg + + DBType = yamlConfig.Project.Database + + } + } + + var uppercase []string + + for _, entity := range Entities { + u := returnUppercase(entity) + + uppercase = append(uppercase, u) + } + + // data.UpperEntity = uppercase + + // till this line of code + + data = buildTemplateData( projectName, projectPort, frameworkConfig, dbConfig, - Entitys, uppercase) + yamlConfig, uppercase) + + data.UpperEntity = uppercase // Render templates - renderTemplateDir("common", projectName, data) - renderTemplateDir("rest/clean", projectName, data) + _ = renderTemplateDir("common", projectName, data) + _ = renderTemplateDir("rest/clean", projectName, data) if DBType != "" { jobs = append(jobs, @@ -231,13 +345,11 @@ func renderTemplateDir(templatePath, destinationPath string, data TemplateData) return os.MkdirAll(filepath.Join(destinationPath, relPath), 0755) } - // Read template file content, err := templates.FS.ReadFile(path) if err != nil { return err } - // Compute dynamic output filename fileName := strings.TrimSuffix(relPath, ".tmpl") if templatePath == "common" { @@ -246,28 +358,47 @@ func renderTemplateDir(templatePath, destinationPath string, data TemplateData) fileName = "." + fileName } } - // fmt.Println("patg: ", path) - - // 🔥 dynamic entity replacement - fileName = strings.ReplaceAll(fileName, "example", strings.ToLower(data.Entity)) - // fileName = strings.ReplaceAll(fileName, "Example", upperFirst(data.Entity)) - targetPath := filepath.Join(destinationPath, fileName) - - // Parse template - tmpl, err := template.New(filepath.Base(path)).Parse(string(content)) - if err != nil { - return err + if len(data.Entities) == 0 { + return writeSingle(data, fileName, path, content, destinationPath) } - // Write output file - outFile, err := os.Create(targetPath) - // fmt.Println("targetPath : ", targetPath, data) - if err != nil { - return err + for _, entity := range data.Entities { + + // correct file renaming + newFile := strings.Replace( + fileName, + "example", + strings.ToLower(entity), + 1, // only first replacement + ) + + entityData := data + entityData.Entity = strings.Title(entity) + entityData.LowerEntity = strings.ToLower(entity) + // capture errors!! + if err := writeSingle(entityData, newFile, path, content, destinationPath); err != nil { + return err + } } - defer outFile.Close() - return tmpl.Execute(outFile, data) + return nil }) } + +func writeSingle(data TemplateData, fileName string, tmpltPath string, content []byte, destinationPath string) error { + targetPath := filepath.Join(destinationPath, fileName) + + tmpl, err := template.New(filepath.Base(tmpltPath)).Parse(string(content)) + if err != nil { + return err + } + + outFile, err := os.Create(targetPath) + if err != nil { + return err + } + defer outFile.Close() + + return tmpl.Execute(outFile, data) +} diff --git a/cmd/root.go b/cmd/root.go index 94bbfdf..f952a48 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,5 +1,5 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Saurav Upadhyay sauravup041103@gmail.com */ package cmd diff --git a/main.go b/main.go index 18f6996..6d9e210 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ /* -Copyright © 2025 NAME HERE +Copyright © 2025 Saurav Upadhyay sauravup041103@gmail.com */ package main diff --git a/pkg/framework/framework.go b/pkg/framework/framework.go index 81090e0..12bc90b 100644 --- a/pkg/framework/framework.go +++ b/pkg/framework/framework.go @@ -3,6 +3,7 @@ package framework import "fmt" type FrameworkConfig struct { + Name string Imports string Entity string ContextName string @@ -19,10 +20,15 @@ type FrameworkConfig struct { Response string ImportRouter string ImportHandler string + Returnable string + ReturnKeyword string + HTTPHandler string + Entities []string } var FrameworkRegistory = map[string]FrameworkConfig{ "gin": { + Name: "gin", Imports: `"github.com/gin-gonic/gin"`, ContextName: "c", ContextType: "*gin.Context", @@ -34,14 +40,13 @@ var FrameworkRegistory = map[string]FrameworkConfig{ FullContext: "c *gin.Context", ApiGroup: func(entity string, get string, lowerentity string) string { apiGroup := fmt.Sprintf(` - api := r.Group("/api/v1") { %s := api.Group("/%s") { - %s.%s("", handler.Get%ss) + %s.%s("", %sHandler.Get%ss) } } - `, lowerentity, lowerentity, lowerentity, get, entity) + `, lowerentity, lowerentity, lowerentity, get, lowerentity, entity) return apiGroup }, @@ -55,11 +60,14 @@ var FrameworkRegistory = map[string]FrameworkConfig{ ImportHandler: ` "github.com/gin-gonic/gin" "net/http" - `, + Returnable: "", + ReturnKeyword: "", + HTTPHandler: "http.Handler", }, "chi": { + Name: "chi", Imports: `"github.com/go-chi/chi/v5"`, ContextName: "r", ContextType: "http.ResponseWriter, *http.Request", // chi passes both @@ -94,31 +102,102 @@ var FrameworkRegistory = map[string]FrameworkConfig{ "net/http" "github.com/go-chi/render" `, + Returnable: "", + ReturnKeyword: "", + HTTPHandler: "http.Handler", }, "echo": { + Name: "echo", Imports: `"github.com/labstack/echo/v4"`, ContextName: "c", - ContextType: "*echo.Context", - Bind: "c.Bind", - JSON: "c.JSON", + ContextType: "echo.Context", + Bind: "c.Bind", // func(obj interface{}) error + JSON: "c.JSON", // func(int, interface{}) error Router: "*echo.Echo", Start: "echo.New()", OtherImports: `"net/http"`, + + ApiGroup: func(entity, get, lowerentity string) string { + return fmt.Sprintf(` + api := r.Group("/api/v1") + { + %s := api.Group("/%s") + { + %s.%s("", handler.Get%ss) + } + } + `, lowerentity, lowerentity, lowerentity, get, entity) + }, + + Get: "GET", + + FullContext: "c echo.Context", + + ToTheClient: "c.JSON(http.StatusOK, ", + + Response: "(http.StatusOK,", + + ImportRouter: ` + "net/http" + "github.com/labstack/echo/v4" + `, + + ImportHandler: ` + "net/http" + "github.com/labstack/echo/v4" + `, + Returnable: "error", + ReturnKeyword: "return", + HTTPHandler: "http.Handler", }, "fiber": { + Name: "fiber", Imports: `"github.com/gofiber/fiber/v2"`, ContextName: "c", ContextType: "*fiber.Ctx", - Bind: "c.BodyParser", // BodyParser(obj) - JSON: "c.JSON", // JSON(obj) + Bind: "c.BodyParser", // func(obj interface{}) error + JSON: "c.JSON", // func(obj interface{}) error Router: "*fiber.App", Start: "fiber.New()", - OtherImports: "", + OtherImports: `"net/http"`, + + ApiGroup: func(entity, get, lowerentity string) string { + return fmt.Sprintf(` + api := r.Group("/api/v1") + { + %s := api.Group("/%s") + { + %s.%s("", handler.Get%ss) + } + } + `, lowerentity, lowerentity, lowerentity, get, entity) + }, + + Get: "Get", + + FullContext: "c *fiber.Ctx", + + ToTheClient: "c.JSON(", + + Response: "(fiber.StatusOK,", + + ImportRouter: ` + "github.com/gofiber/fiber/v2" + `, + + ImportHandler: ` + "github.com/gofiber/fiber/v2" + `, + + Returnable: "error", + ReturnKeyword: "return", + HTTPHandler: "*fiber.App", }, "mux": { + Name: "mux", Imports: `"github.com/gorilla/mux"`, ContextName: "w, r", ContextType: "http.ResponseWriter, *http.Request", diff --git a/pkg/parser/yamlParsing.go b/pkg/parser/yamlParsing.go index a76a97d..7ea5126 100644 --- a/pkg/parser/yamlParsing.go +++ b/pkg/parser/yamlParsing.go @@ -7,24 +7,51 @@ import ( "gopkg.in/yaml.v3" ) +type Config struct { + Project Project `yaml:"project"` + // Feature Feature `yaml:"feature"` + Entities []string `yaml:"entities"` + CustomLogic []string `yaml:"custom_logic"` +} + +type Project struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + Port int `yaml:"port"` + Location string `yaml:"location"` + Database string `yaml:"db"` + Router string `yaml:"router"` +} + +// type Feature struct { +// Database FeatureItem `yaml:"database"` +// Cache FeatureItem `yaml:"cache"` +// Queue FeatureItem `yaml:"queue"` +// Auth FeatureItem `yaml:"auth"` +// } + +type FeatureItem struct { + Name string `yaml:"name"` +} + type ProjectYAML struct { Name string `json:"name" yaml:"name"` Layers []string `json:"layers" yaml:"layers"` Features []string `json:"features" yaml:"features"` } -func ReadYAML(yamlPath string) (*ProjectYAML, error) { +func ReadYAML(yamlPath string) (*Config, error) { yamlByte, err := os.ReadFile(yamlPath) if err != nil { - return &ProjectYAML{}, err + return &Config{}, err } - var yamlProject ProjectYAML + var yamlProject Config err = yaml.Unmarshal(yamlByte, &yamlProject) if err != nil { fmt.Println("error: ", err) - return &ProjectYAML{}, err + return &Config{}, err } return &yamlProject, nil diff --git a/templates/db/database/database.go.tmpl b/templates/db/database/database.go.tmpl index b824337..259a580 100644 --- a/templates/db/database/database.go.tmpl +++ b/templates/db/database/database.go.tmpl @@ -1,80 +1,69 @@ package database import ( - "context" - "database/sql" + "{{.ModuleName}}/internal/model" "fmt" "log" "os" - "strconv" - "time" - {{.Import}} + _ "github.com/jackc/pgx/v5/stdlib" _ "github.com/joho/godotenv/autoload" + "gorm.io/driver/postgres" + "gorm.io/gorm" ) // Service represents a service that interacts with a database. type Service interface { Health() map[string]string Close() error + GetDB() *gorm.DB } type service struct { - db *sql.DB + db *gorm.DB } var ( - database = os.Getenv("BLUEPRINT_DB_DATABASE") - password = os.Getenv("BLUEPRINT_DB_PASSWORD") - username = os.Getenv("BLUEPRINT_DB_USERNAME") - port = os.Getenv("BLUEPRINT_DB_PORT") - host = os.Getenv("BLUEPRINT_DB_HOST") - schema = os.Getenv("BLUEPRINT_DB_SCHEMA") + database = os.Getenv("GONE_DB_DATABASE") + password = os.Getenv("GONE_DB_PASSWORD") + username = os.Getenv("GONE_DB_USERNAME") + port = os.Getenv("GONE_DB_PORT") + host = os.Getenv("GONE_DB_HOST") + schema = os.Getenv("GONE_DB_SCHEMA") dbInstance *service ) - func New() Service { - if dbInstance != nil { - return dbInstance - } - - connStr := fmt.Sprintf("{{.DSN}}", username, password, host, port, database) + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", + host, username, password, database, port, + ) - db, err := sql.Open("{{.Driver}}", connStr) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { - log.Fatal(err) + log.Fatalf("failed to connect database: %v", err) } + db.AutoMigrate(model.Registry...) + dbInstance = &service{db: db} return dbInstance } -func (s *service) Health() map[string]string { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - stats := make(map[string]string) +func (s *service) GetDB() *gorm.DB { + return s.db +} - err := s.db.PingContext(ctx) +func (s *service) Health() map[string]string { + sqlDB, _ := s.db.DB() + err := sqlDB.Ping() if err != nil { - stats["status"] = "down" - stats["error"] = fmt.Sprintf("db down: %v", err) - return stats + return map[string]string{"status": "down"} } - - stats["status"] = "up" - stats["message"] = "healthy" - - dbStats := s.db.Stats() - stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) - stats["in_use"] = strconv.Itoa(dbStats.InUse) - stats["idle"] = strconv.Itoa(dbStats.Idle) - - return stats + return map[string]string{"status": "up"} } func (s *service) Close() error { - log.Printf("Disconnected from {{.DBName}} database: %s", database) - return s.db.Close() + sqlDB, _ := s.db.DB() + return sqlDB.Close() } diff --git a/templates/db/mysql/docker-compose.yml.tmpl b/templates/db/mysql/docker-compose.yml.tmpl index 85b5bda..fee41d9 100644 --- a/templates/db/mysql/docker-compose.yml.tmpl +++ b/templates/db/mysql/docker-compose.yml.tmpl @@ -9,7 +9,6 @@ services: MYSQL_ROOT_PASSWORD: ${GONE_DB_PASSWORD} ports: - "${GONE_DB_PORT}:3306" - command: ["--default-authentication-plugin=mysql_native_password"] volumes: - mysql_volume_bp:/var/lib/mysql diff --git a/templates/rest/clean/internal/handler/example_handler.go.tmpl b/templates/rest/clean/internal/handler/example_handler.go.tmpl index 81f5db4..71f31dc 100644 --- a/templates/rest/clean/internal/handler/example_handler.go.tmpl +++ b/templates/rest/clean/internal/handler/example_handler.go.tmpl @@ -1,19 +1,21 @@ package handler import ( - {{.ImportHandler}} + "{{.ModuleName}}/internal/service" + "net/http" + + "github.com/gin-gonic/gin" ) -type {{.Entity}} struct { - ID int `json:"id"` - Name string `json:"name"` +type {{.Entity}}Handler struct { + Service *service.{{.Entity}}Service } -var {{.LowerEntity}}s = []{{.Entity}}{ - {ID: 1, Name: "Example 1"}, - {ID: 2, Name: "Example 2"}, +func New{{.Entity}}Handler(s *service.{{.Entity}}Service) *{{.Entity}}Handler { + return &{{.Entity}}Handler{Service: s} } -func Get{{.Entity}}s( {{.FullContext}} ) { - {{ .JSON }}{{.Response}}{{.LowerEntity}}s) -} \ No newline at end of file +func (h *{{.Entity}}Handler) Get{{.Entity}}s(c *gin.Context) { + {{.LowerEntity}}s, _ := h.Service.Get{{.Entity}}s() + c.JSON(http.StatusOK, {{.LowerEntity}}s) +} diff --git a/templates/rest/clean/internal/model/example_model.go.tmpl b/templates/rest/clean/internal/model/example_model.go.tmpl new file mode 100644 index 0000000..076a318 --- /dev/null +++ b/templates/rest/clean/internal/model/example_model.go.tmpl @@ -0,0 +1,15 @@ +package model + +import ( + "gorm.io/gorm" +) + +type {{.Entity}} struct { + gorm.Model + ID int `json:"id"` + Name string `json:"name"` +} + +func init() { + Register(&{{.Entity}}{}) +} \ No newline at end of file diff --git a/templates/rest/clean/internal/model/registory.go.tmpl b/templates/rest/clean/internal/model/registory.go.tmpl new file mode 100644 index 0000000..9648607 --- /dev/null +++ b/templates/rest/clean/internal/model/registory.go.tmpl @@ -0,0 +1,7 @@ +package model + +var Registry []interface{} + +func Register(m interface{}) { + Registry = append(Registry, m) +} diff --git a/templates/rest/clean/internal/repository/example_repo.go.tmpl b/templates/rest/clean/internal/repository/example_repo.go.tmpl new file mode 100644 index 0000000..d4fe1d1 --- /dev/null +++ b/templates/rest/clean/internal/repository/example_repo.go.tmpl @@ -0,0 +1,24 @@ +package repository + +import ( + "{{.ModuleName}}/internal/model" + "gorm.io/gorm" +) + +type {{.Entity}}Repo struct { + DB *gorm.DB +} + +func New{{.Entity}}Repo(db *gorm.DB) *{{.Entity}}Repo { + return &{{.Entity}}Repo{DB: db} +} + +func (r *{{.Entity}}Repo) FindAll() ([]model.{{.Entity}}, error) { + var {{.LowerEntity}}s []model.{{.Entity}} + err := r.DB.Find(&{{.LowerEntity}}s).Error + return {{.LowerEntity}}s, err +} + +func (r *{{.Entity}}Repo) Create({{.LowerEntity}}*model.{{.Entity}}) error { + return r.DB.Create({{.LowerEntity}}).Error +} diff --git a/templates/rest/clean/internal/server/routes.go.tmpl b/templates/rest/clean/internal/server/routes.go.tmpl index 21cb033..2cebabb 100644 --- a/templates/rest/clean/internal/server/routes.go.tmpl +++ b/templates/rest/clean/internal/server/routes.go.tmpl @@ -2,27 +2,45 @@ package router import ( "{{.ModuleName}}/internal/handler" + "{{.ModuleName}}/internal/repository" + "{{.ModuleName}}/internal/service" {{.ImportRouter}} ) -func (s *Server) RegisterRoutes() http.Handler { +func (s *Server) RegisterRoutes() {{.HTTPHandler}} { r := {{.Start}} r.{{.Get}}("/", s.HelloWorldHandler) r.{{.Get}}("/health", s.healthHandler) - {{call .ApiGroup .Entity .Get .LowerEntity}} + gormDB := s.db.GetDB() + + api := r.Group("/api/v1") + + {{range $i, $entity := .Entities}} + {{$upper := index $.UpperEntity $i}} + {{$lower := $entity}} + + {{ printf "%sRepo := repository.New%sRepo(gormDB)" $lower $upper }} + {{ printf "%sService := service.New%sService(%sRepo)" $lower $upper $lower }} + {{ printf "%sHandler := handler.New%sHandler(%sService)" $lower $upper $lower }} + + {{call $.ApiGroup $upper $.Get $lower}} + {{end}} + + + return r } -func (s *Server) HelloWorldHandler({{.FullContext}}) { +func (s *Server) HelloWorldHandler({{.FullContext}}) {{.Returnable}} { resp := make(map[string]string) resp["message"] = "Hello World" - {{.ToTheClient}} resp) + {{.ReturnKeyword}} {{.ToTheClient}} resp) } -func (s *Server) healthHandler({{.FullContext}}) { - {{.ToTheClient}} s.db.Health()) +func (s *Server) healthHandler({{.FullContext}}) {{.Returnable}} { + {{.ReturnKeyword}} {{.ToTheClient}} s.db.Health()) } diff --git a/templates/rest/clean/internal/server/server.go.tmpl b/templates/rest/clean/internal/server/server.go.tmpl index b9db1f8..00614e3 100644 --- a/templates/rest/clean/internal/server/server.go.tmpl +++ b/templates/rest/clean/internal/server/server.go.tmpl @@ -6,32 +6,45 @@ import ( "os" "strconv" {{- if .DBType }} - database "{{.ModuleName}}/internal/db" + database "{{.ModuleName}}/internal/db" {{- end }} "time" + + "gorm.io/gorm" ) type Server struct { - port int - db database.Service + port int + db database.Service + gormDB *gorm.DB } func NewServer() *http.Server { port, _ := strconv.Atoi(os.Getenv("PORT")) - NewServer := &Server{ - port: port, - db: database.New(), + dbService := database.New() + if dbService == nil { + panic("database.New() returned nil — DB initialization failed") + } + + gormDB := dbService.GetDB() + if gormDB == nil { + panic("dbService.GetDB() returned nil — Gorm DB is not initialized") + } + + srv := &Server{ + port: port, + db: dbService, + gormDB: gormDB, } - // Declare Server config - server := &http.Server{ - Addr: fmt.Sprintf(":%d", NewServer.port), - Handler: NewServer.RegisterRoutes(), + httpServer := &http.Server{ + Addr: fmt.Sprintf(":%d", srv.port), + Handler: srv.RegisterRoutes(), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, } - return server + return httpServer } diff --git a/templates/rest/clean/internal/service/example_service.go.tmpl b/templates/rest/clean/internal/service/example_service.go.tmpl new file mode 100644 index 0000000..d1d493f --- /dev/null +++ b/templates/rest/clean/internal/service/example_service.go.tmpl @@ -0,0 +1,18 @@ +package service + +import ( + "{{.ModuleName}}/internal/model" + "{{.ModuleName}}/internal/repository" +) + +type {{.Entity}}Service struct { + Repo *repository.{{.Entity}}Repo +} + +func New{{.Entity}}Service(repo *repository.{{.Entity}}Repo) *{{.Entity}}Service { + return &{{.Entity}}Service{Repo: repo} +} + +func (s *{{.Entity}}Service) Get{{.Entity}}s() ([]model.{{.Entity}}, error) { + return s.Repo.FindAll() +} \ No newline at end of file From 62ac4b5e6dea7335a71d605c75ce654783a2c5e4 Mon Sep 17 00:00:00 2001 From: upsaurav12 Date: Tue, 23 Dec 2025 14:22:30 +0530 Subject: [PATCH 5/8] fix rendering --- project.yaml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/project.yaml b/project.yaml index f619f50..79c7c53 100644 --- a/project.yaml +++ b/project.yaml @@ -2,20 +2,9 @@ project: name: "test1" port: 8080 arch: "clean" + router: "chi" # or gin / echo / fiber - -entity: +entities: - user - product - payments - - -add_one: -- db -- auth -- cache -- queue -- k8s - - - From 53d0d450ce5790abf48ede9491117654b369c365 Mon Sep 17 00:00:00 2001 From: upsaurav12 Date: Sat, 17 Jan 2026 01:08:05 +0530 Subject: [PATCH 6/8] add: interactive --- .github/workflows/ci.yml | 2 +- LICENSE | 0 cmd/new.go | 389 ++++++++++++++++-- go.mod | 24 +- go.sum | 44 ++ .../rest/clean/internal/server/routes.go.tmpl | 36 +- 6 files changed, 445 insertions(+), 50 deletions(-) delete mode 100644 LICENSE diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e3b2d4..64f0190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,4 +22,4 @@ jobs: run: go mod tidy - name: Run tests - run: go test -v ./... \ No newline at end of file + run: go test ./... \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e69de29..0000000 diff --git a/cmd/new.go b/cmd/new.go index 1f0a6bc..9134d81 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -13,6 +13,7 @@ import ( "log" "os" "path/filepath" + "strconv" "strings" "text/template" "unicode" @@ -22,14 +23,328 @@ import ( "github.com/upsaurav12/bootstrap/pkg/framework" "github.com/upsaurav12/bootstrap/pkg/parser" "github.com/upsaurav12/bootstrap/templates" + + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/common-nighthawk/go-figure" +) + +type ProjectInput struct { + Name string + Type string + Router string + Port string + DB string + Entities []string +} + +var asciiStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color(primaryBlue)). + Bold(true) + +const ( + stepName = iota + stepType + stepRouter + stepPort + stepDB + stepConfirm +) + +var ( + titleStyle = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color(softBlue)) + + labelStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color(softBlue)) + + hintStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color(mutedGray)) + + boxStyle = lipgloss.NewStyle(). + Padding(3, 2). + Border(lipgloss.RoundedBorder()). + BorderForeground(borderGray). + Width(60) +) + +func renderStep(m wizardModel, title, label, body, hint string) string { + content := lipgloss.JoinVertical( + lipgloss.Left, + titleStyle.Render(title), + "", + labelStyle.Render(label), + "", + body, + "", + hintStyle.Render(hint), + ) + + box := boxStyle. + Width(m.width - 4). + Height(m.height - 2). + Render(content) + + return box +} + +type wizardModel struct { + step int + input ProjectInput + + text textinput.Model + list list.Model + quit bool + + width int + height int +} + +type item string + +func (i item) Title() string { return string(i) } +func (i item) Description() string { return "" } +func (i item) FilterValue() string { return string(i) } + +func initialWizardModel() wizardModel { + return wizardModel{ + step: stepName, + text: newTextInput(""), + } +} + +const ( + primaryBlue = lipgloss.Color("33") // bright blue + softBlue = lipgloss.Color("39") // lighter blue + mutedGray = lipgloss.Color("241") // hints + borderGray = lipgloss.Color("238") // borders ) +func newTextInput(placeholder string) textinput.Model { + ti := textinput.New() + ti.Prompt = "› " + ti.Placeholder = placeholder + ti.SetValue("") // ← critical + ti.Focus() + return ti +} + +func (m wizardModel) Init() tea.Cmd { + return nil +} + +func renderHeader() string { + fig := figure.NewFigure("Bootstrap CLI", "slant", true) + + ascii := strings.Trim(fig.String(), "\n") + + return asciiStyle.Render(ascii) +} + +func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + + switch msg := msg.(type) { + + // ✅ HANDLE WINDOW SIZE FIRST + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + + // ✅ HANDLE KEYS + case tea.KeyMsg: + switch msg.String() { + + case "ctrl+c", "esc": + m.quit = true + return m, tea.Quit + + case "enter": + switch m.step { + + case stepName: + m.input.Name = m.text.Value() + m.step = stepType + m.list = newList("Project type", []string{"rest"}) + return m, nil + + case stepType: + m.input.Type = m.list.SelectedItem().(item).Title() + m.step = stepRouter + m.list = newList("Router", []string{"gin", "chi", "echo"}) + return m, nil + + case stepRouter: + m.input.Router = m.list.SelectedItem().(item).Title() + m.step = stepPort + m.text = newTextInput("") + return m, nil + + case stepPort: + port := m.text.Value() + if port == "" { + port = "8080" + } + if _, err := strconv.Atoi(port); err != nil { + return m, nil + } + m.input.Port = port + m.step = stepDB + m.list = newList("Database", []string{"postgres", "mysql", "mongo"}) + return m, nil + + case stepDB: + m.input.DB = m.list.SelectedItem().(item).Title() + m.step = stepConfirm + return m, nil + + case stepConfirm: + return m, tea.Quit + } + } + } + + var cmd tea.Cmd + if m.step == stepName || m.step == stepPort { + m.text, cmd = m.text.Update(msg) + return m, cmd + } + + if m.step == stepType || m.step == stepRouter || m.step == stepDB { + m.list, cmd = m.list.Update(msg) + return m, cmd + } + + return m, nil +} + +func (m wizardModel) View() string { + if m.quit { + return "" + } + + switch m.step { + + case stepName: + return renderStep( + m, + renderHeader()+"\nCreate New Project", + "Project name", + m.text.View(), + "Enter to continue • Esc to quit", + ) + + case stepType: + return renderStep( + m, + "Project Type", + "Select project type", + m.list.View(), + "↑↓ navigate • Enter select • Esc quit", + ) + + case stepRouter: + return renderStep( + m, + "Router", + "Select router", + m.list.View(), + "↑↓ navigate • Enter select • Esc quit", + ) + + case stepPort: + return renderStep( + m, + "Application Port", + "Port (default: 8080)", + m.text.View(), + "Enter to continue • Esc quit", + ) + + case stepDB: + return renderStep( + m, + "Database", + "Select database", + m.list.View(), + "↑↓ navigate • Enter select • Esc quit", + ) + + case stepConfirm: + summary := fmt.Sprintf( + "Project: %s\nType: %s\nRouter: %s\nPort: %s\nDatabase: %s", + m.input.Name, + m.input.Type, + m.input.Router, + m.input.Port, + m.input.DB, + ) + + return renderStep( + m, + "Confirm Configuration", + "Review your selections", + summary, + "Enter to generate • Esc to cancel", + ) + } + + return "" +} + +func newList(title string, values []string) list.Model { + items := make([]list.Item, len(values)) + for i, v := range values { + items[i] = item(v) + } + + l := list.New(items, list.NewDefaultDelegate(), 20, 10) + l.Title = title + return l +} + +func RunInteractiveWizard() (*ProjectInput, error) { + p := tea.NewProgram(initialWizardModel()) + model, err := p.Run() + if err != nil { + return nil, err + } + + m := model.(wizardModel) + if m.quit { + return nil, fmt.Errorf("aborted") + } + + return &m.input, nil +} + // newCmd represents the new command var newCmd = &cobra.Command{ Use: "new", Short: "command for creating a new project.", Long: `command for creating a new project.`, Run: func(cmd *cobra.Command, args []string) { + interactive, _ := cmd.Flags().GetBool("interactive") + + if interactive || (len(args) == 0 && YAMLPath == "") { + input, err := RunInteractiveWizard() + if err != nil { + fmt.Fprintln(cmd.OutOrStdout(), err) + return + } + + projectType = input.Type + projectRouter = input.Router + projectPort = input.Port + DBType = input.DB + Entities = input.Entities + + createNewProject(input.Name, input.Router, input.Type, cmd.OutOrStdout()) + return + } var dirName string if len(args) < 1 && YAMLPath != "" { @@ -123,6 +438,8 @@ func init() { newCmd.Flags().StringVar(&YAMLPath, "yaml", "", "yaml file path") newCmd.Flags().StringVar(&Entitys, "entity", "", "entity") newCmd.Flags().StringSliceVar(&Entities, "entities", nil, "different entities") + newCmd.Flags().Bool("interactive", false, "run interactive project setup") + } func buildTemplateData(projectName string, @@ -132,15 +449,14 @@ func buildTemplateData(projectName string, yamlConfig *parser.Config, uppercase []string) TemplateData { data := TemplateData{ - Name: frameworkConfig.Name, - ModuleName: projectName, - PortName: projectPort, - DBType: DBType, - Imports: frameworkConfig.Imports, - Start: frameworkConfig.Start, - ContextName: frameworkConfig.ContextName, - ContextType: frameworkConfig.ContextType, - // Entity: uppercase, + Name: frameworkConfig.Name, + ModuleName: projectName, + PortName: projectPort, + DBType: DBType, + Imports: frameworkConfig.Imports, + Start: frameworkConfig.Start, + ContextName: frameworkConfig.ContextName, + ContextType: frameworkConfig.ContextType, Router: frameworkConfig.Router, Bind: frameworkConfig.Bind, JSON: frameworkConfig.JSON, @@ -162,15 +478,14 @@ func buildTemplateData(projectName string, if yamlConfig != nil { data = TemplateData{ - Name: frameworkConfig.Name, - ModuleName: projectName, - PortName: projectPort, - DBType: yamlConfig.Project.Database, - Imports: frameworkConfig.Imports, - Start: frameworkConfig.Start, - ContextName: frameworkConfig.ContextName, - ContextType: frameworkConfig.ContextType, - // Entity: uppercase, + Name: frameworkConfig.Name, + ModuleName: projectName, + PortName: projectPort, + DBType: yamlConfig.Project.Database, + Imports: frameworkConfig.Imports, + Start: frameworkConfig.Start, + ContextName: frameworkConfig.ContextName, + ContextType: frameworkConfig.ContextType, Router: frameworkConfig.Router, Bind: frameworkConfig.Bind, JSON: frameworkConfig.JSON, @@ -242,21 +557,19 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer {"rest/clean", projectName}, } - // if DBType != "" { - // cfg := addons.DbRegistory[DBType] - // dbConfig = &cfg - // } - var data TemplateData - yamlConfig, err := parser.ReadYAML(YAMLPath) - if err != nil { - fmt.Printf("error while reading yaml file: %s", err) - return + var yamlConfig *parser.Config + + if YAMLPath != "" { + var err error + yamlConfig, err = parser.ReadYAML(YAMLPath) + if err != nil { + fmt.Printf("error while reading yaml file: %s", err) + return + } } - // this is subjected to change as we would have more things to add in - // in this project. if DBType != "" { cfg := addons.DbRegistory[DBType] dbConfig = &cfg @@ -290,10 +603,6 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer uppercase = append(uppercase, u) } - // data.UpperEntity = uppercase - - // till this line of code - data = buildTemplateData( projectName, projectPort, @@ -387,7 +696,17 @@ func renderTemplateDir(templatePath, destinationPath string, data TemplateData) } func writeSingle(data TemplateData, fileName string, tmpltPath string, content []byte, destinationPath string) error { - targetPath := filepath.Join(destinationPath, fileName) + newFile := strings.Replace( + fileName, + "example", + "user", + 1, // only first replacement + ) + + entityData := data + entityData.Entity = strings.Title("user") + entityData.LowerEntity = strings.ToLower("user") + targetPath := filepath.Join(destinationPath, newFile) tmpl, err := template.New(filepath.Base(tmpltPath)).Parse(string(content)) if err != nil { @@ -400,5 +719,5 @@ func writeSingle(data TemplateData, fileName string, tmpltPath string, content [ } defer outFile.Close() - return tmpl.Execute(outFile, data) + return tmpl.Execute(outFile, entityData) } diff --git a/go.mod b/go.mod index b25ff21..1ec608b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/upsaurav12/bootstrap -go 1.23.6 +go 1.24.0 require ( github.com/gin-gonic/gin v1.11.0 @@ -10,10 +10,21 @@ require ( ) require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/charmbracelet/bubbles v0.21.0 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -25,23 +36,32 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect google.golang.org/protobuf v1.36.9 // indirect diff --git a/go.sum b/go.sum index 2be9636..b7ced98 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,35 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -37,12 +59,24 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -51,7 +85,12 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -69,6 +108,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= @@ -81,9 +122,12 @@ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= diff --git a/templates/rest/clean/internal/server/routes.go.tmpl b/templates/rest/clean/internal/server/routes.go.tmpl index 2cebabb..7083f8e 100644 --- a/templates/rest/clean/internal/server/routes.go.tmpl +++ b/templates/rest/clean/internal/server/routes.go.tmpl @@ -16,20 +16,32 @@ func (s *Server) RegisterRoutes() {{.HTTPHandler}} { gormDB := s.db.GetDB() - api := r.Group("/api/v1") - - {{range $i, $entity := .Entities}} - {{$upper := index $.UpperEntity $i}} - {{$lower := $entity}} - - {{ printf "%sRepo := repository.New%sRepo(gormDB)" $lower $upper }} - {{ printf "%sService := service.New%sService(%sRepo)" $lower $upper $lower }} - {{ printf "%sHandler := handler.New%sHandler(%sService)" $lower $upper $lower }} - - {{call $.ApiGroup $upper $.Get $lower}} - {{end}} + api := r.Group("/api/v1") + {{ if .Entities }} + {{ range $i, $entity := .Entities }} + {{ $upper := index $.UpperEntity $i }} + {{ $lower := $entity }} + + {{ printf "%sRepo := repository.New%sRepo(gormDB)" $lower $upper }} + {{ printf "%sService := service.New%sService(%sRepo)" $lower $upper $lower }} + {{ printf "%sHandler := handler.New%sHandler(%sService)" $lower $upper $lower }} + + {{ call $.ApiGroup $upper $.Get $lower }} + {{ end }} + {{ else }} + userRepo := repository.NewUserRepo(gormDB) + userService := service.NewUserService(userRepo) + userHandler := handler.NewUserHandler(userService) + + { + user := api.Group("/user") + { + user.GET("", userHandler.GetUsers) + } + } + {{ end }} return r } From ad2950e6126ecb906ba0c6d74ec240ed53d051c0 Mon Sep 17 00:00:00 2001 From: upsaurav12 Date: Sat, 17 Jan 2026 01:20:58 +0530 Subject: [PATCH 7/8] fix: yaml --- cmd/new.go | 20 ++++++++++++++++++++ project.yaml | 2 +- templates/common/project.yaml.tmpl | 10 ++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 templates/common/project.yaml.tmpl diff --git a/cmd/new.go b/cmd/new.go index 9134d81..91f6187 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -306,6 +306,21 @@ func newList(title string, values []string) list.Model { return l } +func copyProjectYAML(srcPath, destDir string) error { + if srcPath == "" { + return nil // nothing to copy + } + + content, err := os.ReadFile(srcPath) + if err != nil { + return err + } + + destPath := filepath.Join(destDir, "project.yaml") + + return os.WriteFile(destPath, content, 0644) +} + func RunInteractiveWizard() (*ProjectInput, error) { p := tea.NewProgram(initialWizardModel()) model, err := p.Run() @@ -545,6 +560,11 @@ func createNewProject(projectName, projectRouter, template string, out io.Writer log.Fatalf("error occured while creating a new project %s: ", projectName) } + // ✅ COPY project.yaml if provided + if err := copyProjectYAML(YAMLPath, projectName); err != nil { + fmt.Fprintf(out, "warning: could not copy project.yaml: %v\n", err) + } + // Prepare configs var frameworkConfig framework.FrameworkConfig diff --git a/project.yaml b/project.yaml index 79c7c53..e8e4e27 100644 --- a/project.yaml +++ b/project.yaml @@ -7,4 +7,4 @@ project: entities: - user - product -- payments +- payment diff --git a/templates/common/project.yaml.tmpl b/templates/common/project.yaml.tmpl new file mode 100644 index 0000000..c6eed0b --- /dev/null +++ b/templates/common/project.yaml.tmpl @@ -0,0 +1,10 @@ +project: + name: "{{ .ModuleName }}" + port: {{ .Port }} + router: "{{ .Router }}" + postgres: "{{ .DBName }}" + +entities: +{{- range .Entities }} + - {{ . }} +{{- end }} From 007eeb2372480865186bdd9b24c0ec88ce4bd2d4 Mon Sep 17 00:00:00 2001 From: upsaurav12 Date: Sat, 17 Jan 2026 01:33:35 +0530 Subject: [PATCH 8/8] fix: yaml --- templates/common/project.yaml.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/common/project.yaml.tmpl b/templates/common/project.yaml.tmpl index c6eed0b..e75ab15 100644 --- a/templates/common/project.yaml.tmpl +++ b/templates/common/project.yaml.tmpl @@ -2,9 +2,13 @@ project: name: "{{ .ModuleName }}" port: {{ .Port }} router: "{{ .Router }}" - postgres: "{{ .DBName }}" + database: "{{ .DBType }}" entities: +{{- if .Entities }} {{- range .Entities }} - {{ . }} {{- end }} +{{- else }} + - user +{{- end }}