diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3a4ab9..4c62f75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.25.8" + go-version: "1.25.0" - name: Lint uses: golangci/golangci-lint-action@v9 @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ["1.25.8"] + go-version: ["1.25.0"] steps: - uses: actions/checkout@v4 @@ -76,7 +76,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.25.8" + go-version: "1.25.0" - name: Build CLI working-directory: cli diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac5a6bd..acb77e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.25.8" + go-version: "1.25.0" - name: Run tests run: go test -race ./... @@ -37,7 +37,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.25.8" + go-version: "1.25.0" - name: Build binary env: diff --git a/.go-version b/.go-version index e6a6e7c..ad21919 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.25.8 +1.25.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ebea3b..8f59231 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Thank you for your interest in contributing to the Gofasta CLI! This document ex cd cli ``` -2. **Verify Go version** (1.25.8 or later required): +2. **Verify Go version** (1.25.0 or later required): ```bash go version diff --git a/README.md b/README.md index 1bbcbba..f2202d8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The command-line tool for [Gofasta](https://github.com/gofastadev/gofasta), a Go The CLI lives in its own Go module (`github.com/gofastadev/cli`) with `main.go` at `cmd/gofasta/`. It is not the same as the `github.com/gofastadev/gofasta` library, which your generated projects import as a dependency. You install one, you import the other. -**Option A — `go install` (recommended for Go developers, requires Go 1.25.8+):** +**Option A — `go install` (recommended for Go developers, requires Go 1.25.0+):** ```bash go install github.com/gofastadev/cli/cmd/gofasta@latest diff --git a/go.mod b/go.mod index dae1b97..d2b6980 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gofastadev/cli -go 1.25.8 +go 1.25.0 require ( github.com/knadh/koanf/parsers/yaml v1.1.0 diff --git a/internal/commands/commands_exec_test.go b/internal/commands/commands_exec_test.go index 408b804..91b7206 100644 --- a/internal/commands/commands_exec_test.go +++ b/internal/commands/commands_exec_test.go @@ -513,14 +513,15 @@ func TestRunNew_GoModInitFails(t *testing.T) { // branches we need the first two exec calls to succeed. func TestRunNew_WarningBranches(t *testing.T) { chdirTemp(t) - stagedFakeExec(t, 0, 0, 1) // mod init ok, gofasta install ok, everything else fails + // mod init ok, mod edit -go ok, gofasta install ok, everything else fails + stagedFakeExec(t, 0, 0, 0, 1) err := runNew("warnapp", false) assert.NoError(t, err) } func TestRunNew_WarningBranches_GraphQL(t *testing.T) { chdirTemp(t) - stagedFakeExec(t, 0, 0, 1) + stagedFakeExec(t, 0, 0, 0, 1) err := runNew("warnapp", true) assert.NoError(t, err) } @@ -549,7 +550,7 @@ func TestRunNew_GofastaReplaceHappyPath(t *testing.T) { // Fake framework checkout — absolute path with a go.mod inside. fakeFramework := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(fakeFramework, "go.mod"), - []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.8\n"), 0o644)) + []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.0\n"), 0o644)) t.Setenv("GOFASTA_REPLACE", fakeFramework) withFakeExec(t, 0) @@ -567,21 +568,21 @@ func TestRunRoutes_SampleProject(t *testing.T) { routesDir := "app/rest/routes" require.NoError(t, os.MkdirAll(routesDir, 0755)) - // Index file includes a .PathPrefix("...").Subrouter() call so the + // Index file includes a chi r.Mount("...", ...) call so the // prefix extraction regex matches and apiPrefix gets set to "/api/v1". index := `package routes -func InitApi(r *mux.Router) { - api := r.PathPrefix("/api/v1").Subrouter() - r.HandleFunc("/health", httputil.Handle(c.Ok)).Methods("GET") - r.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) - _ = api +func InitApi(r *chi.Mux) { + api := chi.NewRouter() + r.Get("/health", httputil.Handle(c.Ok)) + r.Handle("/swagger/*", httpSwagger.WrapHandler) + r.Mount("/api/v1", api) }` require.NoError(t, os.WriteFile(routesDir+"/index.routes.go", []byte(index), 0644)) user := `package routes -func UserRoutes(r *mux.Router) { - r.HandleFunc("/users", httputil.Handle(c.List)).Methods("GET") - r.HandleFunc("/users/{id}", httputil.Handle(c.Get)).Methods("GET") +func UserRoutes(r chi.Router) { + r.Get("/users", httputil.Handle(c.List)) + r.Get("/users/{id}", httputil.Handle(c.Get)) }` require.NoError(t, os.WriteFile(routesDir+"/user.routes.go", []byte(user), 0644)) @@ -599,7 +600,7 @@ func TestRunRoutes_NoIndexFile(t *testing.T) { routesDir := "app/rest/routes" require.NoError(t, os.MkdirAll(routesDir, 0755)) // Only a non-index file — apiPrefix will stay empty - user := `r.HandleFunc("/a", x).Methods("GET")` + user := `r.Get("/a", x)` require.NoError(t, os.WriteFile(routesDir+"/a.routes.go", []byte(user), 0644)) assert.NoError(t, runRoutes()) } @@ -631,7 +632,7 @@ func TestRunRoutes_UnreadableRouteFile(t *testing.T) { // list the file (only needs execute on the parent dir), but ReadFile // fails with EACCES. The file should be silently skipped. require.NoError(t, os.WriteFile(routesDir+"/blocked.routes.go", - []byte(`r.HandleFunc("/x", h).Methods("GET")`), 0o000)) + []byte(`r.Get("/x", h)`), 0o000)) t.Cleanup(func() { _ = os.Chmod(routesDir+"/blocked.routes.go", 0o644) }) // Should not error — unreadable files are skipped (continue branch). diff --git a/internal/commands/configutil/configutil_test.go b/internal/commands/configutil/configutil_test.go index 50f2afc..6dd0867 100644 --- a/internal/commands/configutil/configutil_test.go +++ b/internal/commands/configutil/configutil_test.go @@ -263,6 +263,6 @@ func TestEnvPrefixes_DeDupesGofasta(t *testing.T) { // (which setupConfigDir has already set to a fresh tempdir). func writeGoMod(t *testing.T, modulePath string) { t.Helper() - content := "module " + modulePath + "\n\ngo 1.25.8\n" + content := "module " + modulePath + "\n\ngo 1.25.0\n" require.NoError(t, os.WriteFile("go.mod", []byte(content), 0o644)) } diff --git a/internal/commands/new.go b/internal/commands/new.go index 99b172d..980705c 100644 --- a/internal/commands/new.go +++ b/internal/commands/new.go @@ -121,8 +121,7 @@ func runNew(nameOrPath string, includeGraphQL bool) error { ProjectName: projectName, ProjectNameLower: strings.ToLower(projectName), // Upper variant is used as an env-var prefix in compose.yaml, - // .env.example, k8s deployment.yaml, CI workflows, and the - // generated LoadConfig wrapper. Shell variable names only allow + // .env.example, CI workflows, and the generated LoadConfig wrapper. Shell variable names only allow // [A-Z0-9_], so we strip anything else (dashes, dots, etc.) — // otherwise a project named "my-app" would produce invalid env // vars like "MY-APP_DATABASE_HOST" and the framework would never @@ -153,6 +152,16 @@ func runNew(nameOrPath string, includeGraphQL bool) error { if err := runCmdSilent("go", "mod", "init", modulePath); err != nil { return fmt.Errorf("go mod init failed: %w", err) } + // `go mod init` writes the current toolchain version as the `go` directive + // — so a developer running Go 1.27 would get `go 1.27` in their scaffold + // even though we only require 1.25.0. Normalise to the declared minimum so + // generated projects match our stated support floor regardless of the + // developer's local toolchain. Best-effort: if this fails, the scaffold + // still works, it just ships with the developer's toolchain version + // instead of the declared minimum. + if err := runCmdSilent("go", "mod", "edit", "-go=1.25.0"); err != nil { + termcolor.PrintWarn("Could not normalise go directive to 1.25.0 (generated go.mod may pin a higher version): %v", err) + } // Walk embedded skeleton and generate files termcolor.PrintStep("🏗 Creating project structure...") @@ -302,6 +311,7 @@ func runNew(nameOrPath string, includeGraphQL bool) error { _ = runCmdSilent("go", "get", "github.com/air-verse/air@latest") _ = runCmdSilent("go", "get", "github.com/swaggo/swag/cmd/swag@latest") _ = runCmdSilent("go", "get", "github.com/swaggo/http-swagger/v2@latest") + _ = runCmdSilent("go", "get", "github.com/go-chi/chi/v5@latest") // Register as Go tools if includeGraphQL { _ = runCmdSilent("go", "mod", "edit", "-tool", "github.com/99designs/gqlgen") diff --git a/internal/commands/new_test.go b/internal/commands/new_test.go index 7dbac74..9f1d943 100644 --- a/internal/commands/new_test.go +++ b/internal/commands/new_test.go @@ -197,7 +197,7 @@ func TestInstallGofastaFromLocal_HappyPath(t *testing.T) { // Create a fake "gofasta checkout" — a directory with a go.mod inside. fakeFramework := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(fakeFramework, "go.mod"), - []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.8\n"), 0o644)) + []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.0\n"), 0o644)) // Mock execCommand so the `go mod edit` calls "succeed" without // actually hitting the real go binary. withFakeExec(t, 0) @@ -211,7 +211,7 @@ func TestInstallGofastaFromLocal_EditRequireFails(t *testing.T) { setupGoMod(t) fakeFramework := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(fakeFramework, "go.mod"), - []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.8\n"), 0o644)) + []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.0\n"), 0o644)) withFakeExec(t, 1) // every exec fails err := installGofastaFromLocal(fakeFramework) @@ -226,7 +226,7 @@ func TestInstallGofastaFromLocal_EditReplaceFails(t *testing.T) { setupGoMod(t) fakeFramework := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(fakeFramework, "go.mod"), - []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.8\n"), 0o644)) + []byte("module github.com/gofastadev/gofasta\n\ngo 1.25.0\n"), 0o644)) stagedFakeExec(t, 0, 1) // require ok, replace fails err := installGofastaFromLocal(fakeFramework) @@ -253,7 +253,7 @@ func TestRunNew_GofastaReplaceBadPath(t *testing.T) { func setupGoMod(t *testing.T) { t.Helper() require.NoError(t, os.WriteFile("go.mod", - []byte("module testproject\n\ngo 1.25.8\n"), 0o644)) + []byte("module testproject\n\ngo 1.25.0\n"), 0o644)) } // chdirTemp is a lightweight helper that pins the test to a fresh temp diff --git a/internal/commands/routes.go b/internal/commands/routes.go index ef0885e..2c54daa 100644 --- a/internal/commands/routes.go +++ b/internal/commands/routes.go @@ -13,11 +13,11 @@ import ( var routesCmd = &cobra.Command{ Use: "routes", Short: "List every registered REST route in a table (method, path, source)", - Long: `Statically parse every file under app/rest/routes/ for ` + "`router.GET`" + ` / -` + "`router.POST`" + ` / ` + "`router.PUT`" + ` / ` + "`router.DELETE`" + ` / ` + "`router.PATCH`" + ` calls and print -a formatted table showing HTTP method, full path (including router group -prefixes), and source file. Does not import or run your project code — -purely a grep-and-format pass. + Long: `Statically parse every file under app/rest/routes/ for chi router +method calls (` + "`r.Get`" + `, ` + "`r.Post`" + `, ` + "`r.Put`" + `, ` + "`r.Delete`" + `, ` + "`r.Patch`" + `) and print +a formatted table showing HTTP method, full path (including mounted +subrouter prefixes), and source file. Does not import or run your project +code — purely a grep-and-format pass. Useful for debugging route conflicts, documenting the public API, and spotting unregistered handlers. For GraphQL schema introspection use @@ -38,9 +38,9 @@ type routeEntry struct { } var ( - handleFuncRe = regexp.MustCompile(`\.HandleFunc\("([^"]+)",.+\.Methods\("([^"]+)"\)`) - prefixRe = regexp.MustCompile(`\.PathPrefix\("([^"]+)"\)\.Subrouter\(\)`) - pathHandlerRe = regexp.MustCompile(`\.PathPrefix\("([^"]+)"\)\.Handler\(`) + routeMethodRe = regexp.MustCompile(`\.(Get|Post|Put|Delete|Patch|Head|Options)\("([^"]+)",`) + mountRe = regexp.MustCompile(`\.Mount\("([^"]+)",`) + wildcardRe = regexp.MustCompile(`\.Handle\("([^"]+\*)",`) ) func runRoutes() error { @@ -54,11 +54,11 @@ func runRoutes() error { return fmt.Errorf("failed to read routes directory: %w", err) } - // Extract API prefix from index file + // Extract API prefix from index file via the chi Mount call. apiPrefix := "" indexPath := routesDir + "/index.routes.go" if indexContent, err := os.ReadFile(indexPath); err == nil { - if matches := prefixRe.FindSubmatch(indexContent); len(matches) > 1 { + if matches := mountRe.FindSubmatch(indexContent); len(matches) > 1 { apiPrefix = string(matches[1]) } } @@ -98,29 +98,28 @@ func runRoutes() error { } func extractRoutes(content, prefix, filename string) []routeEntry { - var routes []routeEntry - - // Match .HandleFunc("path", ...).Methods("METHOD") - for _, m := range handleFuncRe.FindAllStringSubmatch(content, -1) { + methodMatches := routeMethodRe.FindAllStringSubmatch(content, -1) + wildcardMatches := wildcardRe.FindAllStringSubmatch(content, -1) + routes := make([]routeEntry, 0, len(methodMatches)+len(wildcardMatches)) + + // Match r.Get("path", ...) / r.Post / r.Put / r.Delete / r.Patch — chi's + // method-based API. The captured method name is already uppercase-first, + // so convert to HTTP verb with ToUpper. + for _, m := range methodMatches { routes = append(routes, routeEntry{ - method: m[2], - path: prefix + m[1], + method: strings.ToUpper(m[1]), + path: prefix + m[2], filename: filename, }) } - // Match .PathPrefix("path").Handler(...) — used by swagger UI and - // other prefix-mounted handlers. Shown as GET since they serve content. - for _, m := range pathHandlerRe.FindAllStringSubmatch(content, -1) { - path := m[1] - // Don't pick up the API subrouter prefix (e.g. /api/v1) — that's - // already handled by prefixRe and used as a prefix for child routes. - if path == prefix || path == prefix+"/" { - continue - } + // Match r.Handle("path/*", ...) — used by swagger UI and other + // wildcard-mounted handlers. Shown as GET since they serve content. + // chi patterns already include the trailing wildcard, so display as-is. + for _, m := range wildcardMatches { routes = append(routes, routeEntry{ method: "GET", - path: path + "*", + path: m[1], filename: filename, }) } diff --git a/internal/commands/routes_test.go b/internal/commands/routes_test.go index b5d8256..99b7f34 100644 --- a/internal/commands/routes_test.go +++ b/internal/commands/routes_test.go @@ -26,12 +26,12 @@ func TestRoutesCmd_HasDescription(t *testing.T) { func TestExtractRoutes_BasicRouteFile(t *testing.T) { content := `package routes -func UserRoutes(r *mux.Router, c *controllers.UserController) { - r.HandleFunc("/users", httputil.Handle(c.List)).Methods("GET") - r.HandleFunc("/users", httputil.Handle(c.Create)).Methods("POST") - r.HandleFunc("/users/{id}", httputil.Handle(c.GetByID)).Methods("GET") - r.HandleFunc("/users/{id}", httputil.Handle(c.Update)).Methods("PUT") - r.HandleFunc("/users/{id}", httputil.Handle(c.Archive)).Methods("DELETE") +func UserRoutes(r chi.Router, c *controllers.UserController) { + r.Get("/users", httputil.Handle(c.List)) + r.Post("/users", httputil.Handle(c.Create)) + r.Get("/users/{id}", httputil.Handle(c.GetByID)) + r.Put("/users/{id}", httputil.Handle(c.Update)) + r.Delete("/users/{id}", httputil.Handle(c.Archive)) }` routes := extractRoutes(content, "/api/v1", "user.routes.go") @@ -51,10 +51,10 @@ func UserRoutes(r *mux.Router, c *controllers.UserController) { func TestExtractRoutes_IndexFile(t *testing.T) { content := `package routes -func InitApiRoutes(config *RouteConfig) *mux.Router { - r.HandleFunc("/health", httputil.Handle(config.HealthController.Check)).Methods("GET") - r.HandleFunc("/health/live", httputil.Handle(config.HealthController.Live)).Methods("GET") - r.HandleFunc("/health/ready", httputil.Handle(config.HealthController.Ready)).Methods("GET") +func InitApiRoutes(config *RouteConfig) *chi.Mux { + r.Get("/health", httputil.Handle(config.HealthController.Check)) + r.Get("/health/live", httputil.Handle(config.HealthController.Live)) + r.Get("/health/ready", httputil.Handle(config.HealthController.Ready)) }` routes := extractRoutes(content, "", "index.routes.go") @@ -66,12 +66,12 @@ func InitApiRoutes(config *RouteConfig) *mux.Router { assert.Equal(t, "/health/ready", routes[2].path) } -func TestExtractRoutes_PathPrefixHandler(t *testing.T) { +func TestExtractRoutes_WildcardHandler(t *testing.T) { content := `package routes -func InitApiRoutes(config *RouteConfig) *mux.Router { - r.HandleFunc("/health", httputil.Handle(config.HealthController.Check)).Methods("GET") - r.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) +func InitApiRoutes(config *RouteConfig) *chi.Mux { + r.Get("/health", httputil.Handle(config.HealthController.Check)) + r.Handle("/swagger/*", httpSwagger.WrapHandler) }` routes := extractRoutes(content, "", "index.routes.go") @@ -79,32 +79,18 @@ func InitApiRoutes(config *RouteConfig) *mux.Router { assert.Len(t, routes, 2) assert.Equal(t, "GET", routes[0].method) assert.Equal(t, "/health", routes[0].path) - // PathPrefix-mounted handlers show as GET with a trailing wildcard + // Wildcard-mounted handlers show as GET with the pattern as-is. assert.Equal(t, "GET", routes[1].method) assert.Equal(t, "/swagger/*", routes[1].path) } -func TestExtractRoutes_PathPrefixSkipsAPIPrefix(t *testing.T) { - // When a .PathPrefix("...").Handler(...) call uses the same path as the - // API prefix passed to extractRoutes, it should be skipped — it's the - // subrouter setup, not a user-facing endpoint. Test both exact match - // and trailing-slash match. - content := `r.PathPrefix("/api/v1").Handler(someHandler)` - routes := extractRoutes(content, "/api/v1", "index.routes.go") - assert.Empty(t, routes, "exact prefix match should be skipped") - - content2 := `r.PathPrefix("/api/v1/").Handler(someHandler)` - routes2 := extractRoutes(content2, "/api/v1", "index.routes.go") - assert.Empty(t, routes2, "prefix+slash match should be skipped") -} - func TestExtractRoutes_EmptyContent(t *testing.T) { routes := extractRoutes("package routes", "/api/v1", "empty.routes.go") assert.Empty(t, routes) } func TestExtractRoutes_NoPrefix(t *testing.T) { - content := `r.HandleFunc("/test", httputil.Handle(c.Test)).Methods("GET")` + content := `r.Get("/test", httputil.Handle(c.Test))` routes := extractRoutes(content, "", "test.routes.go") assert.Len(t, routes, 1) diff --git a/internal/generate/commands_runE_test.go b/internal/generate/commands_runE_test.go index ec06d4e..1fb8bf2 100644 --- a/internal/generate/commands_runE_test.go +++ b/internal/generate/commands_runE_test.go @@ -69,14 +69,16 @@ var ProviderSet = wire.NewSet( require.NoError(t, os.MkdirAll("app/rest/routes", 0755)) require.NoError(t, os.WriteFile("app/rest/routes/index.routes.go", []byte(`package routes -import "github.com/gorilla/mux" +import "github.com/go-chi/chi/v5" type RouteConfig struct { // controllers } -func InitApiRoutes(config *RouteConfig) *mux.Router { - r := mux.NewRouter() +func InitApiRoutes(config *RouteConfig) *chi.Mux { + r := chi.NewRouter() + api := chi.NewRouter() + r.Mount("/api/v1", api) return r } `), 0644)) diff --git a/internal/generate/patcher.go b/internal/generate/patcher.go index 92b73f0..a7356bc 100644 --- a/internal/generate/patcher.go +++ b/internal/generate/patcher.go @@ -127,7 +127,8 @@ func PatchRouteConfig(d ScaffoldData) error { 1) routeCall := fmt.Sprintf("\t%sRoutes(api, config.%s)\n", d.Name, controllerField) - s = strings.Replace(s, "\n\treturn r", routeCall+"\n\treturn r", 1) + mountLine := "\tr.Mount(\"/api/v1\", api)" + s = strings.Replace(s, mountLine, routeCall+mountLine, 1) termcolor.PrintPatch(path, "") return os.WriteFile(path, []byte(s), 0o644) diff --git a/internal/generate/patcher_test.go b/internal/generate/patcher_test.go index 378021e..177b3fd 100644 --- a/internal/generate/patcher_test.go +++ b/internal/generate/patcher_test.go @@ -205,8 +205,10 @@ type RouteConfig struct { HealthController *health.Controller } -func InitApiRoutes(config *RouteConfig) *mux.Router { - r := mux.NewRouter() +func InitApiRoutes(config *RouteConfig) *chi.Mux { + r := chi.NewRouter() + api := chi.NewRouter() + r.Mount("/api/v1", api) return r } ` diff --git a/internal/generate/scaffold_data_test.go b/internal/generate/scaffold_data_test.go index 183aecc..b3174f5 100644 --- a/internal/generate/scaffold_data_test.go +++ b/internal/generate/scaffold_data_test.go @@ -71,7 +71,7 @@ func TestReadModulePath_NoModuleLine(t *testing.T) { dir := t.TempDir() origDir, _ := os.Getwd() t.Cleanup(func() { os.Chdir(origDir) }) - os.WriteFile(filepath.Join(dir, "go.mod"), []byte("go 1.25.8\n"), 0644) + os.WriteFile(filepath.Join(dir, "go.mod"), []byte("go 1.25.0\n"), 0644) os.Chdir(dir) assert.Equal(t, "", readModulePath()) } @@ -142,7 +142,7 @@ func TestBuildScaffoldData_NoConfig(t *testing.T) { origDir, _ := os.Getwd() t.Cleanup(func() { os.Chdir(origDir) }) os.MkdirAll(filepath.Join(dir, "db", "migrations"), 0755) - os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module testmod\n\ngo 1.25.8\n"), 0644) + os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module testmod\n\ngo 1.25.0\n"), 0644) os.Chdir(dir) d := BuildScaffoldData("item", nil) diff --git a/internal/generate/templates/controller.go b/internal/generate/templates/controller.go index ac5e839..5047fa1 100644 --- a/internal/generate/templates/controller.go +++ b/internal/generate/templates/controller.go @@ -7,7 +7,7 @@ import ( "encoding/json" "net/http" - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" "{{.ModulePath}}/app/dtos" svcInterfaces "{{.ModulePath}}/app/services/interfaces" "github.com/gofastadev/gofasta/pkg/utils" @@ -62,7 +62,7 @@ func (c *{{.Name}}Controller) List(w http.ResponseWriter, r *http.Request) error // @Router /{{.PluralLower}}/{id} [get] {{- end}} func (c *{{.Name}}Controller) GetByID(w http.ResponseWriter, r *http.Request) error { - id, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + id, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } @@ -115,7 +115,7 @@ func (c *{{.Name}}Controller) Create(w http.ResponseWriter, r *http.Request) err // @Router /{{.PluralLower}}/{id} [put] {{- end}} func (c *{{.Name}}Controller) Update(w http.ResponseWriter, r *http.Request) error { - id, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + id, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } @@ -146,7 +146,7 @@ func (c *{{.Name}}Controller) Update(w http.ResponseWriter, r *http.Request) err // @Router /{{.PluralLower}}/{id} [delete] {{- end}} func (c *{{.Name}}Controller) Archive(w http.ResponseWriter, r *http.Request) error { - id, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + id, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } diff --git a/internal/generate/templates/routes.go b/internal/generate/templates/routes.go index 480c519..deecfcc 100644 --- a/internal/generate/templates/routes.go +++ b/internal/generate/templates/routes.go @@ -4,16 +4,16 @@ package templates var Routes = `package routes import ( - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" "{{.ModulePath}}/app/rest/controllers" "github.com/gofastadev/gofasta/pkg/httputil" ) -func {{.Name}}Routes(r *mux.Router, c *controllers.{{.Name}}Controller) { - r.HandleFunc("/{{.PluralSnake}}", httputil.Handle(c.List)).Methods("GET") - r.HandleFunc("/{{.PluralSnake}}", httputil.Handle(c.Create)).Methods("POST") - r.HandleFunc("/{{.PluralSnake}}/{id}", httputil.Handle(c.GetByID)).Methods("GET") - r.HandleFunc("/{{.PluralSnake}}/{id}", httputil.Handle(c.Update)).Methods("PUT") - r.HandleFunc("/{{.PluralSnake}}/{id}", httputil.Handle(c.Archive)).Methods("DELETE") +func {{.Name}}Routes(r chi.Router, c *controllers.{{.Name}}Controller) { + r.Get("/{{.PluralSnake}}", httputil.Handle(c.List)) + r.Post("/{{.PluralSnake}}", httputil.Handle(c.Create)) + r.Get("/{{.PluralSnake}}/{id}", httputil.Handle(c.GetByID)) + r.Put("/{{.PluralSnake}}/{id}", httputil.Handle(c.Update)) + r.Delete("/{{.PluralSnake}}/{id}", httputil.Handle(c.Archive)) } ` diff --git a/internal/generate/testhelpers_test.go b/internal/generate/testhelpers_test.go index 52d3f42..55e26d3 100644 --- a/internal/generate/testhelpers_test.go +++ b/internal/generate/testhelpers_test.go @@ -18,7 +18,7 @@ func setupTempProject(t *testing.T) { t.Cleanup(func() { os.Chdir(origDir) }) os.MkdirAll(filepath.Join(dir, "db", "migrations"), 0755) - os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module github.com/testorg/testapp\n\ngo 1.25.8\n"), 0644) + os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module github.com/testorg/testapp\n\ngo 1.25.0\n"), 0644) os.WriteFile(filepath.Join(dir, "config.yaml"), []byte("database:\n driver: postgres\n"), 0644) if err := os.Chdir(dir); err != nil { diff --git a/internal/skeleton/project/Dockerfile b/internal/skeleton/project/Dockerfile index 00639d3..20b2efc 100644 --- a/internal/skeleton/project/Dockerfile +++ b/internal/skeleton/project/Dockerfile @@ -1,12 +1,13 @@ # Production Dockerfile # Multi-stage build: compiles a static binary, runs on minimal Alpine. -# Works with: AWS ECS/ECR, GCP Cloud Run/GCR, Azure Container Apps/ACR, any VPS with Docker. +# Designed for VPS deployment with Docker — paired with `gofasta deploy` +# (which ships the built image over SSH and starts it via Docker Compose). # # Build: docker build -t myapp . # Run: docker run -p 8080:8080 --env-file .env myapp # ── Stage 1: Build ── -FROM golang:1.25.8-alpine AS builder +FROM golang:1.25.0-alpine AS builder RUN apk add --no-cache git diff --git a/internal/skeleton/project/app/di/providers/core.go.tmpl b/internal/skeleton/project/app/di/providers/core.go.tmpl index 98fce50..30803ce 100644 --- a/internal/skeleton/project/app/di/providers/core.go.tmpl +++ b/internal/skeleton/project/app/di/providers/core.go.tmpl @@ -25,9 +25,8 @@ import ( // It calls config.LoadConfigWithPrefix with the project's uppercased name // as the env var prefix, so variables like {{.ProjectNameUpper}}_DATABASE_HOST // are honored. Using the project's own name as the prefix means the .env -// file, docker-compose, CI configs, and k8s manifests all read variables -// named after the project itself — no toolkit branding leaks into your -// infrastructure files. A developer who later replaces pkg/config with a +// file, docker-compose, and CI configs all read variables named after the +// project itself — no toolkit branding leaks into your infrastructure files. A developer who later replaces pkg/config with a // different loader can reuse the same env var names without renaming // anything in their deployment configs. func LoadConfig() (*config.AppConfig, error) { diff --git a/internal/skeleton/project/app/rest/controllers/user.controller.go.tmpl b/internal/skeleton/project/app/rest/controllers/user.controller.go.tmpl index 5e743bd..0496d97 100644 --- a/internal/skeleton/project/app/rest/controllers/user.controller.go.tmpl +++ b/internal/skeleton/project/app/rest/controllers/user.controller.go.tmpl @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" "github.com/gorilla/schema" "{{.ModulePath}}/app/dtos" svcInterfaces "{{.ModulePath}}/app/services/interfaces" @@ -115,7 +115,7 @@ func (uc *UserController) CreateUser(w http.ResponseWriter, r *http.Request) err // @Failure 500 {object} dtos.TCommonAPIErrorDto // @Router /users/{id} [put] func (uc *UserController) UpdateUser(w http.ResponseWriter, r *http.Request) error { - userId, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + userId, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } @@ -146,7 +146,7 @@ func (uc *UserController) UpdateUser(w http.ResponseWriter, r *http.Request) err // @Failure 500 {object} dtos.TCommonAPIErrorDto // @Router /users/{id} [get] func (uc *UserController) GetUser(w http.ResponseWriter, r *http.Request) error { - userId, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + userId, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } @@ -171,7 +171,7 @@ func (uc *UserController) GetUser(w http.ResponseWriter, r *http.Request) error // @Failure 500 {object} dtos.TCommonAPIErrorDto // @Router /users/{id} [delete] func (uc *UserController) ArchiveUser(w http.ResponseWriter, r *http.Request) error { - userId, err := utils.ParseIDStringIsValidUUID(mux.Vars(r)["id"]) + userId, err := utils.ParseIDStringIsValidUUID(chi.URLParam(r, "id")) if err != nil { return apperrors.NewBadRequest("id should be a valid UUID", nil) } diff --git a/internal/skeleton/project/app/rest/routes/index.routes.go.tmpl b/internal/skeleton/project/app/rest/routes/index.routes.go.tmpl index 89aabd7..d4dc766 100644 --- a/internal/skeleton/project/app/rest/routes/index.routes.go.tmpl +++ b/internal/skeleton/project/app/rest/routes/index.routes.go.tmpl @@ -3,7 +3,7 @@ package routes import ( "net/http" - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" httpSwagger "github.com/swaggo/http-swagger/v2" "{{.ModulePath}}/app/rest/controllers" _ "{{.ModulePath}}/docs" @@ -19,30 +19,31 @@ type RouteConfig struct { WebSocketHub *websocket.Hub } -func InitApiRoutes(config *RouteConfig) *mux.Router { - r := mux.NewRouter() +func InitApiRoutes(config *RouteConfig) *chi.Mux { + r := chi.NewRouter() // Health checks if config.HealthController != nil { - r.HandleFunc("/health", httputil.Handle(config.HealthController.Check)).Methods("GET") - r.HandleFunc("/health/live", httputil.Handle(config.HealthController.Live)).Methods("GET") - r.HandleFunc("/health/ready", httputil.Handle(config.HealthController.Ready)).Methods("GET") + r.Get("/health", httputil.Handle(config.HealthController.Check)) + r.Get("/health/live", httputil.Handle(config.HealthController.Live)) + r.Get("/health/ready", httputil.Handle(config.HealthController.Ready)) } // Swagger UI — OpenAPI docs generated by `gofasta swagger`. // Visit /swagger/index.html in the browser to explore the API. - r.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) + r.Handle("/swagger/*", httpSwagger.WrapHandler) // WebSocket endpoint if config.WebSocketHub != nil { - r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + r.Get("/ws", func(w http.ResponseWriter, r *http.Request) { websocket.ServeWS(config.WebSocketHub, w, r) }) } - // API v1 routes - api := r.PathPrefix("/api/v1").Subrouter() + // API v1 routes — register new resource routes above the Mount call. + api := chi.NewRouter() UserRoutes(api, config.UserController) + r.Mount("/api/v1", api) return r } diff --git a/internal/skeleton/project/app/rest/routes/user.routes.go.tmpl b/internal/skeleton/project/app/rest/routes/user.routes.go.tmpl index af26825..33e03d9 100644 --- a/internal/skeleton/project/app/rest/routes/user.routes.go.tmpl +++ b/internal/skeleton/project/app/rest/routes/user.routes.go.tmpl @@ -1,15 +1,15 @@ package routes import ( - "github.com/gorilla/mux" + "github.com/go-chi/chi/v5" "{{.ModulePath}}/app/rest/controllers" "github.com/gofastadev/gofasta/pkg/httputil" ) -func UserRoutes(r *mux.Router, uc *controllers.UserController) { - r.HandleFunc("/users", httputil.Handle(uc.ListUsers)).Methods("GET") - r.HandleFunc("/users", httputil.Handle(uc.CreateUser)).Methods("POST") - r.HandleFunc("/users/{id}", httputil.Handle(uc.GetUser)).Methods("GET") - r.HandleFunc("/users/{id}", httputil.Handle(uc.UpdateUser)).Methods("PUT") - r.HandleFunc("/users/{id}", httputil.Handle(uc.ArchiveUser)).Methods("DELETE") +func UserRoutes(r chi.Router, uc *controllers.UserController) { + r.Get("/users", httputil.Handle(uc.ListUsers)) + r.Post("/users", httputil.Handle(uc.CreateUser)) + r.Get("/users/{id}", httputil.Handle(uc.GetUser)) + r.Put("/users/{id}", httputil.Handle(uc.UpdateUser)) + r.Delete("/users/{id}", httputil.Handle(uc.ArchiveUser)) } diff --git a/internal/skeleton/project/config.yaml.tmpl b/internal/skeleton/project/config.yaml.tmpl index cc8872a..9766392 100644 --- a/internal/skeleton/project/config.yaml.tmpl +++ b/internal/skeleton/project/config.yaml.tmpl @@ -99,6 +99,11 @@ i18n: locales_dir: locales feature_flag: + # Master switch consumed by `pkg/featureflag`. When false the service is a + # no-op and every flag resolves to its caller-supplied default. When true + # the service evaluates through whichever OpenFeature provider the app + # registered at startup (in-memory, Flagd, LaunchDarkly, go-feature-flag, + # etc.). See `configs/features.yaml` for notes. enabled: false config_path: configs/features.yaml diff --git a/internal/skeleton/project/configs/features.yaml b/internal/skeleton/project/configs/features.yaml index 212cbe4..5c536c5 100644 --- a/internal/skeleton/project/configs/features.yaml +++ b/internal/skeleton/project/configs/features.yaml @@ -1,14 +1,17 @@ -# Feature Flags Configuration -# See: https://docs.gofeatureflag.org/ +# Feature flag bootstrap data. # -# Example: -# new-dashboard: -# variations: -# enabled: true -# disabled: false -# defaultRule: -# variation: disabled -# targeting: -# - name: beta-users -# query: userID eq "admin" -# variation: enabled +# gofasta ships `pkg/featureflag` as a thin wrapper around the OpenFeature Go +# SDK (https://openfeature.dev). OpenFeature evaluates flags through a +# *provider* — a swappable adapter that can be: +# +# • in-memory — flags defined in Go code, zero network, good for dev +# • Flagd — CNCF-standard sidecar (gRPC) +# • LaunchDarkly — commercial SaaS +# • go-feature-flag — file-driven rules + AB testing, self-hosted +# • any custom provider that implements openfeature.FeatureProvider +# +# This file is intentionally empty — the default scaffold does not register a +# provider, so every flag resolves to the default value its caller supplies. +# To opt in, register a provider during startup with +# `openfeature.SetProvider(...)` and shape this file to match its config +# format (if the provider you pick is file-driven). diff --git a/internal/skeleton/project/deployments/ci/github-actions-deploy-aws.yml.tmpl b/internal/skeleton/project/deployments/ci/github-actions-deploy-aws.yml.tmpl deleted file mode 100644 index c27953e..0000000 --- a/internal/skeleton/project/deployments/ci/github-actions-deploy-aws.yml.tmpl +++ /dev/null @@ -1,48 +0,0 @@ -# .github/workflows/deploy-aws.yml -# Deploy to AWS ECS (Fargate) via ECR. -# Copy to .github/workflows/ and set GitHub secrets: -# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, ECR_REPOSITORY, ECS_CLUSTER, ECS_SERVICE - -name: Deploy to AWS - -on: - push: - branches: [main] - -env: - AWS_REGION: us-east-1 - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{"{{"}} secrets.AWS_ACCESS_KEY_ID {{"}}"}} - aws-secret-access-key: ${{"{{"}} secrets.AWS_SECRET_ACCESS_KEY {{"}}"}} - aws-region: ${{"{{"}} env.AWS_REGION {{"}}"}} - - - name: Login to ECR - id: ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Build and push Docker image - env: - ECR_REGISTRY: ${{"{{"}} steps.ecr.outputs.registry {{"}}"}} - ECR_REPOSITORY: ${{"{{"}} secrets.ECR_REPOSITORY {{"}}"}} - IMAGE_TAG: ${{"{{"}} github.sha {{"}}"}} - run: | - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -t $ECR_REGISTRY/$ECR_REPOSITORY:latest . - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest - - - name: Deploy to ECS - run: | - aws ecs update-service \ - --cluster ${{"{{"}} secrets.ECS_CLUSTER {{"}}"}} \ - --service ${{"{{"}} secrets.ECS_SERVICE {{"}}"}} \ - --force-new-deployment diff --git a/internal/skeleton/project/deployments/ci/github-actions-deploy-azure.yml.tmpl b/internal/skeleton/project/deployments/ci/github-actions-deploy-azure.yml.tmpl deleted file mode 100644 index 2b15fad..0000000 --- a/internal/skeleton/project/deployments/ci/github-actions-deploy-azure.yml.tmpl +++ /dev/null @@ -1,45 +0,0 @@ -# .github/workflows/deploy-azure.yml -# Deploy to Azure Container Apps via ACR. -# Copy to .github/workflows/ and set GitHub secrets: -# AZURE_CREDENTIALS (service principal JSON), ACR_LOGIN_SERVER, ACR_USERNAME, ACR_PASSWORD, -# AZURE_RESOURCE_GROUP, AZURE_CONTAINER_APP - -name: Deploy to Azure - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Login to Azure - uses: azure/login@v2 - with: - creds: ${{"{{"}} secrets.AZURE_CREDENTIALS {{"}}"}} - - - name: Login to ACR - uses: azure/docker-login@v2 - with: - login-server: ${{"{{"}} secrets.ACR_LOGIN_SERVER {{"}}"}} - username: ${{"{{"}} secrets.ACR_USERNAME {{"}}"}} - password: ${{"{{"}} secrets.ACR_PASSWORD {{"}}"}} - - - name: Build and push Docker image - env: - IMAGE: ${{"{{"}} secrets.ACR_LOGIN_SERVER {{"}}"}}/{{.ProjectNameLower}} - run: | - docker build -t $IMAGE:${{"{{"}} github.sha {{"}}"}} -t $IMAGE:latest . - docker push $IMAGE:${{"{{"}} github.sha {{"}}"}} - docker push $IMAGE:latest - - - name: Deploy to Container Apps - run: | - az containerapp update \ - --name ${{"{{"}} secrets.AZURE_CONTAINER_APP {{"}}"}} \ - --resource-group ${{"{{"}} secrets.AZURE_RESOURCE_GROUP {{"}}"}} \ - --image ${{"{{"}} secrets.ACR_LOGIN_SERVER {{"}}"}}/{{.ProjectNameLower}}:${{"{{"}} github.sha {{"}}"}} diff --git a/internal/skeleton/project/deployments/ci/github-actions-deploy-gcp.yml.tmpl b/internal/skeleton/project/deployments/ci/github-actions-deploy-gcp.yml.tmpl deleted file mode 100644 index ea2783c..0000000 --- a/internal/skeleton/project/deployments/ci/github-actions-deploy-gcp.yml.tmpl +++ /dev/null @@ -1,48 +0,0 @@ -# .github/workflows/deploy-gcp.yml -# Deploy to GCP Cloud Run via Artifact Registry. -# Copy to .github/workflows/ and set GitHub secrets: -# GCP_PROJECT_ID, GCP_SA_KEY (service account JSON), GCP_REGION, CLOUD_RUN_SERVICE - -name: Deploy to GCP - -on: - push: - branches: [main] - -env: - GCP_REGION: us-central1 - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Authenticate to GCP - uses: google-github-actions/auth@v2 - with: - credentials_json: ${{"{{"}} secrets.GCP_SA_KEY {{"}}"}} - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v2 - - - name: Configure Docker for Artifact Registry - run: gcloud auth configure-docker ${{"{{"}} env.GCP_REGION {{"}}"}}-docker.pkg.dev - - - name: Build and push Docker image - env: - IMAGE: ${{"{{"}} env.GCP_REGION {{"}}"}}-docker.pkg.dev/${{"{{"}} secrets.GCP_PROJECT_ID {{"}}"}}/{{.ProjectNameLower}}/app - run: | - docker build -t $IMAGE:${{"{{"}} github.sha {{"}}"}} -t $IMAGE:latest . - docker push $IMAGE:${{"{{"}} github.sha {{"}}"}} - docker push $IMAGE:latest - - - name: Deploy to Cloud Run - run: | - gcloud run deploy ${{"{{"}} secrets.CLOUD_RUN_SERVICE {{"}}"}} \ - --image ${{"{{"}} env.GCP_REGION {{"}}"}}-docker.pkg.dev/${{"{{"}} secrets.GCP_PROJECT_ID {{"}}"}}/{{.ProjectNameLower}}/app:${{"{{"}} github.sha {{"}}"}} \ - --region ${{"{{"}} env.GCP_REGION {{"}}"}} \ - --platform managed \ - --allow-unauthenticated \ - --port 8080 diff --git a/internal/skeleton/project/deployments/docker/dev.dockerfile.tmpl b/internal/skeleton/project/deployments/docker/dev.dockerfile.tmpl index df66ced..b64567b 100644 --- a/internal/skeleton/project/deployments/docker/dev.dockerfile.tmpl +++ b/internal/skeleton/project/deployments/docker/dev.dockerfile.tmpl @@ -1,7 +1,7 @@ # {{.ProjectName}} Development Dockerfile # Used by compose.yaml — mounts source code, runs air for hot reload. -FROM golang:1.25.8-alpine +FROM golang:1.25.0-alpine RUN apk add --no-cache git curl bash diff --git a/internal/skeleton/project/deployments/k8s/deployment.yaml.tmpl b/internal/skeleton/project/deployments/k8s/deployment.yaml.tmpl deleted file mode 100644 index 77ac7b8..0000000 --- a/internal/skeleton/project/deployments/k8s/deployment.yaml.tmpl +++ /dev/null @@ -1,60 +0,0 @@ -# {{.ProjectName}} Kubernetes Deployment -# Works with: AWS EKS, GCP GKE, Azure AKS, any k8s cluster. -# -# Deploy: -# kubectl apply -f deployments/k8s/ -# -# Customize: -# - Replace IMAGE with your registry image (e.g. 123456.dkr.ecr.us-east-1.amazonaws.com/myapp:latest) -# - Create the secret: kubectl create secret generic {{.ProjectNameLower}}-secrets --from-env-file=.env - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{.ProjectNameLower}} - labels: - app: {{.ProjectNameLower}} -spec: - replicas: 2 - selector: - matchLabels: - app: {{.ProjectNameLower}} - template: - metadata: - labels: - app: {{.ProjectNameLower}} - spec: - containers: - - name: {{.ProjectNameLower}} - image: IMAGE:TAG # Replace with your container registry image - ports: - - containerPort: 8080 - envFrom: - - secretRef: - name: {{.ProjectNameLower}}-secrets - env: - - name: {{.ProjectNameUpper}}_DATABASE_HOST - value: {{.ProjectNameLower}}-db # Points to the DB service below - - name: {{.ProjectNameUpper}}_DATABASE_PORT - value: "5432" - - name: {{.ProjectNameUpper}}_LOG_FORMAT - value: json - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi - livenessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 10 - periodSeconds: 15 - readinessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 5 - periodSeconds: 5 diff --git a/internal/skeleton/project/deployments/k8s/ingress.yaml.tmpl b/internal/skeleton/project/deployments/k8s/ingress.yaml.tmpl deleted file mode 100644 index 85ffcd8..0000000 --- a/internal/skeleton/project/deployments/k8s/ingress.yaml.tmpl +++ /dev/null @@ -1,33 +0,0 @@ -# Ingress — configure your domain and TLS. -# Works with nginx-ingress, traefik, ALB ingress controller, etc. - -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{.ProjectNameLower}} - annotations: - # Uncomment for nginx-ingress: - # kubernetes.io/ingress.class: nginx - # Uncomment for AWS ALB: - # kubernetes.io/ingress.class: alb - # alb.ingress.kubernetes.io/scheme: internet-facing - # Uncomment for cert-manager TLS: - # cert-manager.io/cluster-issuer: letsencrypt-prod - nginx.ingress.kubernetes.io/proxy-body-size: "10m" -spec: - # Uncomment for TLS: - # tls: - # - hosts: - # - api.example.com - # secretName: {{.ProjectNameLower}}-tls - rules: - - host: api.example.com # Replace with your domain - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: {{.ProjectNameLower}} - port: - number: 80 diff --git a/internal/skeleton/project/deployments/k8s/service.yaml.tmpl b/internal/skeleton/project/deployments/k8s/service.yaml.tmpl deleted file mode 100644 index 9f01d20..0000000 --- a/internal/skeleton/project/deployments/k8s/service.yaml.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{.ProjectNameLower}} - labels: - app: {{.ProjectNameLower}} -spec: - type: ClusterIP - ports: - - port: 80 - targetPort: 8080 - protocol: TCP - selector: - app: {{.ProjectNameLower}} - ---- -# Database service (for dev/staging — use managed DB in production) -apiVersion: v1 -kind: Service -metadata: - name: {{.ProjectNameLower}}-db - labels: - app: {{.ProjectNameLower}}-db -spec: - type: ClusterIP - ports: - - port: 5432 - targetPort: 5432 - selector: - app: {{.ProjectNameLower}}-db diff --git a/internal/skeleton/project/dot-go-version b/internal/skeleton/project/dot-go-version index e6a6e7c..ad21919 100644 --- a/internal/skeleton/project/dot-go-version +++ b/internal/skeleton/project/dot-go-version @@ -1 +1 @@ -1.25.8 +1.25.0