├── .github ├── semantic.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml ├── pull_request_template.md └── workflows │ ├── release.yml │ ├── testcontainers.yml │ ├── docs.yml │ ├── ci.yml │ ├── generate-linter-core.yml │ ├── npm-publish.yml │ ├── generate-linter-advanced.yml │ └── update-htmx-version.yml ├── .gitignore ├── cmd ├── template │ ├── advanced │ │ ├── files │ │ │ ├── tailwind │ │ │ │ ├── output.css.tmpl │ │ │ │ └── input.css.tmpl │ │ │ ├── react │ │ │ │ ├── tailwind │ │ │ │ │ ├── index.css.tmpl │ │ │ │ │ ├── vite.config.ts.tmpl │ │ │ │ │ └── app.tsx.tmpl │ │ │ │ └── app.tsx.tmpl │ │ │ ├── websocket │ │ │ │ └── imports │ │ │ │ │ ├── fiber.tmpl │ │ │ │ │ └── standard_library.tmpl │ │ │ ├── htmx │ │ │ │ ├── imports │ │ │ │ │ ├── standard_library.tmpl │ │ │ │ │ ├── gin.tmpl │ │ │ │ │ └── fiber.tmpl │ │ │ │ ├── efs.go.tmpl │ │ │ │ ├── tailwind │ │ │ │ │ └── tailwind.config.js.tmpl │ │ │ │ ├── routes │ │ │ │ │ ├── chi.tmpl │ │ │ │ │ ├── standard_library.tmpl │ │ │ │ │ ├── echo.tmpl │ │ │ │ │ ├── http_router.tmpl │ │ │ │ │ ├── gorilla.tmpl │ │ │ │ │ ├── gin.tmpl │ │ │ │ │ └── fiber.tmpl │ │ │ │ ├── hello.go.tmpl │ │ │ │ ├── base.templ.tmpl │ │ │ │ ├── hello.templ.tmpl │ │ │ │ └── hello_fiber.go.tmpl │ │ │ ├── workflow │ │ │ │ └── github │ │ │ │ │ ├── github_action_gotest.yml.tmpl │ │ │ │ │ ├── github_action_goreleaser.yml.tmpl │ │ │ │ │ └── github_action_releaser_config.yml.tmpl │ │ │ └── docker │ │ │ │ ├── docker_compose.yml.tmpl │ │ │ │ └── dockerfile.tmpl │ │ ├── docker.go │ │ ├── gitHubAction.go │ │ └── routes.go │ ├── framework │ │ ├── files │ │ │ ├── globalenv.tmpl │ │ │ ├── server │ │ │ │ ├── fiber.go.tmpl │ │ │ │ └── standard_library.go.tmpl │ │ │ ├── gitignore.tmpl │ │ │ ├── tests │ │ │ │ ├── default-test.go.tmpl │ │ │ │ ├── gin-test.go.tmpl │ │ │ │ ├── echo-test.go.tmpl │ │ │ │ └── fiber-test.go.tmpl │ │ │ ├── README.md.tmpl │ │ │ ├── air.toml.tmpl │ │ │ ├── main │ │ │ │ ├── main.go.tmpl │ │ │ │ └── fiber_main.go.tmpl │ │ │ └── routes │ │ │ │ ├── fiber.go.tmpl │ │ │ │ ├── gin.go.tmpl │ │ │ │ ├── echo.go.tmpl │ │ │ │ ├── chi.go.tmpl │ │ │ │ ├── gorilla.go.tmpl │ │ │ │ ├── http_router.go.tmpl │ │ │ │ └── standard_library.go.tmpl │ │ ├── main.go │ │ ├── ginRoutes.go │ │ ├── chiRoutes.go │ │ ├── echoRoutes.go │ │ ├── gorillaRoutes.go │ │ ├── routerRoutes.go │ │ ├── httpRoutes.go │ │ └── fiberServer.go │ ├── dbdriver │ │ ├── files │ │ │ ├── env │ │ │ │ ├── sqlite.tmpl │ │ │ │ ├── redis.tmpl │ │ │ │ ├── mongo.tmpl │ │ │ │ ├── postgres.tmpl │ │ │ │ ├── mysql.tmpl │ │ │ │ └── scylla.tmpl │ │ │ ├── service │ │ │ │ ├── mongo.tmpl │ │ │ │ ├── sqlite.tmpl │ │ │ │ ├── postgres.tmpl │ │ │ │ └── mysql.tmpl │ │ │ └── tests │ │ │ │ ├── mongo.tmpl │ │ │ │ ├── redis.tmpl │ │ │ │ ├── mysql.tmpl │ │ │ │ ├── postgres.tmpl │ │ │ │ └── scylla.tmpl │ │ ├── sqlite.go │ │ ├── mongo.go │ │ ├── mysql.go │ │ ├── redis.go │ │ ├── scylla.go │ │ └── postgres.go │ ├── globalEnv.go │ └── docker │ │ ├── mongo.go │ │ ├── mysql.go │ │ ├── redis.go │ │ ├── scylla.go │ │ ├── postgres.go │ │ └── files │ │ └── docker-compose │ │ ├── redis.tmpl │ │ ├── mongo.tmpl │ │ ├── postgres.tmpl │ │ ├── mysql.tmpl │ │ └── scylla.tmpl ├── flags │ ├── git.go │ ├── advancedFeatures.go │ ├── database.go │ └── frameworks.go ├── root.go ├── ui │ ├── textinput │ │ ├── textinput_test.go │ │ └── textinput.go │ ├── spinner │ │ └── spinner.go │ ├── multiSelect │ │ └── multiSelect.go │ └── multiInput │ │ └── multiInput.go ├── utils │ └── utils_test.go ├── version.go └── steps │ └── steps.go ├── docs ├── requirements.txt ├── docs │ ├── public │ │ ├── htmx.png │ │ ├── logo.png │ │ ├── react.png │ │ ├── tailwind.png │ │ ├── blueprint_1.png │ │ ├── blueprint_ui.png │ │ └── blueprint_advanced.png │ ├── endpoints-test │ │ ├── web.md │ │ ├── websocket.md │ │ ├── mongo.md │ │ ├── server.md │ │ └── sql.md │ ├── blueprint-ui.md │ ├── advanced-flag │ │ ├── websocket.md │ │ ├── advanced-flag.md │ │ ├── goreleaser.md │ │ ├── htmx-templ.md │ │ ├── tailwind.md │ │ └── docker.md │ ├── blueprint-core │ │ ├── frameworks.md │ │ └── db-drivers.md │ ├── creating-project │ │ ├── makefile.md │ │ ├── air.md │ │ └── project-init.md │ └── installation.md ├── custom_theme │ └── main.html ├── Makefile └── mkdocs.yml ├── public ├── logo.png ├── ui.gif ├── stats.gif ├── advanced.gif ├── database.gif ├── example.gif ├── install.gif ├── license.gif ├── blueprint_1.png ├── frameworks.gif └── blueprint_advanced.png ├── main.go ├── scripts └── completions.sh ├── .pre-commit-config.yaml ├── .goreleaser.yml ├── LICENSE ├── contributors.yml ├── go.mod ├── CONTRIBUTING.md └── go.sum /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleOnly: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go-blueprint 2 | site 3 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/tailwind/output.css.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.5.3 2 | mkdocs-material==9.5.15 -------------------------------------------------------------------------------- /cmd/template/advanced/files/tailwind/input.css.tmpl: -------------------------------------------------------------------------------- 1 | @import "tailwindcss" 2 | -------------------------------------------------------------------------------- /cmd/template/framework/files/globalenv.tmpl: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | APP_ENV=local 3 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/react/tailwind/index.css.tmpl: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/ui.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/ui.gif -------------------------------------------------------------------------------- /public/stats.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/stats.gif -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [melkeydev] 4 | -------------------------------------------------------------------------------- /public/advanced.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/advanced.gif -------------------------------------------------------------------------------- /public/database.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/database.gif -------------------------------------------------------------------------------- /public/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/example.gif -------------------------------------------------------------------------------- /public/install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/install.gif -------------------------------------------------------------------------------- /public/license.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/license.gif -------------------------------------------------------------------------------- /cmd/template/advanced/files/websocket/imports/fiber.tmpl: -------------------------------------------------------------------------------- 1 | "github.com/gofiber/contrib/websocket" 2 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/websocket/imports/standard_library.tmpl: -------------------------------------------------------------------------------- 1 | "github.com/coder/websocket" 2 | -------------------------------------------------------------------------------- /public/blueprint_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/blueprint_1.png -------------------------------------------------------------------------------- /public/frameworks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/frameworks.gif -------------------------------------------------------------------------------- /docs/docs/public/htmx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/htmx.png -------------------------------------------------------------------------------- /docs/docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/logo.png -------------------------------------------------------------------------------- /docs/docs/public/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/react.png -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/imports/standard_library.tmpl: -------------------------------------------------------------------------------- 1 | "github.com/a-h/templ" 2 | "{{.ProjectName}}/cmd/web" -------------------------------------------------------------------------------- /docs/docs/public/tailwind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/tailwind.png -------------------------------------------------------------------------------- /public/blueprint_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/public/blueprint_advanced.png -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/imports/gin.tmpl: -------------------------------------------------------------------------------- 1 | "github.com/a-h/templ" 2 | "{{.ProjectName}}/cmd/web" 3 | "io/fs" 4 | -------------------------------------------------------------------------------- /docs/docs/public/blueprint_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/blueprint_1.png -------------------------------------------------------------------------------- /docs/docs/public/blueprint_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/blueprint_ui.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/melkeydev/go-blueprint/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/efs.go.tmpl: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import "embed" 4 | 5 | //go:embed "assets" 6 | var Files embed.FS 7 | -------------------------------------------------------------------------------- /docs/docs/public/blueprint_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Melkeydev/go-blueprint/HEAD/docs/docs/public/blueprint_advanced.png -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/sqlite.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .AdvancedOptions.docker }} 2 | BLUEPRINT_DB_URL=./db/test.db 3 | {{- else }} 4 | BLUEPRINT_DB_URL=./test.db 5 | {{- end }} 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Melkeydev Discord 4 | url: https://discord.gg/HHZMSCu 5 | about: Chat with the community. -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/tailwind/tailwind.config.js.tmpl: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./**/*.html", "./**/*.templ", "./**/*.go",], 3 | theme: { extend: {}, }, 4 | plugins: [], 5 | } 6 | -------------------------------------------------------------------------------- /scripts/completions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm -rf completions 4 | mkdir completions 5 | for sh in bash zsh fish; do 6 | go run main.go completion "$sh" >"completions/go-blueprint.$sh" 7 | done 8 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/imports/fiber.tmpl: -------------------------------------------------------------------------------- 1 | "github.com/a-h/templ" 2 | "{{.ProjectName}}/cmd/web" 3 | "github.com/gofiber/fiber/v2/middleware/adaptor" 4 | "github.com/gofiber/fiber/v2/middleware/filesystem" 5 | "net/http" -------------------------------------------------------------------------------- /docs/custom_theme/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block libs %} 4 | {{ super() }} 5 | 6 | {% endblock %} -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/chi.tmpl: -------------------------------------------------------------------------------- 1 | fileServer := http.FileServer(http.FS(web.Files)) 2 | r.Handle("/assets/*", fileServer) 3 | r.Get("/web", templ.Handler(web.HelloForm()).ServeHTTP) 4 | r.Post("/hello", web.HelloWebHandler) 5 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/redis.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .AdvancedOptions.docker }} 2 | BLUEPRINT_DB_ADDRESS=redis_bp 3 | {{- else }} 4 | BLUEPRINT_DB_ADDRESS=localhost 5 | {{- end }} 6 | BLUEPRINT_DB_PORT=6379 7 | BLUEPRINT_DB_PASSWORD= 8 | BLUEPRINT_DB_DATABASE=0 9 | -------------------------------------------------------------------------------- /cmd/template/globalEnv.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed framework/files/globalenv.tmpl 8 | var globalEnvTemplate []byte 9 | 10 | func GlobalEnvTemplate() []byte { 11 | return globalEnvTemplate 12 | } 13 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/standard_library.tmpl: -------------------------------------------------------------------------------- 1 | fileServer := http.FileServer(http.FS(web.Files)) 2 | mux.Handle("/assets/", fileServer) 3 | mux.Handle("/web", templ.Handler(web.HelloForm())) 4 | mux.HandleFunc("/hello", web.HelloWebHandler) 5 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/mongo.tmpl: -------------------------------------------------------------------------------- 1 | {{ if .AdvancedOptions.docker }} 2 | BLUEPRINT_DB_HOST=mongo_bp 3 | {{- else }} 4 | BLUEPRINT_DB_HOST=localhost 5 | {{- end }} 6 | BLUEPRINT_DB_PORT=27017 7 | BLUEPRINT_DB_USERNAME=melkey 8 | BLUEPRINT_DB_ROOT_PASSWORD=password1234 9 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/echo.tmpl: -------------------------------------------------------------------------------- 1 | fileServer := http.FileServer(http.FS(web.Files)) 2 | e.GET("/assets/*", echo.WrapHandler(fileServer)) 3 | 4 | e.GET("/web", echo.WrapHandler(templ.Handler(web.HelloForm()))) 5 | e.POST("/hello", echo.WrapHandler(http.HandlerFunc(web.HelloWebHandler))) 6 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/react/tailwind/vite.config.ts.tmpl: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(),tailwindcss()], 8 | }) -------------------------------------------------------------------------------- /cmd/template/docker/mongo.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type MongoDockerTemplate struct{} 8 | 9 | //go:embed files/docker-compose/mongo.tmpl 10 | var mongoDockerTemplate []byte 11 | 12 | func (m MongoDockerTemplate) Docker() []byte { 13 | return mongoDockerTemplate 14 | } -------------------------------------------------------------------------------- /cmd/template/docker/mysql.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type MysqlDockerTemplate struct{} 8 | 9 | //go:embed files/docker-compose/mysql.tmpl 10 | var mysqlDockerTemplate []byte 11 | 12 | func (m MysqlDockerTemplate) Docker() []byte { 13 | return mysqlDockerTemplate 14 | } -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/http_router.tmpl: -------------------------------------------------------------------------------- 1 | fileServer := http.FileServer(http.FS(web.Files)) 2 | r.Handler(http.MethodGet, "/assets/*filepath", fileServer) 3 | r.Handler(http.MethodGet, "/web", templ.Handler(web.HelloForm())) 4 | r.HandlerFunc(http.MethodPost, "/hello", web.HelloWebHandler) 5 | 6 | -------------------------------------------------------------------------------- /cmd/template/docker/redis.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type RedisDockerTemplate struct{} 8 | 9 | //go:embed files/docker-compose/redis.tmpl 10 | var redisDockerTemplate []byte 11 | 12 | func (r RedisDockerTemplate) Docker() []byte { 13 | return redisDockerTemplate 14 | } 15 | -------------------------------------------------------------------------------- /cmd/template/docker/scylla.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type ScyllaDockerTemplate struct{} 8 | 9 | //go:embed files/docker-compose/scylla.tmpl 10 | var scyllaDockerTemplate []byte 11 | 12 | func (r ScyllaDockerTemplate) Docker() []byte { 13 | return scyllaDockerTemplate 14 | } 15 | -------------------------------------------------------------------------------- /cmd/template/docker/postgres.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type PostgresDockerTemplate struct{} 8 | 9 | //go:embed files/docker-compose/postgres.tmpl 10 | var postgresDockerTemplate []byte 11 | 12 | func (m PostgresDockerTemplate) Docker() []byte { 13 | return postgresDockerTemplate 14 | } -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/gorilla.tmpl: -------------------------------------------------------------------------------- 1 | fileServer := http.FileServer(http.FS(web.Files)) 2 | r.PathPrefix("/assets/").Handler(fileServer) 3 | 4 | r.HandleFunc("/web", func(w http.ResponseWriter, r *http.Request) { 5 | templ.Handler(web.HelloForm()).ServeHTTP(w, r) 6 | }) 7 | 8 | r.HandleFunc("/hello", web.HelloWebHandler) 9 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/postgres.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .AdvancedOptions.docker }} 2 | BLUEPRINT_DB_HOST=psql_bp 3 | {{- else }} 4 | BLUEPRINT_DB_HOST=localhost 5 | {{- end }} 6 | BLUEPRINT_DB_PORT=5432 7 | BLUEPRINT_DB_DATABASE=blueprint 8 | BLUEPRINT_DB_USERNAME=melkey 9 | BLUEPRINT_DB_PASSWORD=password1234 10 | BLUEPRINT_DB_SCHEMA=public 11 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/mysql.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .AdvancedOptions.docker }} 2 | BLUEPRINT_DB_HOST=mysql_bp 3 | {{- else }} 4 | BLUEPRINT_DB_HOST=localhost 5 | {{- end }} 6 | BLUEPRINT_DB_PORT=3306 7 | BLUEPRINT_DB_DATABASE=blueprint 8 | BLUEPRINT_DB_USERNAME=melkey 9 | BLUEPRINT_DB_PASSWORD=password1234 10 | BLUEPRINT_DB_ROOT_PASSWORD=password4321 11 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/gin.tmpl: -------------------------------------------------------------------------------- 1 | staticFiles, _ := fs.Sub(web.Files, "assets") 2 | r.StaticFS("/assets", http.FS(staticFiles)) 3 | 4 | r.GET("/web", func(c *gin.Context) { 5 | templ.Handler(web.HelloForm()).ServeHTTP(c.Writer, c.Request) 6 | }) 7 | 8 | r.POST("/hello", func(c *gin.Context) { 9 | web.HelloWebHandler(c.Writer, c.Request) 10 | }) 11 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/routes/fiber.tmpl: -------------------------------------------------------------------------------- 1 | s.App.Use("/assets", filesystem.New(filesystem.Config{ 2 | Root: http.FS(web.Files), 3 | PathPrefix: "assets", 4 | Browse: false, 5 | })) 6 | 7 | s.App.Get("/web", adaptor.HTTPHandler(templ.Handler(web.HelloForm()))) 8 | 9 | s.App.Post("/hello", func(c *fiber.Ctx) error { 10 | return web.HelloWebHandler(c) 11 | }) 12 | -------------------------------------------------------------------------------- /cmd/template/advanced/docker.go: -------------------------------------------------------------------------------- 1 | package advanced 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed files/docker/dockerfile.tmpl 8 | var dockerfileTemplate []byte 9 | 10 | //go:embed files/docker/docker_compose.yml.tmpl 11 | var dockerComposeTemplate []byte 12 | 13 | func Dockerfile() []byte { 14 | return dockerfileTemplate 15 | } 16 | 17 | func DockerCompose() []byte { 18 | return dockerComposeTemplate 19 | } 20 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/env/scylla.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .AdvancedOptions.docker }} 2 | # BLUEPRINT_DB_HOSTS=scylla_bp:9042 # ScyllaDB default port 3 | BLUEPRINT_DB_HOSTS=scylla_bp:19042 # ScyllaDB Shard-Aware port 4 | {{- else }} 5 | # BLUEPRINT_DB_HOSTS=localhost:9042 # ScyllaDB default port 6 | BLUEPRINT_DB_HOSTS=localhost:19042 # ScyllaDB Shard-Aware port 7 | {{- end }} 8 | BLUEPRINT_DB_CONSISTENCY="LOCAL_QUORUM" 9 | # BLUEPRINT_DB_USERNAME= 10 | # BLUEPRINT_DB_PASSWORD= -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license. 2 | 3 | ## Problem/Feature 4 | 5 | Please include a description of the problem or feature this PR is addressing. 6 | 7 | ## Description of Changes: 8 | 9 | - Item 1 10 | - Item 2 11 | 12 | ## Checklist 13 | 14 | - [ ] I have self-reviewed the changes being requested 15 | - [ ] I have updated the documentation (check issue ticket #218) 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-added-large-files 10 | - repo: https://github.com/golangci/golangci-lint 11 | rev: v1.55.2 12 | hooks: 13 | - id: golangci-lint 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | 3 | default: install 4 | 5 | all: install build 6 | 7 | 8 | h help: 9 | @grep '^[a-z]' Makefile 10 | 11 | 12 | install: 13 | pip install pip --upgrade 14 | pip install -r requirements.txt 15 | 16 | upgrade: 17 | pip install pip --upgrade 18 | pip install -r requirements.txt --upgrade 19 | 20 | 21 | s serve: 22 | mkdocs serve --strict 23 | 24 | 25 | b build: 26 | mkdocs build --strict 27 | 28 | d deploy: 29 | mkdocs gh-deploy --strict --force 30 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/sqlite.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type SqliteTemplate struct{} 8 | 9 | //go:embed files/service/sqlite.tmpl 10 | var sqliteServiceTemplate []byte 11 | 12 | //go:embed files/env/sqlite.tmpl 13 | var sqliteEnvTemplate []byte 14 | 15 | func (m SqliteTemplate) Service() []byte { 16 | return sqliteServiceTemplate 17 | } 18 | 19 | func (m SqliteTemplate) Env() []byte { 20 | return sqliteEnvTemplate 21 | } 22 | 23 | func (m SqliteTemplate) Tests() []byte { 24 | return []byte{} 25 | } 26 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/hello.go.tmpl: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func HelloWebHandler(w http.ResponseWriter, r *http.Request) { 9 | err := r.ParseForm() 10 | if err != nil { 11 | http.Error(w, "Bad Request", http.StatusBadRequest) 12 | } 13 | 14 | name := r.FormValue("name") 15 | component := HelloPost(name) 16 | err = component.Render(r.Context(), w) 17 | if err != nil { 18 | http.Error(w, err.Error(), http.StatusBadRequest) 19 | log.Fatalf("Error rendering in HelloWebHandler: %e", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/docs/endpoints-test/web.md: -------------------------------------------------------------------------------- 1 | 2 | To test the /web endpoint when HTMX and Temp are used, you can simply open it in a web browser. This endpoint serves an HTML page with a form. 3 | 4 | Navigate to `http://localhost:PORT/web` 5 | 6 | This page contains a form with a single input field and a submit button. Upon submitting the form, "Hello, [input]" will be displayed. 7 | 8 | ## Sample output 9 | 10 | ![htmx](../public/htmx.png) 11 | 12 | ## Terminal log 13 | 14 | ```bash 15 | make run 16 | 2024/05/28 20:42:06 "POST http://localhost:8070/hello HTTP/1.1" from 127.0.0.1:45494 - 200 24B in 53.23µs 17 | ``` 18 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/mongo.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type MongoTemplate struct{} 8 | 9 | //go:embed files/service/mongo.tmpl 10 | var mongoServiceTemplate []byte 11 | 12 | //go:embed files/env/mongo.tmpl 13 | var mongoEnvTemplate []byte 14 | 15 | //go:embed files/tests/mongo.tmpl 16 | var mongoTestcontainersTemplate []byte 17 | 18 | func (m MongoTemplate) Service() []byte { 19 | return mongoServiceTemplate 20 | } 21 | 22 | func (m MongoTemplate) Env() []byte { 23 | return mongoEnvTemplate 24 | } 25 | 26 | func (m MongoTemplate) Tests() []byte { 27 | return mongoTestcontainersTemplate 28 | } 29 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/mysql.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type MysqlTemplate struct{} 8 | 9 | //go:embed files/service/mysql.tmpl 10 | var mysqlServiceTemplate []byte 11 | 12 | //go:embed files/env/mysql.tmpl 13 | var mysqlEnvTemplate []byte 14 | 15 | //go:embed files/tests/mysql.tmpl 16 | var mysqlTestcontainersTemplate []byte 17 | 18 | func (m MysqlTemplate) Service() []byte { 19 | return mysqlServiceTemplate 20 | } 21 | 22 | func (m MysqlTemplate) Env() []byte { 23 | return mysqlEnvTemplate 24 | } 25 | 26 | func (m MysqlTemplate) Tests() []byte { 27 | return mysqlTestcontainersTemplate 28 | } 29 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/redis.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type RedisTemplate struct{} 8 | 9 | //go:embed files/service/redis.tmpl 10 | var redisServiceTemplate []byte 11 | 12 | //go:embed files/env/redis.tmpl 13 | var redisEnvTemplate []byte 14 | 15 | //go:embed files/tests/redis.tmpl 16 | var redisTestcontainersTemplate []byte 17 | 18 | func (r RedisTemplate) Service() []byte { 19 | return redisServiceTemplate 20 | } 21 | 22 | func (r RedisTemplate) Env() []byte { 23 | return redisEnvTemplate 24 | } 25 | 26 | func (r RedisTemplate) Tests() []byte { 27 | return redisTestcontainersTemplate 28 | } 29 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/scylla.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type ScyllaTemplate struct{} 8 | 9 | //go:embed files/service/scylla.tmpl 10 | var scyllaServiceTemplate []byte 11 | 12 | //go:embed files/env/scylla.tmpl 13 | var scyllaEnvTemplate []byte 14 | 15 | //go:embed files/tests/scylla.tmpl 16 | var scyllaTestcontainersTemplate []byte 17 | 18 | func (r ScyllaTemplate) Service() []byte { 19 | return scyllaServiceTemplate 20 | } 21 | 22 | func (r ScyllaTemplate) Env() []byte { 23 | return scyllaEnvTemplate 24 | } 25 | 26 | func (r ScyllaTemplate) Tests() []byte { 27 | return scyllaTestcontainersTemplate 28 | } 29 | -------------------------------------------------------------------------------- /docs/docs/blueprint-ui.md: -------------------------------------------------------------------------------- 1 | The Blueprint UI is a crucial component of the Go Blueprint ecosystem, providing a user-friendly interface for creating CLI commands and visualizing project structures. 2 | 3 | By visiting the Blueprint UI website at [go-blueprint.dev](https://go-blueprint.dev), users can interact with a visual representation of their project setup before executing commands. 4 | 5 | ![BlueprintUI](public/blueprint_ui.png) 6 | 7 | This enhances the overall experience of using Go Blueprint by providing a visual representation of project setups and simplifying the command generation process. Check Blueprint UI [code](https://github.com/briancbarrow/go-blueprint-htmx). 8 | -------------------------------------------------------------------------------- /cmd/template/framework/files/server/fiber.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | {{if ne .DBDriver "none"}} 6 | "{{.ProjectName}}/internal/database" 7 | {{end}} 8 | ) 9 | 10 | type FiberServer struct { 11 | *fiber.App 12 | {{if ne .DBDriver "none"}} 13 | db database.Service 14 | {{end}} 15 | } 16 | 17 | func New() *FiberServer { 18 | server := &FiberServer{ 19 | App: fiber.New(fiber.Config{ 20 | ServerHeader: "{{.ProjectName}}", 21 | AppName: "{{.ProjectName}}", 22 | }), 23 | {{if ne .DBDriver "none"}} 24 | db: database.New(), 25 | {{end}} 26 | } 27 | 28 | return server 29 | } 30 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/postgres.go: -------------------------------------------------------------------------------- 1 | package dbdriver 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | type PostgresTemplate struct{} 8 | 9 | //go:embed files/service/postgres.tmpl 10 | var postgresServiceTemplate []byte 11 | 12 | //go:embed files/env/postgres.tmpl 13 | var postgresEnvTemplate []byte 14 | 15 | //go:embed files/tests/postgres.tmpl 16 | var postgresTestcontainersTemplate []byte 17 | 18 | func (m PostgresTemplate) Service() []byte { 19 | return postgresServiceTemplate 20 | } 21 | 22 | func (m PostgresTemplate) Env() []byte { 23 | return postgresEnvTemplate 24 | } 25 | 26 | func (m PostgresTemplate) Tests() []byte { 27 | return postgresTestcontainersTemplate 28 | } 29 | -------------------------------------------------------------------------------- /cmd/template/advanced/gitHubAction.go: -------------------------------------------------------------------------------- 1 | package advanced 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed files/workflow/github/github_action_goreleaser.yml.tmpl 8 | var gitHubActionBuildTemplate []byte 9 | 10 | //go:embed files/workflow/github/github_action_gotest.yml.tmpl 11 | var gitHubActionTestTemplate []byte 12 | 13 | //go:embed files/workflow/github/github_action_releaser_config.yml.tmpl 14 | var gitHubActionConfigTemplate []byte 15 | 16 | func Releaser() []byte { 17 | return gitHubActionBuildTemplate 18 | } 19 | 20 | func Test() []byte { 21 | return gitHubActionTestTemplate 22 | } 23 | 24 | func ReleaserConfig() []byte { 25 | return gitHubActionConfigTemplate 26 | } 27 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/base.templ.tmpl: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | templ Base() { 4 | 5 | 6 | 7 | 8 | 9 | Go Blueprint Hello 10 | 11 | 12 | 13 | 14 |
15 | { children... } 16 |
17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /cmd/flags/git.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Git string 9 | 10 | const ( 11 | Commit = "commit" 12 | Stage = "stage" 13 | Skip = "skip" 14 | ) 15 | 16 | var AllowedGitsOptions = []string{string(Commit), string(Stage), string(Skip)} 17 | 18 | func (f Git) String() string { 19 | return string(f) 20 | } 21 | 22 | func (f *Git) Type() string { 23 | return "Git" 24 | } 25 | 26 | func (f *Git) Set(value string) error { 27 | for _, gitOption := range AllowedGitsOptions { 28 | if gitOption == value { 29 | *f = Git(value) 30 | return nil 31 | } 32 | } 33 | 34 | return fmt.Errorf("Git to use. Allowed values: %s", strings.Join(AllowedGitsOptions, ", ")) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/hello.templ.tmpl: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | templ HelloForm() { 4 | @Base() { 5 |
6 | 7 | 8 |
9 |
10 | } 11 | } 12 | 13 | templ HelloPost(name string) { 14 |
15 |

Hello, { name }

16 |
17 | } 18 | -------------------------------------------------------------------------------- /cmd/template/framework/files/gitignore.tmpl: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with "go test -c" 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Go workspace file 18 | go.work 19 | tmp/ 20 | 21 | # IDE specific files 22 | .vscode 23 | .idea 24 | 25 | # .env file 26 | .env 27 | 28 | # Project build 29 | main 30 | *templ.go 31 | 32 | # OS X generated file 33 | .DS_Store 34 | {{if ( .AdvancedOptions.tailwind )}} 35 | 36 | # Tailwind CSS 37 | cmd/web/assets/css/output.css 38 | tailwindcss 39 | {{end}} 40 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 Melkey melkeydev@gmail.com 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "go-blueprint", 14 | Short: "A program to spin up a quick Go project using a popular framework", 15 | Long: `Go Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly. 16 | It also gives the option to integrate with one of the more popular Go frameworks!`, 17 | } 18 | 19 | func Execute() { 20 | err := rootCmd.Execute() 21 | if err != nil { 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | func init() { 27 | rootCmd.AddCommand(versionCmd) 28 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 29 | } 30 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/workflow/github/github_action_gotest.yml.tmpl: -------------------------------------------------------------------------------- 1 | name: Go-test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Setup Go 11 | uses: actions/setup-go@v4 12 | with: 13 | go-version: '1.22.x' 14 | {{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}} 15 | - name: Install templ 16 | shell: bash 17 | run: go install github.com/a-h/templ/cmd/templ@latest 18 | - name: Run templ generate 19 | shell: bash 20 | run: templ generate -path . 21 | {{end}} 22 | - name: Build 23 | run: go build -v ./... 24 | - name: Test with the Go CLI 25 | run: go test ./... 26 | -------------------------------------------------------------------------------- /cmd/ui/textinput/textinput_test.go: -------------------------------------------------------------------------------- 1 | package textinput 2 | 3 | import "testing" 4 | 5 | func TestInputSanitization(t *testing.T) { 6 | passTestCases := []string{ 7 | "chi", 8 | "new_project", 9 | "NEW_PROJECT", 10 | "new-project", 11 | "new/project", 12 | "new.project", 13 | } 14 | for _, testCase := range passTestCases { 15 | if err := sanitizeInput(testCase); err != nil { 16 | t.Errorf("expected no error, got: %v", err) 17 | } 18 | } 19 | failTestCases := []string{ 20 | "new project", 21 | "NEW\\PROJECT", 22 | "new%project", 23 | " ", 24 | " ", 25 | "#", 26 | "@", 27 | } 28 | for _, testCase := range failTestCases { 29 | if err := sanitizeInput(testCase); err == nil { 30 | t.Errorf("expected error for input %v, got nil", testCase) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/docker/docker_compose.yml.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | target: prod 7 | restart: unless-stopped 8 | ports: 9 | - ${PORT}:${PORT} 10 | environment: 11 | APP_ENV: ${APP_ENV} 12 | PORT: ${PORT} 13 | {{- if and (.AdvancedOptions.docker) (eq .DBDriver "sqlite") }} 14 | BLUEPRINT_DB_URL: ${BLUEPRINT_DB_URL} 15 | volumes: 16 | - sqlite_bp:/app/db 17 | {{- end }} 18 | {{- if .AdvancedOptions.react }} 19 | frontend: 20 | build: 21 | context: . 22 | dockerfile: Dockerfile 23 | target: frontend 24 | restart: unless-stopped 25 | ports: 26 | - 5173:5173 27 | depends_on: 28 | - app 29 | {{- end }} 30 | 31 | {{- if and (.AdvancedOptions.docker) (eq .DBDriver "sqlite") }} 32 | volumes: 33 | sqlite_bp: 34 | {{- end }} 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new idea for go-blueprint. 3 | title: "[Feature Request] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please provide as much information as possible by filling out form below. 10 | - type: markdown 11 | attributes: 12 | value: --- 13 | - type: textarea 14 | id: idea 15 | attributes: 16 | label: Tell us about your feature request 17 | description: | 18 | Describe what you would like go-blueprint to be able to do. 19 | validations: 20 | required: true 21 | - type: checkboxes 22 | id: disclaimer 23 | attributes: 24 | label: Disclaimer 25 | description: I have verified that this has not been suggested before. 26 | options: 27 | - label: I agree 28 | required: true 29 | -------------------------------------------------------------------------------- /cmd/template/framework/files/server/standard_library.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | _ "github.com/joho/godotenv/autoload" 11 | {{if ne .DBDriver "none"}} 12 | "{{.ProjectName}}/internal/database" 13 | {{end}} 14 | ) 15 | 16 | type Server struct { 17 | port int 18 | {{if ne .DBDriver "none"}} 19 | db database.Service 20 | {{end}} 21 | } 22 | 23 | func NewServer() *http.Server { 24 | port, _ := strconv.Atoi(os.Getenv("PORT")) 25 | NewServer := &Server{ 26 | port: port, 27 | {{if ne .DBDriver "none"}} 28 | db: database.New(), 29 | {{end}} 30 | } 31 | 32 | // Declare Server config 33 | server := &http.Server{ 34 | Addr: fmt.Sprintf(":%d", NewServer.port), 35 | Handler: NewServer.RegisterRoutes(), 36 | IdleTimeout: time.Minute, 37 | ReadTimeout: 10 * time.Second, 38 | WriteTimeout: 30 * time.Second, 39 | } 40 | 41 | return server 42 | } 43 | -------------------------------------------------------------------------------- /cmd/template/framework/files/tests/default-test.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | import ( 3 | "io" 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | func TestHandler(t *testing.T) { 9 | s := &Server{} 10 | server := httptest.NewServer(http.HandlerFunc(s.HelloWorldHandler)) 11 | defer server.Close() 12 | resp, err := http.Get(server.URL) 13 | if err != nil { 14 | t.Fatalf("error making request to server. Err: %v", err) 15 | } 16 | defer resp.Body.Close() 17 | // Assertions 18 | if resp.StatusCode != http.StatusOK { 19 | t.Errorf("expected status OK; got %v", resp.Status) 20 | } 21 | expected := "{\"message\":\"Hello World\"}" 22 | body, err := io.ReadAll(resp.Body) 23 | if err != nil { 24 | t.Fatalf("error reading response body. Err: %v", err) 25 | } 26 | if expected != string(body) { 27 | t.Errorf("expected response body to be %v; got %v", expected, string(body)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: "1.21.1" 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v5.0.0 22 | with: 23 | distribution: goreleaser 24 | version: ${{ env.GITHUB_REF_NAME }} 25 | args: release --clean 26 | workdir: ./ 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | npm-publish: 31 | needs: goreleaser 32 | permissions: 33 | contents: read 34 | id-token: write 35 | uses: ./.github/workflows/npm-publish.yml 36 | with: 37 | tag: ${{ github.ref_name }} 38 | secrets: inherit 39 | 40 | -------------------------------------------------------------------------------- /cmd/template/framework/files/tests/gin-test.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | import ( 3 | "net/http" 4 | "net/http/httptest" 5 | "testing" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | func TestHelloWorldHandler(t *testing.T) { 9 | s := &Server{} 10 | r := gin.New() 11 | r.GET("/", s.HelloWorldHandler) 12 | // Create a test HTTP request 13 | req, err := http.NewRequest("GET", "/", nil) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | // Create a ResponseRecorder to record the response 18 | rr := httptest.NewRecorder() 19 | // Serve the HTTP request 20 | r.ServeHTTP(rr, req) 21 | // Check the status code 22 | if status := rr.Code; status != http.StatusOK { 23 | t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK) 24 | } 25 | // Check the response body 26 | expected := "{\"message\":\"Hello World\"}" 27 | if rr.Body.String() != expected { 28 | t.Errorf("Handler returned unexpected body: got %v want %v", rr.Body.String(), expected) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | - ./scripts/completions.sh 5 | builds: 6 | - binary: go-blueprint 7 | main: ./ 8 | goos: 9 | - darwin 10 | - linux 11 | - windows 12 | goarch: 13 | - amd64 14 | - arm64 15 | env: 16 | - CGO_ENABLED=0 17 | ldflags: 18 | - -s -w -X github.com/melkeydev/go-blueprint/cmd.GoBlueprintVersion={{.Version}} 19 | 20 | release: 21 | prerelease: auto 22 | 23 | universal_binaries: 24 | - replace: true 25 | archives: 26 | - name_template: >- 27 | {{- .ProjectName }}_ {{- .Version }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end -}} 28 | format_overrides: 29 | - goos: windows 30 | format: zip 31 | builds_info: 32 | group: root 33 | owner: root 34 | files: 35 | - README.md 36 | - LICENSE 37 | - completions/* 38 | 39 | checksum: 40 | name_template: "checksums.txt" 41 | -------------------------------------------------------------------------------- /cmd/template/framework/main.go: -------------------------------------------------------------------------------- 1 | // Package template provides utility functions that 2 | // help with the templating of created files. 3 | package framework 4 | 5 | import ( 6 | _ "embed" 7 | ) 8 | 9 | //go:embed files/main/main.go.tmpl 10 | var mainTemplate []byte 11 | 12 | //go:embed files/air.toml.tmpl 13 | var airTomlTemplate []byte 14 | 15 | //go:embed files/README.md.tmpl 16 | var readmeTemplate []byte 17 | 18 | //go:embed files/makefile.tmpl 19 | var makeTemplate []byte 20 | 21 | //go:embed files/gitignore.tmpl 22 | var gitIgnoreTemplate []byte 23 | 24 | // MakeTemplate returns a byte slice that represents 25 | // the default Makefile template. 26 | func MakeTemplate() []byte { 27 | return makeTemplate 28 | } 29 | 30 | func GitIgnoreTemplate() []byte { 31 | return gitIgnoreTemplate 32 | } 33 | 34 | func AirTomlTemplate() []byte { 35 | return airTomlTemplate 36 | } 37 | 38 | // ReadmeTemplate returns a byte slice that represents 39 | // the default README.md file template. 40 | func ReadmeTemplate() []byte { 41 | return readmeTemplate 42 | } 43 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/workflow/github/github_action_goreleaser.yml.tmpl: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.22.x' 20 | {{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}} 21 | - name: Install templ 22 | shell: bash 23 | run: go install github.com/a-h/templ/cmd/templ@latest 24 | - name: Run templ generate 25 | shell: bash 26 | run: templ generate -path . 27 | {{end}} 28 | - name: Run GoReleaser 29 | uses: goreleaser/goreleaser-action@v6 30 | with: 31 | distribution: goreleaser 32 | version: ${{"{{"}} env.GITHUB_REF_NAME {{"}}"}} 33 | args: release --clean 34 | workdir: ./ 35 | env: 36 | GITHUB_TOKEN: ${{"{{"}} secrets.GITHUB_TOKEN {{"}}"}} 37 | -------------------------------------------------------------------------------- /.github/workflows/testcontainers.yml: -------------------------------------------------------------------------------- 1 | name: Integrations Test for the Generated Blueprints 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | 7 | jobs: 8 | itests_matrix: 9 | strategy: 10 | matrix: 11 | driver: 12 | [mysql, postgres, mongo, redis, scylla] 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Setup Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: '1.23.x' 20 | 21 | - name: Commit report 22 | run: | 23 | git config --global user.name 'testname' 24 | git config --global user.email 'testemail@users.noreply.github.com' 25 | 26 | - name: build ${{ matrix.driver }} template 27 | run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -g commit -f fiber -d ${{matrix.driver}}" 28 | 29 | - name: run ${{ matrix.driver }} integration tests 30 | working-directory: ${{ matrix.driver }} 31 | run: make itest 32 | 33 | - name: remove ${{ matrix.driver }} template 34 | run: rm -rf ${{ matrix.driver }} 35 | -------------------------------------------------------------------------------- /cmd/template/framework/files/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # Project {{.ProjectName}} 2 | 3 | One Paragraph of project description goes here 4 | 5 | ## Getting Started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 8 | 9 | ## MakeFile 10 | 11 | Run build make command with tests 12 | ```bash 13 | make all 14 | ``` 15 | 16 | Build the application 17 | ```bash 18 | make build 19 | ``` 20 | 21 | Run the application 22 | ```bash 23 | make run 24 | ``` 25 | 26 | {{- if or .AdvancedOptions.docker (and (ne .DBDriver "none") (ne .DBDriver "sqlite")) }} 27 | Create DB container 28 | ```bash 29 | make docker-run 30 | ``` 31 | 32 | Shutdown DB Container 33 | ```bash 34 | make docker-down 35 | ``` 36 | 37 | DB Integrations Test: 38 | ```bash 39 | make itest 40 | ``` 41 | {{- end }} 42 | 43 | Live reload the application: 44 | ```bash 45 | make watch 46 | ``` 47 | 48 | Run the test suite: 49 | ```bash 50 | make test 51 | ``` 52 | 53 | Clean up binary from the last build: 54 | ```bash 55 | make clean 56 | ``` 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Melkeydev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contributors.yml: -------------------------------------------------------------------------------- 1 | - Melkeydev 2 | - Ujstor 3 | - tylermeekel 4 | - MitchellBerend 5 | - H0llyW00dzZ 6 | - SudoSurya 7 | - mimatache 8 | - SputNikPlop 9 | - limesten 10 | - itschip 11 | - CamPen21 12 | - xsadia 13 | - arnevm123 14 | - muandane 15 | - joshjms 16 | - brijesh-amin 17 | - briancbarrow 18 | - arafays 19 | - LrsK 20 | - juleszs 21 | - vadhe 22 | - Patel-Raj 23 | - PrajvalBadiger 24 | - pradytpk 25 | - pellizzetti 26 | - Owbird 27 | - Jamlie 28 | - alexandear 29 | - NimishKashyap 30 | - narasaka 31 | - mubashiroliyantakath 32 | - abhishekmj303 33 | - Sakelig 34 | - reavessm 35 | - young-steveo 36 | - sibteali786 37 | - tomasohCHOM 38 | - vinitparekh17 39 | - vsnaichuk 40 | - Waldeedle 41 | - jpx40 42 | - nhlmg93 43 | - rustafariandev 44 | - s0up4200 45 | - alexanderilyin 46 | - Yoquec 47 | - jexroid 48 | - andrerocco 49 | - ashupednekar 50 | - basokant 51 | - schoolboybru 52 | - brendonotto 53 | - danielhe4rt 54 | - spankie 55 | - Echo5678 56 | - EinarLogi 57 | - silaselisha 58 | - eric-jacobson 59 | - KennyMwendwaX 60 | - KibuuleNoah 61 | - LarsArtmann 62 | - mdelapenya 63 | - Marcellofabrizio 64 | - kobamkode 65 | - MatthewAraujo 66 | - mikelerch 67 | - MohammadAlhallaq 68 | -------------------------------------------------------------------------------- /cmd/template/framework/ginRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/gin.go.tmpl 10 | var ginRoutesTemplate []byte 11 | 12 | //go:embed files/tests/gin-test.go.tmpl 13 | var ginTestHandlerTemplate []byte 14 | 15 | // GinTemplates contains the methods used for building 16 | // an app that uses [github.com/gin-gonic/gin] 17 | type GinTemplates struct{} 18 | 19 | func (g GinTemplates) Main() []byte { 20 | return mainTemplate 21 | } 22 | 23 | func (g GinTemplates) Server() []byte { 24 | return standardServerTemplate 25 | } 26 | 27 | func (g GinTemplates) Routes() []byte { 28 | return ginRoutesTemplate 29 | } 30 | 31 | func (g GinTemplates) TestHandler() []byte { 32 | return ginTestHandlerTemplate 33 | } 34 | 35 | func (g GinTemplates) HtmxTemplImports() []byte { 36 | return advanced.GinHtmxTemplImportsTemplate() 37 | } 38 | 39 | func (g GinTemplates) HtmxTemplRoutes() []byte { 40 | return advanced.GinHtmxTemplRoutesTemplate() 41 | } 42 | 43 | func (g GinTemplates) WebsocketImports() []byte { 44 | return advanced.StdLibWebsocketTemplImportsTemplate() 45 | } 46 | -------------------------------------------------------------------------------- /cmd/template/framework/chiRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/chi.go.tmpl 10 | var chiRoutesTemplate []byte 11 | 12 | //go:embed files/tests/default-test.go.tmpl 13 | var chiTestHandlerTemplate []byte 14 | 15 | // ChiTemplates contains the methods used for building 16 | // an app that uses [github.com/go-chi/chi] 17 | type ChiTemplates struct{} 18 | 19 | func (c ChiTemplates) Main() []byte { 20 | return mainTemplate 21 | } 22 | 23 | func (c ChiTemplates) Server() []byte { 24 | return standardServerTemplate 25 | } 26 | 27 | func (c ChiTemplates) Routes() []byte { 28 | return chiRoutesTemplate 29 | } 30 | 31 | func (c ChiTemplates) TestHandler() []byte { 32 | return chiTestHandlerTemplate 33 | } 34 | 35 | func (c ChiTemplates) HtmxTemplImports() []byte { 36 | return advanced.StdLibHtmxTemplImportsTemplate() 37 | } 38 | 39 | func (c ChiTemplates) HtmxTemplRoutes() []byte { 40 | return advanced.ChiHtmxTemplRoutesTemplate() 41 | } 42 | 43 | func (c ChiTemplates) WebsocketImports() []byte { 44 | return advanced.StdLibWebsocketTemplImportsTemplate() 45 | } 46 | -------------------------------------------------------------------------------- /cmd/template/framework/echoRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/echo.go.tmpl 10 | var echoRoutesTemplate []byte 11 | 12 | //go:embed files/tests/echo-test.go.tmpl 13 | var echoTestHandlerTemplate []byte 14 | 15 | // EchoTemplates contains the methods used for building 16 | // an app that uses [github.com/labstack/echo] 17 | type EchoTemplates struct{} 18 | 19 | func (e EchoTemplates) Main() []byte { 20 | return mainTemplate 21 | } 22 | func (e EchoTemplates) Server() []byte { 23 | return standardServerTemplate 24 | } 25 | 26 | func (e EchoTemplates) Routes() []byte { 27 | return echoRoutesTemplate 28 | } 29 | 30 | func (e EchoTemplates) TestHandler() []byte { 31 | return echoTestHandlerTemplate 32 | } 33 | 34 | func (e EchoTemplates) HtmxTemplImports() []byte { 35 | return advanced.StdLibHtmxTemplImportsTemplate() 36 | } 37 | 38 | func (e EchoTemplates) HtmxTemplRoutes() []byte { 39 | return advanced.EchoHtmxTemplRoutesTemplate() 40 | } 41 | 42 | func (e EchoTemplates) WebsocketImports() []byte { 43 | return advanced.StdLibWebsocketTemplImportsTemplate() 44 | } 45 | -------------------------------------------------------------------------------- /cmd/flags/advancedFeatures.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type AdvancedFeatures []string 9 | 10 | const ( 11 | Htmx string = "htmx" 12 | GoProjectWorkflow string = "githubaction" 13 | Websocket string = "websocket" 14 | Tailwind string = "tailwind" 15 | React string = "react" 16 | Docker string = "docker" 17 | ) 18 | 19 | var AllowedAdvancedFeatures = []string{string(React), string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Docker)} 20 | 21 | func (f AdvancedFeatures) String() string { 22 | return strings.Join(f, ",") 23 | } 24 | 25 | func (f *AdvancedFeatures) Type() string { 26 | return "AdvancedFeatures" 27 | } 28 | 29 | func (f *AdvancedFeatures) Set(value string) error { 30 | // Contains isn't available in 1.20 yet 31 | // if AdvancedFeatures.Contains(value) { 32 | for _, advancedFeature := range AllowedAdvancedFeatures { 33 | if advancedFeature == value { 34 | *f = append(*f, advancedFeature) 35 | return nil 36 | } 37 | } 38 | 39 | return fmt.Errorf("advanced Feature to use. Allowed values: %s", strings.Join(AllowedAdvancedFeatures, ", ")) 40 | } 41 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/service/mongo.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "go.mongodb.org/mongo-driver/mongo" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | _ "github.com/joho/godotenv/autoload" 13 | ) 14 | 15 | type Service interface { 16 | Health() map[string]string 17 | } 18 | 19 | type service struct { 20 | db *mongo.Client 21 | } 22 | 23 | var ( 24 | host = os.Getenv("BLUEPRINT_DB_HOST") 25 | port = os.Getenv("BLUEPRINT_DB_PORT") 26 | //database = os.Getenv("BLUEPRINT_DB_DATABASE") 27 | ) 28 | 29 | func New() Service { 30 | client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s", host, port))) 31 | 32 | if err != nil { 33 | log.Fatal(err) 34 | 35 | } 36 | return &service{ 37 | db: client, 38 | } 39 | } 40 | 41 | func (s *service) Health() map[string]string { 42 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 43 | defer cancel() 44 | 45 | err := s.db.Ping(ctx, nil) 46 | if err != nil { 47 | log.Fatalf("db down: %v", err) 48 | } 49 | 50 | return map[string]string{ 51 | "message": "It's healthy", 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/websocket.md: -------------------------------------------------------------------------------- 1 | A `/websocket` endpoint is added in `routes.go` to facilitate websocket connections. Upon accessing this endpoint, the server establishes a websocket connection and begins transmitting timestamp messages at 2-second intervals. WS is utilized across all Go-blueprint supported frameworks. This simple implementation showcases how flexible a project is. 2 | 3 | ### Code Implementation 4 | 5 | ```go 6 | func (s *Server) websocketHandler(c *gin.Context) { 7 | w := c.Writer 8 | r := c.Request 9 | socket, err := websocket.Accept(w, r, nil) 10 | 11 | if err != nil { 12 | log.Printf("could not open websocket: %v", err) 13 | _, _ = w.Write([]byte("could not open websocket")) 14 | w.WriteHeader(http.StatusInternalServerError) 15 | return 16 | } 17 | 18 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 19 | 20 | ctx := r.Context() 21 | socketCtx := socket.CloseRead(ctx) 22 | 23 | for { 24 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 25 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 26 | if err != nil { 27 | break 28 | } 29 | time.Sleep(time.Second * 2) 30 | } 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/workflow/github/github_action_releaser_config.yml.tmpl: -------------------------------------------------------------------------------- 1 | version: 2 2 | before: 3 | hooks: 4 | - go mod tidy 5 | 6 | env: 7 | - PACKAGE_PATH=github.com///cmd 8 | 9 | builds: 10 | - binary: "{{"{{"}} .ProjectName {{"}}"}}" 11 | main: ./cmd/api 12 | goos: 13 | - darwin 14 | - linux 15 | - windows 16 | goarch: 17 | - amd64 18 | - arm64 19 | env: 20 | - CGO_ENABLED=0 21 | ldflags: 22 | - -s -w -X {{"{{"}}.Env.PACKAGE_PATH{{"}}"}}={{"{{"}}.Version{{"}}"}} 23 | release: 24 | prerelease: auto 25 | 26 | universal_binaries: 27 | - replace: true 28 | 29 | archives: 30 | - name_template: > 31 | {{"{{"}}- .ProjectName {{"}}"}}_{{"{{"}}- .Version {{"}}"}}_{{"{{"}}- title .Os {{"}}"}}_{{"{{"}}- if eq .Arch "amd64" {{"}}"}}x86_64{{"{{"}}- else if eq .Arch "386" {{"}}"}}i386{{"{{"}}- else {{"}}"}}{{"{{"}} .Arch {{"}}"}}{{"{{"}} end {{"}}"}}{{"{{"}}- if .Arm {{"}}"}}v{{"{{"}} .Arm {{"}}"}}{{"{{"}} end -{{"}}"}} 32 | format_overrides: 33 | - goos: windows 34 | format: zip 35 | builds_info: 36 | group: root 37 | owner: root 38 | files: 39 | - README.md 40 | 41 | checksum: 42 | name_template: 'checksums.txt' 43 | -------------------------------------------------------------------------------- /cmd/template/framework/files/tests/echo-test.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | import ( 3 | "encoding/json" 4 | "net/http" 5 | "net/http/httptest" 6 | "reflect" 7 | "testing" 8 | "github.com/labstack/echo/v4" 9 | ) 10 | func TestHandler(t *testing.T) { 11 | e := echo.New() 12 | req := httptest.NewRequest(http.MethodGet, "/", nil) 13 | resp := httptest.NewRecorder() 14 | c := e.NewContext(req, resp) 15 | s := &Server{} 16 | // Assertions 17 | if err := s.HelloWorldHandler(c); err != nil { 18 | t.Errorf("handler() error = %v", err) 19 | return 20 | } 21 | if resp.Code != http.StatusOK { 22 | t.Errorf("handler() wrong status code = %v", resp.Code) 23 | return 24 | } 25 | expected := map[string]string{"message": "Hello World"} 26 | var actual map[string]string 27 | // Decode the response body into the actual map 28 | if err := json.NewDecoder(resp.Body).Decode(&actual); err != nil { 29 | t.Errorf("handler() error decoding response body: %v", err) 30 | return 31 | } 32 | // Compare the decoded response with the expected value 33 | if !reflect.DeepEqual(expected, actual) { 34 | t.Errorf("handler() wrong response body. expected = %v, actual = %v", expected, actual) 35 | return 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/template/framework/files/air.toml.tmpl: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = [] 7 | bin = {{if .OSCheck.UnixBased }}"./main"{{ else }}".\\main.exe"{{ end }} 8 | cmd = "make build" 9 | delay = 1000 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"{{if .AdvancedOptions.htmx}}, ".*_templ.go"{{end}}] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"{{if .AdvancedOptions.htmx}}, "templ"{{end}}] 18 | include_file = [] 19 | kill_delay = "0s" 20 | log = "build-errors.log" 21 | poll = false 22 | poll_interval = 0 23 | post_cmd = [] 24 | pre_cmd = [] 25 | rerun = false 26 | rerun_delay = 500 27 | send_interrupt = false 28 | stop_on_error = false 29 | 30 | [color] 31 | app = "" 32 | build = "yellow" 33 | main = "magenta" 34 | runner = "green" 35 | watcher = "cyan" 36 | 37 | [log] 38 | main_only = false 39 | time = false 40 | 41 | [misc] 42 | clean_on_exit = false 43 | 44 | [screen] 45 | clear_on_rebuild = false 46 | keep_scroll = true 47 | -------------------------------------------------------------------------------- /cmd/template/framework/files/tests/fiber-test.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | import ( 3 | "io" 4 | "net/http" 5 | "testing" 6 | "github.com/gofiber/fiber/v2" 7 | ) 8 | func TestHandler(t *testing.T) { 9 | // Create a Fiber app for testing 10 | app := fiber.New() 11 | // Inject the Fiber app into the server 12 | s := &FiberServer{App: app} 13 | // Define a route in the Fiber app 14 | app.Get("/", s.HelloWorldHandler) 15 | // Create a test HTTP request 16 | req,err := http.NewRequest("GET", "/", nil) 17 | if err != nil { 18 | t.Fatalf("error creating request. Err: %v", err) 19 | } 20 | // Perform the request 21 | resp, err := app.Test(req) 22 | if err != nil { 23 | t.Fatalf("error making request to server. Err: %v", err) 24 | } 25 | // Your test assertions... 26 | if resp.StatusCode != http.StatusOK { 27 | t.Errorf("expected status OK; got %v", resp.Status) 28 | } 29 | expected := "{\"message\":\"Hello World\"}" 30 | body, err := io.ReadAll(resp.Body) 31 | if err != nil { 32 | t.Fatalf("error reading response body. Err: %v", err) 33 | } 34 | if expected != string(body) { 35 | t.Errorf("expected response body to be %v; got %v", expected, string(body)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/htmx/hello_fiber.go.tmpl: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/gofiber/fiber/v2" 9 | ) 10 | 11 | func HelloWebHandler(c *fiber.Ctx) error { 12 | // Parse form data 13 | if err := c.BodyParser(c); err != nil { 14 | innerErr := c.Status(fiber.StatusBadRequest).SendString("Bad Request") 15 | if innerErr != nil { 16 | log.Fatalf("Could not send error in HelloWebHandler: %e", innerErr) 17 | } 18 | } 19 | 20 | // Get the name from the form data 21 | name := c.FormValue("name") 22 | 23 | // Render the component 24 | component := HelloPost(name) 25 | buf := new(bytes.Buffer) 26 | err := component.Render(c.Context(), buf) 27 | if err != nil { 28 | errorString := fmt.Sprintf("Error rendering in HelloWebHandler: %e", err) 29 | innerErr := c.Status(fiber.StatusBadRequest).SendString(errorString) 30 | if innerErr != nil { 31 | log.Fatalf("Could not send error in HelloWebHandler: %e", innerErr) 32 | } 33 | log.Fatalf("%s", errorString) 34 | } 35 | 36 | // Send the response 37 | err = c.Status(fiber.StatusOK).SendString(buf.String()) 38 | if err != nil { 39 | log.Fatalf("Could not send OK in HelloWebHandler: %e", err) 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/melkeydev/go-blueprint 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/charmbracelet/bubbles v0.16.1 7 | github.com/charmbracelet/bubbletea v0.24.2 8 | github.com/charmbracelet/lipgloss v0.9.0 9 | github.com/spf13/cobra v1.7.0 10 | github.com/spf13/pflag v1.0.5 11 | ) 12 | 13 | require ( 14 | github.com/atotto/clipboard v0.1.4 // indirect 15 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 16 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 17 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 18 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 19 | github.com/mattn/go-isatty v0.0.18 // indirect 20 | github.com/mattn/go-localereader v0.0.1 // indirect 21 | github.com/mattn/go-runewidth v0.0.15 // indirect 22 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/muesli/reflow v0.3.0 // indirect 25 | github.com/muesli/termenv v0.15.2 // indirect 26 | github.com/rivo/uniseg v0.2.0 // indirect 27 | golang.org/x/sync v0.1.0 // indirect 28 | golang.org/x/sys v0.12.0 // indirect 29 | golang.org/x/term v0.6.0 // indirect 30 | golang.org/x/text v0.3.8 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /cmd/template/framework/gorillaRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/gorilla.go.tmpl 10 | var gorillaRoutesTemplate []byte 11 | 12 | //go:embed files/tests/default-test.go.tmpl 13 | var gorillaTestHandlerTemplate []byte 14 | 15 | // GorillaTemplates contains the methods used for building 16 | // an app that uses [github.com/gorilla/mux] 17 | type GorillaTemplates struct{} 18 | 19 | func (g GorillaTemplates) Main() []byte { 20 | return mainTemplate 21 | } 22 | 23 | func (g GorillaTemplates) Server() []byte { 24 | return standardServerTemplate 25 | } 26 | 27 | func (g GorillaTemplates) Routes() []byte { 28 | return gorillaRoutesTemplate 29 | } 30 | 31 | func (g GorillaTemplates) TestHandler() []byte { 32 | return gorillaTestHandlerTemplate 33 | } 34 | 35 | func (g GorillaTemplates) HtmxTemplImports() []byte { 36 | return advanced.StdLibHtmxTemplImportsTemplate() 37 | } 38 | 39 | func (g GorillaTemplates) HtmxTemplRoutes() []byte { 40 | return advanced.GorillaHtmxTemplRoutesTemplate() 41 | } 42 | 43 | func (g GorillaTemplates) WebsocketImports() []byte { 44 | return advanced.StdLibWebsocketTemplImportsTemplate() 45 | } 46 | -------------------------------------------------------------------------------- /cmd/template/framework/routerRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/http_router.go.tmpl 10 | var httpRouterRoutesTemplate []byte 11 | 12 | //go:embed files/tests/default-test.go.tmpl 13 | var httpRouterTestHandlerTemplate []byte 14 | 15 | // RouterTemplates contains the methods used for building 16 | // an app that uses [github.com/julienschmidt/httprouter] 17 | type RouterTemplates struct{} 18 | 19 | func (r RouterTemplates) Main() []byte { 20 | return mainTemplate 21 | } 22 | func (r RouterTemplates) Server() []byte { 23 | return standardServerTemplate 24 | } 25 | 26 | func (r RouterTemplates) Routes() []byte { 27 | return httpRouterRoutesTemplate 28 | } 29 | 30 | func (r RouterTemplates) TestHandler() []byte { 31 | return httpRouterTestHandlerTemplate 32 | } 33 | 34 | func (r RouterTemplates) HtmxTemplImports() []byte { 35 | return advanced.StdLibHtmxTemplImportsTemplate() 36 | } 37 | 38 | func (r RouterTemplates) HtmxTemplRoutes() []byte { 39 | return advanced.HttpRouterHtmxTemplRoutesTemplate() 40 | } 41 | 42 | func (r RouterTemplates) WebsocketImports() []byte { 43 | return advanced.StdLibWebsocketTemplImportsTemplate() 44 | } 45 | -------------------------------------------------------------------------------- /cmd/flags/database.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Database string 9 | 10 | // These are all the current databases supported. If you want to add one, you 11 | // can simply copy and paste a line here. Do not forget to also add it into the 12 | // AllowedDBDrivers slice too! 13 | const ( 14 | MySql Database = "mysql" 15 | Postgres Database = "postgres" 16 | Sqlite Database = "sqlite" 17 | Mongo Database = "mongo" 18 | Redis Database = "redis" 19 | Scylla Database = "scylla" 20 | None Database = "none" 21 | ) 22 | 23 | var AllowedDBDrivers = []string{string(MySql), string(Postgres), string(Sqlite), string(Mongo), string(Redis), string(Scylla), string(None)} 24 | 25 | func (f Database) String() string { 26 | return string(f) 27 | } 28 | 29 | func (f *Database) Type() string { 30 | return "Database" 31 | } 32 | 33 | func (f *Database) Set(value string) error { 34 | // Contains isn't available in 1.20 yet 35 | // if AllowedDBDrivers.Contains(value) { 36 | for _, database := range AllowedDBDrivers { 37 | if database == value { 38 | *f = Database(value) 39 | return nil 40 | } 41 | } 42 | 43 | return fmt.Errorf("Database to use. Allowed values: %s", strings.Join(AllowedDBDrivers, ", ")) 44 | } 45 | -------------------------------------------------------------------------------- /docs/docs/blueprint-core/frameworks.md: -------------------------------------------------------------------------------- 1 | Created projects can utilize several Go web frameworks to handle HTTP routing and server functionality. The chosen frameworks are: 2 | 3 | 1. [**Chi**](https://github.com/go-chi/chi): Lightweight and flexible router for building Go HTTP services. 4 | 2. [**Echo**](https://github.com/labstack/echo): High-performance, extensible, minimalist Go web framework. 5 | 3. [**Fiber**](https://github.com/gofiber/fiber): Express-inspired web framework designed to be fast, simple, and efficient. 6 | 4. [**Gin**](https://github.com/gin-gonic/gin): A web framework with a martini-like API, but with much better performance. 7 | 5. [**Gorilla/mux**](https://github.com/gorilla/mux): A powerful URL router and dispatcher for Golang. 8 | 6. [**HttpRouter**](https://github.com/julienschmidt/httprouter): A high-performance HTTP request router that scales well. 9 | 10 | ## Project Structure 11 | 12 | The project is structured with a simple layout, focusing on the cmd, internal, and tests directories: 13 | 14 | ```bash 15 | /(Root) 16 | ├── /cmd 17 | │ └── /api 18 | │ └── main.go 19 | ├── /internal 20 | │ └── /server 21 | │ ├── routes.go 22 | │ ├── routes_test.go 23 | │ └── server.go 24 | ├── go.mod 25 | ├── go.sum 26 | ├── Makefile 27 | └── README.md 28 | ``` 29 | -------------------------------------------------------------------------------- /cmd/ui/spinner/spinner.go: -------------------------------------------------------------------------------- 1 | package spinner 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/charmbracelet/bubbles/spinner" 7 | tea "github.com/charmbracelet/bubbletea" 8 | "github.com/charmbracelet/lipgloss" 9 | ) 10 | 11 | type errMsg error 12 | 13 | type model struct { 14 | spinner spinner.Model 15 | quitting bool 16 | err error 17 | } 18 | 19 | func InitialModelNew() model { 20 | s := spinner.New() 21 | s.Spinner = spinner.Line 22 | s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) 23 | return model{spinner: s} 24 | } 25 | 26 | func (m model) Init() tea.Cmd { 27 | return m.spinner.Tick 28 | } 29 | 30 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 31 | switch msg := msg.(type) { 32 | case tea.KeyMsg: 33 | switch msg.String() { 34 | case "q", "esc", "ctrl+c": 35 | m.quitting = true 36 | return m, tea.Quit 37 | default: 38 | return m, nil 39 | } 40 | 41 | case errMsg: 42 | m.err = msg 43 | return m, nil 44 | 45 | default: 46 | var cmd tea.Cmd 47 | m.spinner, cmd = m.spinner.Update(msg) 48 | return m, cmd 49 | } 50 | } 51 | 52 | func (m model) View() string { 53 | 54 | if m.err != nil { 55 | return m.err.Error() 56 | } 57 | str := fmt.Sprintf("%s Preparing...", m.spinner.View()) 58 | if m.quitting { 59 | return str + "\n" 60 | } 61 | return str 62 | } 63 | -------------------------------------------------------------------------------- /cmd/template/framework/httpRoutes.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/standard_library.go.tmpl 10 | var standardRoutesTemplate []byte 11 | 12 | //go:embed files/server/standard_library.go.tmpl 13 | var standardServerTemplate []byte 14 | 15 | //go:embed files/tests/default-test.go.tmpl 16 | var standardTestHandlerTemplate []byte 17 | 18 | // StandardLibTemplate contains the methods used for building 19 | // an app that uses [net/http] 20 | type StandardLibTemplate struct{} 21 | 22 | func (s StandardLibTemplate) Main() []byte { 23 | return mainTemplate 24 | } 25 | 26 | func (s StandardLibTemplate) Server() []byte { 27 | return standardServerTemplate 28 | } 29 | 30 | func (s StandardLibTemplate) Routes() []byte { 31 | return standardRoutesTemplate 32 | } 33 | 34 | func (s StandardLibTemplate) TestHandler() []byte { 35 | return standardTestHandlerTemplate 36 | } 37 | 38 | func (s StandardLibTemplate) HtmxTemplImports() []byte { 39 | return advanced.StdLibHtmxTemplImportsTemplate() 40 | } 41 | 42 | func (s StandardLibTemplate) HtmxTemplRoutes() []byte { 43 | return advanced.StdLibHtmxTemplRoutesTemplate() 44 | } 45 | 46 | func (s StandardLibTemplate) WebsocketImports() []byte { 47 | return advanced.StdLibWebsocketTemplImportsTemplate() 48 | } 49 | -------------------------------------------------------------------------------- /cmd/flags/frameworks.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Framework string 9 | 10 | // These are all the current frameworks supported. If you want to add one, you 11 | // can simply copy and paste a line here. Do not forget to also add it into the 12 | // AllowedProjectTypes slice too! 13 | const ( 14 | Chi Framework = "chi" 15 | Gin Framework = "gin" 16 | Fiber Framework = "fiber" 17 | GorillaMux Framework = "gorilla/mux" 18 | HttpRouter Framework = "httprouter" 19 | StandardLibrary Framework = "standard-library" 20 | Echo Framework = "echo" 21 | ) 22 | 23 | var AllowedProjectTypes = []string{string(Chi), string(Gin), string(Fiber), string(GorillaMux), string(HttpRouter), string(StandardLibrary), string(Echo)} 24 | 25 | func (f Framework) String() string { 26 | return string(f) 27 | } 28 | 29 | func (f *Framework) Type() string { 30 | return "Framework" 31 | } 32 | 33 | func (f *Framework) Set(value string) error { 34 | // Contains isn't available in 1.20 yet 35 | // if AllowedProjectTypes.Contains(value) { 36 | for _, project := range AllowedProjectTypes { 37 | if project == value { 38 | *f = Framework(value) 39 | return nil 40 | } 41 | } 42 | 43 | return fmt.Errorf("Framework to use. Allowed values: %s", strings.Join(AllowedProjectTypes, ", ")) 44 | } 45 | -------------------------------------------------------------------------------- /cmd/template/framework/fiberServer.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/melkeydev/go-blueprint/cmd/template/advanced" 7 | ) 8 | 9 | //go:embed files/routes/fiber.go.tmpl 10 | var fiberRoutesTemplate []byte 11 | 12 | //go:embed files/server/fiber.go.tmpl 13 | var fiberServerTemplate []byte 14 | 15 | //go:embed files/main/fiber_main.go.tmpl 16 | var fiberMainTemplate []byte 17 | 18 | //go:embed files/tests/fiber-test.go.tmpl 19 | var fiberTestHandlerTemplate []byte 20 | 21 | // FiberTemplates contains the methods used for building 22 | // an app that uses [github.com/gofiber/fiber] 23 | type FiberTemplates struct{} 24 | 25 | func (f FiberTemplates) Main() []byte { 26 | return fiberMainTemplate 27 | } 28 | func (f FiberTemplates) Server() []byte { 29 | return fiberServerTemplate 30 | } 31 | 32 | func (f FiberTemplates) Routes() []byte { 33 | return fiberRoutesTemplate 34 | } 35 | 36 | func (f FiberTemplates) TestHandler() []byte { 37 | return fiberTestHandlerTemplate 38 | } 39 | 40 | func (f FiberTemplates) HtmxTemplImports() []byte { 41 | return advanced.FiberHtmxTemplImportsTemplate() 42 | } 43 | 44 | func (f FiberTemplates) HtmxTemplRoutes() []byte { 45 | return advanced.FiberHtmxTemplRoutesTemplate() 46 | } 47 | 48 | func (f FiberTemplates) WebsocketImports() []byte { 49 | return advanced.FiberWebsocketTemplImportsTemplate() 50 | } 51 | -------------------------------------------------------------------------------- /docs/docs/endpoints-test/websocket.md: -------------------------------------------------------------------------------- 1 | ## Testing with WebSocat 2 | [WebSocat](https://github.com/vi/websocat) is a versatile tool for working with websockets from the command line. Below are some examples of using WebSocat to test the websocket endpoint: 3 | 4 | ```bash 5 | # Start server 6 | make run 7 | ``` 8 | 9 | ```bash 10 | # Connect to the websocket endpoint 11 | $ websocat ws://localhost:PORT/websocket 12 | ``` 13 | 14 | Replace `PORT` with the port number on which your server is running. 15 | 16 | ## Sample Output 17 | Upon successful connection, the client should start receiving timestamp messages from the server every 2 seconds. 18 | 19 | ```bash 20 | server timestamp: 1709046650354893857 21 | server timestamp: 1709046652355956336 22 | server timestamp: 1709046654357101642 23 | server timestamp: 1709046656357202535 24 | server timestamp: 1709046658358258120 25 | server timestamp: 1709046660359338389 26 | server timestamp: 1709046662360422533 27 | server timestamp: 1709046664361194735 28 | server timestamp: 1709046666362308678 29 | server timestamp: 1709046668363390475 30 | server timestamp: 1709046670364477838 31 | server timestamp: 1709046672365193667 32 | server timestamp: 1709046674366265199 33 | server timestamp: 1709046676366564490 34 | server timestamp: 1709046678367646090 35 | server timestamp: 1709046680367851980 36 | server timestamp: 1709046682368920527 37 | ``` 38 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-deploy: 10 | name: Build and deploy docs 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.cache/pip 25 | key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }} 26 | restore-keys: | 27 | ${{ runner.os }}-pip- 28 | ${{ runner.os }}- 29 | 30 | - name: Install dependencies 31 | working-directory: docs 32 | run: make install 33 | 34 | - name: Build and deploy to GitHub Pages 35 | working-directory: docs 36 | run: make deploy 37 | 38 | # config for custom domain on gh-pages 39 | # - name: Create and push CNAME file to gh-pages 40 | # run: | 41 | # git config --local user.email "actions@github.com" 42 | # git config --local user.name "GitHub Actions" 43 | # git checkout gh-pages 44 | 45 | # echo "" > CNAME 46 | 47 | # git add CNAME 48 | # git commit -m "Add CNAME file" 49 | # git push origin gh-pages 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: continuous integration 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.go' 7 | - go.sum 8 | - go.mod 9 | branches-ignore: 10 | - main 11 | pull_request: 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: '1.23.x' 23 | 24 | - name: Deps cache 25 | id: cache-go-deps 26 | uses: actions/cache@v4 27 | env: 28 | cache-name: go-deps-cache 29 | with: 30 | path: ~/godeps 31 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-build-${{ env.cache-name }}- 34 | 35 | - if: ${{ steps.cache-go-deps.outputs.cache-hit != 'true' }} 36 | name: List the state of go modules 37 | continue-on-error: true 38 | run: go mod graph 39 | 40 | - name: Install dependencies 41 | run: | 42 | go mod tidy 43 | go mod download 44 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 45 | 46 | - name: Run golangci-lint 47 | run: golangci-lint run 48 | 49 | - name: Run tests 50 | run: | 51 | go test ./... 52 | 53 | -------------------------------------------------------------------------------- /cmd/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func TestValidateModuleName(t *testing.T) { 6 | passTestCases := []string{ 7 | "github.com/user/project", 8 | "github.com/user/projec1-hyphen", 9 | "github.com/user/projecT_under_Score", 10 | "github.com/user/project.hyphen3", 11 | "project", 12 | "ProJEct", 13 | "PRo_45-.4Jc", 14 | "PRo_/4J/c", 15 | } 16 | for _, testCase := range passTestCases { 17 | ok := ValidateModuleName(testCase) 18 | if !ok { 19 | t.Errorf("testing:%s expected:true got:%v", testCase, ok) 20 | } 21 | } 22 | 23 | failTestCases := []string{ 24 | "", 25 | "/", 26 | ".", 27 | "//", 28 | "/project", 29 | "ProJEct/", 30 | "PRo_$4Jc", 31 | "PRo_@J/c", 32 | } 33 | for _, testCase := range failTestCases { 34 | ok := ValidateModuleName(testCase) 35 | if ok { 36 | t.Errorf("testing:%s expected:false got:%v", testCase, ok) 37 | } 38 | } 39 | } 40 | 41 | func TestGeRootDir(t *testing.T) { 42 | testCases := map[string]string{ 43 | "github.com/user/pro-ject": "pro-ject", 44 | "pro-ject": "pro-ject", 45 | "/": "", 46 | "": "", 47 | "//": "", 48 | "@": "@", 49 | } 50 | 51 | for intput, output := range testCases { 52 | rootDir := GetRootDir(intput) 53 | if rootDir != output { 54 | t.Errorf("testing:%s expected:%s got:%s", intput, output, rootDir) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cmd/template/docker/files/docker-compose/redis.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | {{- if .AdvancedOptions.docker }} 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: prod 8 | restart: unless-stopped 9 | ports: 10 | - ${PORT}:${PORT} 11 | environment: 12 | APP_ENV: ${APP_ENV} 13 | PORT: ${PORT} 14 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 15 | BLUEPRINT_DB_ADDRESS: ${BLUEPRINT_DB_ADDRESS} 16 | BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 17 | BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE} 18 | depends_on: 19 | redis_bp: 20 | condition: service_healthy 21 | networks: 22 | - blueprint 23 | {{- end }} 24 | {{- if and .AdvancedOptions.react .AdvancedOptions.docker }} 25 | frontend: 26 | build: 27 | context: . 28 | dockerfile: Dockerfile 29 | target: frontend 30 | restart: unless-stopped 31 | depends_on: 32 | - app 33 | ports: 34 | - 5173:5173 35 | networks: 36 | - blueprint 37 | {{- end }} 38 | redis_bp: 39 | image: redis:7.2.4 40 | restart: unless-stopped 41 | ports: 42 | - "${BLUEPRINT_DB_PORT}:6379" 43 | {{- if .AdvancedOptions.docker }} 44 | healthcheck: 45 | test: ["CMD", "redis-cli", "ping"] 46 | interval: 5s 47 | timeout: 5s 48 | retries: 3 49 | start_period: 15s 50 | networks: 51 | - blueprint 52 | 53 | networks: 54 | blueprint: 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for helping make go-blueprint better! 4 | - [Contributing](#contributing) 5 | - [Design Principles](#design-principles) 6 | - [Report an Issue](#report-an-issue) 7 | - [Contributing Code with Pull Requests](#contributing-code-with-pull-requests) 8 | - [Requirements](#requirements) 9 | - [Licensing](#licensing) 10 | 11 | ## Design Principles 12 | 13 | Contributions to go-blueprint should align with the project’s design principles: 14 | 15 | * Maintain backwards compatibility whenever possible. 16 | 17 | ## Report an Issue 18 | 19 | If you have run into a bug or want to discuss a new feature, please [file an issue](https://github.com/Melkeydev/go-blueprint/issues). 20 | 21 | ## Contributing Code with Pull Requests 22 | 23 | go-blueprint uses [Github pull requests](https://github.com/Melkeydev/go-blueprint/pulls). Feel free to fork, hack away at your changes and submit. 24 | 25 | ### Requirements 26 | 27 | * All commands and functionality should be documented appropriately 28 | * All new functionality/features should have appropriate unit testing 29 | 30 | go-blueprint strives to have a consistent set of documentation that matches the command structure and any new functionality must have accompanying documentation in the PR. 31 | 32 | ## Licensing 33 | 34 | See the [LICENSE](https://github.com/melkeydev/go-blueprint/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 35 | -------------------------------------------------------------------------------- /docs/docs/endpoints-test/mongo.md: -------------------------------------------------------------------------------- 1 | To test the MongoDB Health Check endpoint, use the following curl command: 2 | 3 | ```bash 4 | curl http://localhost:PORT/health 5 | ``` 6 | ## Health Function 7 | 8 | The `Health` function checks the health of the MongoDB by pinging it. It returns a simple map containing a health message. 9 | 10 | ### Functionality 11 | 12 | **Ping MongoDB Server**: The function pings the MongoDB thru server to check its availability. 13 | 14 | - If the ping fails, it logs the error and terminates the program. 15 | - If the ping succeeds, it returns a health message indicating that the server is healthy. 16 | 17 | ### Sample Output 18 | 19 | The `Health` returns a JSON-like map structure with a single key indicating the health status: 20 | 21 | ```json 22 | { 23 | "message": "It's healthy" 24 | } 25 | ``` 26 | 27 | ## Code implementation 28 | 29 | ```go 30 | func (s *service) Health() map[string]string { 31 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 32 | defer cancel() 33 | 34 | err := s.db.Ping(ctx, nil) 35 | if err != nil { 36 | log.Fatalf("db down: %v", err) 37 | } 38 | 39 | return map[string]string{ 40 | "message": "It's healthy", 41 | } 42 | } 43 | ``` 44 | 45 | ## Note 46 | 47 | MongoDB does not support advanced health check functions like SQL databases or Redis. The implementation is basic, providing only a simple ping response to indicate if the server is reachable and DB connection healthy. 48 | -------------------------------------------------------------------------------- /.github/workflows/generate-linter-core.yml: -------------------------------------------------------------------------------- 1 | name: Linting Generated Blueprints Core 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | 7 | jobs: 8 | framework_matrix: 9 | strategy: 10 | matrix: 11 | framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo] 12 | driver: [mysql, postgres, sqlite, mongo, redis, scylla, none] 13 | git: [commit, stage, skip] 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: '1.23.x' 23 | 24 | - name: Install golangci-lint 25 | run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 26 | 27 | - name: Commit report 28 | run: | 29 | git config --global user.name 'testname' 30 | git config --global user.email 'testemail@users.noreply.github.com' 31 | 32 | - name: Set framework variable 33 | id: set-proejct-directory 34 | run: echo "PROJECT_DIRECTORY=${{ matrix.framework }}" | sed 's/\//-/g' >> $GITHUB_ENV 35 | 36 | - name: build templates 37 | run: script -q /dev/null -c "go run main.go create -n ${{ env.PROJECT_DIRECTORY }} -f ${{ matrix.framework}} -d ${{ matrix.driver }} -g ${{ matrix.git}}" 38 | 39 | - name: golangci-lint 40 | run: | 41 | cd ${{ env.PROJECT_DIRECTORY }} 42 | golangci-lint run 43 | 44 | - name: remove templates 45 | run: rm -rf ${{ env.PROJECT_DIRECTORY }} 46 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/docker/dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.4-alpine AS build 2 | {{- if or (.AdvancedOptions.tailwind) (eq .DBDriver "sqlite") }} 3 | RUN apk add --no-cache{{- if .AdvancedOptions.tailwind }} curl libstdc++ libgcc{{ end }}{{- if (eq .DBDriver "sqlite") }} alpine-sdk{{ end }} 4 | {{- end }} 5 | 6 | WORKDIR /app 7 | 8 | COPY go.mod go.sum ./ 9 | RUN go mod download 10 | 11 | COPY . . 12 | 13 | {{- if or .AdvancedOptions.htmx .AdvancedOptions.tailwind }} 14 | RUN go install github.com/a-h/templ/cmd/templ@latest && \ 15 | templ generate{{- if .AdvancedOptions.tailwind}} && \{{- end}} 16 | {{- end}} 17 | 18 | {{- if .AdvancedOptions.tailwind}} 19 | curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64-musl -o tailwindcss && \ 20 | chmod +x tailwindcss && \ 21 | ./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css 22 | {{- end }} 23 | 24 | RUN {{ if (eq .DBDriver "sqlite") }}CGO_ENABLED=1 GOOS=linux {{ end }}go build -o main cmd/api/main.go 25 | 26 | FROM alpine:3.20.1 AS prod 27 | WORKDIR /app 28 | COPY --from=build /app/main /app/main 29 | EXPOSE ${PORT} 30 | CMD ["./main"] 31 | 32 | {{ if .AdvancedOptions.react}} 33 | FROM node:20 AS frontend_builder 34 | WORKDIR /frontend 35 | 36 | COPY frontend/package*.json ./ 37 | RUN npm install 38 | COPY frontend/. . 39 | RUN npm run build 40 | 41 | FROM node:23-slim AS frontend 42 | RUN npm install -g serve 43 | COPY --from=frontend_builder /frontend/dist /app/dist 44 | EXPOSE 5173 45 | CMD ["serve", "-s", "/app/dist", "-l", "5173"] 46 | {{- end}} 47 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/tests/mongo.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | 8 | "github.com/testcontainers/testcontainers-go" 9 | "github.com/testcontainers/testcontainers-go/modules/mongodb" 10 | ) 11 | 12 | func mustStartMongoContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) { 13 | dbContainer, err := mongodb.Run(context.Background(), "mongo:latest") 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | dbHost, err := dbContainer.Host(context.Background()) 19 | if err != nil { 20 | return dbContainer.Terminate, err 21 | } 22 | 23 | dbPort, err := dbContainer.MappedPort(context.Background(), "27017/tcp") 24 | if err != nil { 25 | return dbContainer.Terminate, err 26 | } 27 | 28 | host = dbHost 29 | port = dbPort.Port() 30 | 31 | return dbContainer.Terminate, err 32 | } 33 | 34 | func TestMain(m *testing.M) { 35 | teardown, err := mustStartMongoContainer() 36 | if err != nil { 37 | log.Fatalf("could not start mongodb container: %v", err) 38 | } 39 | 40 | m.Run() 41 | 42 | if teardown != nil && teardown(context.Background()) != nil { 43 | log.Fatalf("could not teardown mongodb container: %v", err) 44 | } 45 | } 46 | 47 | func TestNew(t *testing.T) { 48 | srv := New() 49 | if srv == nil { 50 | t.Fatal("New() returned nil") 51 | } 52 | } 53 | 54 | func TestHealth(t *testing.T) { 55 | srv := New() 56 | 57 | stats := srv.Health() 58 | 59 | if stats["message"] != "It's healthy" { 60 | t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/react/app.tsx.tmpl: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import reactLogo from './assets/react.svg' 3 | import viteLogo from '/vite.svg' 4 | import './App.css' 5 | 6 | function App() { 7 | const [count, setCount] = useState(0) 8 | 9 | const fetchData = () => { 10 | fetch(`http://localhost:${import.meta.env.VITE_PORT}/`) 11 | .then(response => response.text()) 12 | .then(data => setMessage(data)) 13 | .catch(error => console.error('Error fetching data:', error)) 14 | } 15 | const [message, setMessage] = useState('') 16 | 17 | return ( 18 | <> 19 |
20 | 21 | Vite logo 22 | 23 | 24 | React logo 25 | 26 |
27 |

Vite + React

28 |
29 | 32 |

33 | Edit src/App.tsx and save to test HMR 34 |

35 |
36 |

37 | Click on the Vite and React logos to learn more 38 |

39 | 42 | {message && ( 43 |
44 |

Server Response:

45 |

{message}

46 |
47 | )} 48 | 49 | ) 50 | } 51 | 52 | export default App 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Found a bug? Please let us know! 3 | title: "[Bug]" 4 | labels: ["Bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please provide as much information as possible. This will help us resolve the Bug quickly and accurately. 10 | - type: markdown 11 | attributes: 12 | value: --- 13 | - type: textarea 14 | id: what-happened 15 | attributes: 16 | label: What is the problem? 17 | description: | 18 | Description of what needs to be fixed. 19 | placeholder: Tell us what you see! 20 | validations: 21 | required: true 22 | - type: input 23 | id: os 24 | attributes: 25 | label: Operating System 26 | description: What is the affected operating system? 27 | validations: 28 | required: true 29 | - type: input 30 | id: arch 31 | attributes: 32 | label: Architecture Version (x86, x64, arm, etc) 33 | description: (x86, x64, arm, etc) 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: reproduce 38 | attributes: 39 | label: Steps to reproduce 40 | description: | 41 | This includes the steps for reproducing the problem, the expected result, and the actual result. 42 | placeholder: | 43 | 1. ... 44 | 2. ... 45 | 3. ... 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: logs 50 | attributes: 51 | label: Relevant log output 52 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 53 | render: shell 54 | -------------------------------------------------------------------------------- /cmd/template/framework/files/main/main.go.tmpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | "{{.ProjectName}}/internal/server" 13 | ) 14 | 15 | func gracefulShutdown(apiServer *http.Server, done chan bool) { 16 | // Create context that listens for the interrupt signal from the OS. 17 | ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 18 | defer stop() 19 | 20 | // Listen for the interrupt signal. 21 | <-ctx.Done() 22 | 23 | log.Println("shutting down gracefully, press Ctrl+C again to force") 24 | stop() // Allow Ctrl+C to force shutdown 25 | 26 | // The context is used to inform the server it has 5 seconds to finish 27 | // the request it is currently handling 28 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 29 | defer cancel() 30 | if err := apiServer.Shutdown(ctx); err != nil { 31 | log.Printf("Server forced to shutdown with error: %v", err) 32 | } 33 | 34 | log.Println("Server exiting") 35 | 36 | // Notify the main goroutine that the shutdown is complete 37 | done <- true 38 | } 39 | 40 | func main() { 41 | 42 | server := server.NewServer() 43 | 44 | // Create a done channel to signal when the shutdown is complete 45 | done := make(chan bool, 1) 46 | 47 | // Run graceful shutdown in a separate goroutine 48 | go gracefulShutdown(server, done) 49 | 50 | err := server.ListenAndServe() 51 | if err != nil && err != http.ErrServerClosed { 52 | panic(fmt.Sprintf("http server error: %s", err)) 53 | } 54 | 55 | // Wait for the graceful shutdown to complete 56 | <-done 57 | log.Println("Graceful shutdown complete.") 58 | } 59 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/advanced-flag.md: -------------------------------------------------------------------------------- 1 | # Advanced Flag in Blueprint 2 | 3 | The `--advanced` flag in Blueprint serves as a switch to enable additional features during project creation. It is applied with the `create` command and unlocks the following features: 4 | 5 | - **HTMX Support using Templ:** 6 | Enables the integration of HTMX support for dynamic web pages using Templ. 7 | 8 | - **CI/CD Workflow Setup using GitHub Actions:** 9 | Automates the setup of a CI/CD workflow using GitHub Actions. 10 | 11 | - **Websocket Support:** 12 | WebSocket endpoint that sends continuous data streams through the WS protocol. 13 | 14 | - **Tailwind:** 15 | Adds Tailwind CSS support to the project. 16 | 17 | - **Docker:** 18 | Docker configuration for go project. 19 | 20 | - **React:** 21 | Frontend written in TypeScript, including an example fetch request to the backend. 22 | 23 | 24 | To utilize the `--advanced` flag, use the following command: 25 | 26 | ```bash 27 | go-blueprint create --name --framework --driver --advanced 28 | ``` 29 | 30 | By including the `--advanced` flag, users can choose one or all of the advanced features. The flag enhances the simplicity of Blueprint while offering flexibility for users who require additional functionality. 31 | 32 | To recreate the project using the same configuration semi-interactively, use the following command: 33 | ```bash 34 | go-blueprint create --name my-project --framework chi --driver mysql --advanced 35 | ``` 36 | 37 | Non-Interactive Setup is also possible: 38 | 39 | ```bash 40 | go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind 41 | ``` 42 | -------------------------------------------------------------------------------- /cmd/template/docker/files/docker-compose/mongo.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | {{- if .AdvancedOptions.docker }} 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: prod 8 | restart: unless-stopped 9 | ports: 10 | - ${PORT}:${PORT} 11 | environment: 12 | APP_ENV: ${APP_ENV} 13 | PORT: ${PORT} 14 | BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST} 15 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 16 | BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME} 17 | BLUEPRINT_DB_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD} 18 | depends_on: 19 | mongo_bp: 20 | condition: service_healthy 21 | networks: 22 | - blueprint 23 | {{- end }} 24 | {{- if and .AdvancedOptions.react .AdvancedOptions.docker }} 25 | frontend: 26 | build: 27 | context: . 28 | dockerfile: Dockerfile 29 | target: frontend 30 | restart: unless-stopped 31 | depends_on: 32 | - app 33 | ports: 34 | - 5173:5173 35 | networks: 36 | - blueprint 37 | {{- end }} 38 | mongo_bp: 39 | image: mongo:latest 40 | restart: unless-stopped 41 | environment: 42 | MONGO_INITDB_ROOT_USERNAME: ${BLUEPRINT_DB_USERNAME} 43 | MONGO_INITDB_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD} 44 | ports: 45 | - "${BLUEPRINT_DB_PORT}:27017" 46 | volumes: 47 | - mongo_volume_bp:/data/db 48 | {{- if .AdvancedOptions.docker }} 49 | healthcheck: 50 | test: ["CMD","mongosh", "--eval", "db.adminCommand('ping')"] 51 | interval: 5s 52 | timeout: 5s 53 | retries: 3 54 | start_period: 15s 55 | networks: 56 | - blueprint 57 | {{- end }} 58 | 59 | volumes: 60 | mongo_volume_bp: 61 | {{- if .AdvancedOptions.docker }} 62 | networks: 63 | blueprint: 64 | {{- end }} 65 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/tests/redis.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | 8 | "github.com/testcontainers/testcontainers-go" 9 | "github.com/testcontainers/testcontainers-go/modules/redis" 10 | ) 11 | 12 | func mustStartRedisContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) { 13 | dbContainer, err := redis.Run( 14 | context.Background(), 15 | "docker.io/redis:7.2.4", 16 | redis.WithSnapshotting(10, 1), 17 | redis.WithLogLevel(redis.LogLevelVerbose), 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | dbHost, err := dbContainer.Host(context.Background()) 24 | if err != nil { 25 | return dbContainer.Terminate, err 26 | } 27 | 28 | dbPort, err := dbContainer.MappedPort(context.Background(), "6379/tcp") 29 | if err != nil { 30 | return dbContainer.Terminate, err 31 | } 32 | 33 | address = dbHost 34 | port = dbPort.Port() 35 | database = "0" 36 | 37 | return dbContainer.Terminate, err 38 | } 39 | 40 | func TestMain(m *testing.M) { 41 | teardown, err := mustStartRedisContainer() 42 | if err != nil { 43 | log.Fatalf("could not start redis container: %v", err) 44 | } 45 | 46 | m.Run() 47 | 48 | if teardown != nil && teardown(context.Background()) != nil { 49 | log.Fatalf("could not teardown redis container: %v", err) 50 | } 51 | } 52 | 53 | func TestNew(t *testing.T) { 54 | srv := New() 55 | if srv == nil { 56 | t.Fatal("New() returned nil") 57 | } 58 | } 59 | 60 | func TestHealth(t *testing.T) { 61 | srv := New() 62 | 63 | stats := srv.Health() 64 | 65 | if stats["redis_status"] != "up" { 66 | t.Fatalf("expected status to be up, got %s", stats["redis_status"]) 67 | } 68 | 69 | if _, ok := stats["redis_version"]; !ok { 70 | t.Fatalf("expected redis_version to be present, got %v", stats["redis_version"]) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cmd/template/framework/files/main/fiber_main.go.tmpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "strconv" 10 | "syscall" 11 | "time" 12 | "{{.ProjectName}}/internal/server" 13 | 14 | _ "github.com/joho/godotenv/autoload" 15 | ) 16 | 17 | func gracefulShutdown(fiberServer *server.FiberServer, done chan bool) { 18 | // Create context that listens for the interrupt signal from the OS. 19 | ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 20 | defer stop() 21 | 22 | // Listen for the interrupt signal. 23 | <-ctx.Done() 24 | 25 | log.Println("shutting down gracefully, press Ctrl+C again to force") 26 | stop() // Allow Ctrl+C to force shutdown 27 | 28 | // The context is used to inform the server it has 5 seconds to finish 29 | // the request it is currently handling 30 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 31 | defer cancel() 32 | if err := fiberServer.ShutdownWithContext(ctx); err != nil { 33 | log.Printf("Server forced to shutdown with error: %v", err) 34 | } 35 | 36 | log.Println("Server exiting") 37 | 38 | // Notify the main goroutine that the shutdown is complete 39 | done <- true 40 | } 41 | 42 | func main() { 43 | 44 | server := server.New() 45 | 46 | server.RegisterFiberRoutes() 47 | 48 | // Create a done channel to signal when the shutdown is complete 49 | done := make(chan bool, 1) 50 | 51 | go func() { 52 | port, _ := strconv.Atoi(os.Getenv("PORT")) 53 | err := server.Listen(fmt.Sprintf(":%d", port)) 54 | if err != nil { 55 | panic(fmt.Sprintf("http server error: %s", err)) 56 | } 57 | }() 58 | 59 | // Run graceful shutdown in a separate goroutine 60 | go gracefulShutdown(server, done) 61 | 62 | // Wait for the graceful shutdown to complete 63 | <-done 64 | log.Println("Graceful shutdown complete.") 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | tag: 7 | description: "Release tag to publish (e.g., v1.0.0)" 8 | required: true 9 | type: string 10 | workflow_dispatch: 11 | inputs: 12 | tag: 13 | description: "Release tag to publish (e.g., v1.0.0)" 14 | required: true 15 | type: string 16 | 17 | jobs: 18 | npm-publish: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | id-token: write 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Set up Node.js 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: 22 32 | registry-url: "https://registry.npmjs.org" 33 | 34 | - name: Download release assets 35 | run: | 36 | TAG="${{ inputs.tag }}" 37 | VERSION=${TAG#v} 38 | mkdir -p dist 39 | gh release download "$TAG" --dir dist 40 | echo "VERSION=$VERSION" >> $GITHUB_ENV 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Create npm packages 45 | run: | 46 | chmod +x ./scripts/create-npm-packages.sh 47 | ./scripts/create-npm-packages.sh "$VERSION" 48 | 49 | - name: Publish platform-specific packages to npm 50 | run: | 51 | for platform_dir in platform-packages/go-blueprint-*; do 52 | if [ -d "$platform_dir" ]; then 53 | cd "$platform_dir" 54 | npm publish --provenance --access public 55 | cd - > /dev/null 56 | fi 57 | done 58 | 59 | - name: Publish main package to npm 60 | run: | 61 | cd npm-package 62 | npm publish --provenance --access public 63 | 64 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/goreleaser.md: -------------------------------------------------------------------------------- 1 | Release process for Go projects, providing extensive customization options through its configuration file, `.goreleaser.yml`. By default, it ensures dependency cleanliness, builds binaries for various platforms and architectures, facilitates pre-release creation, and organizes binary packaging into archives with naming schemes. 2 | 3 | For comprehensive insights into customization possibilities, refer to the [GoReleaser documentation](https://goreleaser.com/customization/). 4 | 5 | ## Usage with Tags 6 | 7 | To initiate release builds with GoReleaser, you need to follow these steps: 8 | 9 | - **Tag Creation:** 10 | When your project is ready for a release, create a new tag in your Git repository. For example: 11 | ```bash 12 | git tag v1.0.0 13 | ``` 14 | 15 | - **Tag Pushing:** 16 | Push the tag to the repository to trigger GoReleaser: 17 | ```bash 18 | git push origin v1.0.0 19 | ``` 20 | 21 | Following these steps ensures proper tagging of your project, prompting GoReleaser to execute configured releases. This approach simplifies release management and automates artifact distribution. 22 | 23 | ## Go Test - Continuous Integration for Go Projects 24 | 25 | The `go-test.yml` file defines a GitHub Actions workflow for continuous integration (CI) of Go projects within a GitHub repository. 26 | 27 | ## Workflow Steps 28 | 29 | The job outlined in this workflow includes the following steps: 30 | 31 | 1. **Checkout:** 32 | Fetches the project's codebase from the repository. 33 | 34 | 2. **Go Setup:** 35 | Configures the Go environment with version 1.21.x. 36 | 37 | 3. **Build and Test:** 38 | Builds the project using `go build` and runs tests across all packages (`./...`) using `go test`. 39 | 40 | This workflow serves to automate the testing process of a Go project within a GitHub repository, ensuring code quality and reliability with each commit and pull request. 41 | -------------------------------------------------------------------------------- /.github/workflows/generate-linter-advanced.yml: -------------------------------------------------------------------------------- 1 | name: Linting Generated Blueprints Advanced 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | 7 | jobs: 8 | framework_matrix: 9 | strategy: 10 | matrix: 11 | framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo] 12 | driver: [postgres] 13 | git: [commit] 14 | advanced: [htmx, githubaction, websocket, tailwind, docker, react] 15 | 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: '1.23.x' 24 | 25 | - name: Install golangci-lint 26 | run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 27 | 28 | - name: Commit report 29 | run: | 30 | git config --global user.name 'testname' 31 | git config --global user.email 'testemail@users.noreply.github.com' 32 | 33 | - name: Set framework variable 34 | id: set-proejct-directory 35 | run: echo "PROJECT_DIRECTORY=${{ matrix.framework }}" | sed 's/\//-/g' >> $GITHUB_ENV 36 | 37 | - name: build templates 38 | run: script -q /dev/null -c "go run main.go create -n ${{ env.PROJECT_DIRECTORY }} -f ${{ matrix.framework}} -d ${{ matrix.driver }} -g ${{ matrix.git}} --advanced --feature ${{ matrix.advanced }}" 39 | 40 | - if: ${{ matrix.advanced == 'htmx' || matrix.advanced == 'tailwind' }} 41 | name: Install Templ & gen templates 42 | run: | 43 | go install github.com/a-h/templ/cmd/templ@latest 44 | /home/runner/go/bin/templ generate -path ${{ env.PROJECT_DIRECTORY }} 45 | 46 | - name: golangci-lint 47 | run: | 48 | cd ${{ env.PROJECT_DIRECTORY }} 49 | golangci-lint run 50 | 51 | - name: remove templates 52 | run: rm -rf ${{ env.PROJECT_DIRECTORY }} 53 | -------------------------------------------------------------------------------- /cmd/template/docker/files/docker-compose/postgres.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | {{- if .AdvancedOptions.docker }} 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: prod 8 | restart: unless-stopped 9 | ports: 10 | - ${PORT}:${PORT} 11 | environment: 12 | APP_ENV: ${APP_ENV} 13 | PORT: ${PORT} 14 | BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST} 15 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 16 | BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE} 17 | BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME} 18 | BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 19 | BLUEPRINT_DB_SCHEMA: ${BLUEPRINT_DB_SCHEMA} 20 | depends_on: 21 | psql_bp: 22 | condition: service_healthy 23 | networks: 24 | - blueprint 25 | {{- end }} 26 | {{- if and .AdvancedOptions.react .AdvancedOptions.docker }} 27 | frontend: 28 | build: 29 | context: . 30 | dockerfile: Dockerfile 31 | target: frontend 32 | restart: unless-stopped 33 | depends_on: 34 | - app 35 | ports: 36 | - 5173:5173 37 | networks: 38 | - blueprint 39 | {{- end }} 40 | psql_bp: 41 | image: postgres:latest 42 | restart: unless-stopped 43 | environment: 44 | POSTGRES_DB: ${BLUEPRINT_DB_DATABASE} 45 | POSTGRES_USER: ${BLUEPRINT_DB_USERNAME} 46 | POSTGRES_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 47 | ports: 48 | - "${BLUEPRINT_DB_PORT}:5432" 49 | volumes: 50 | - psql_volume_bp:/var/lib/postgresql/data 51 | {{- if .AdvancedOptions.docker }} 52 | healthcheck: 53 | test: ["CMD-SHELL", "sh -c 'pg_isready -U ${BLUEPRINT_DB_USERNAME} -d ${BLUEPRINT_DB_DATABASE}'"] 54 | interval: 5s 55 | timeout: 5s 56 | retries: 3 57 | start_period: 15s 58 | networks: 59 | - blueprint 60 | {{- end }} 61 | 62 | volumes: 63 | psql_volume_bp: 64 | {{- if .AdvancedOptions.docker }} 65 | networks: 66 | blueprint: 67 | {{- end }} 68 | -------------------------------------------------------------------------------- /cmd/template/docker/files/docker-compose/mysql.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | {{- if .AdvancedOptions.docker }} 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: prod 8 | restart: unless-stopped 9 | ports: 10 | - ${PORT}:${PORT} 11 | environment: 12 | APP_ENV: ${APP_ENV} 13 | PORT: ${PORT} 14 | BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST} 15 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 16 | BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE} 17 | BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME} 18 | BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 19 | depends_on: 20 | mysql_bp: 21 | condition: service_healthy 22 | networks: 23 | - blueprint 24 | {{- end }} 25 | {{- if and .AdvancedOptions.react .AdvancedOptions.docker }} 26 | frontend: 27 | build: 28 | context: . 29 | dockerfile: Dockerfile 30 | target: frontend 31 | restart: unless-stopped 32 | depends_on: 33 | - app 34 | ports: 35 | - 5173:5173 36 | networks: 37 | - blueprint 38 | {{- end }} 39 | mysql_bp: 40 | image: mysql:latest 41 | restart: unless-stopped 42 | environment: 43 | MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE} 44 | MYSQL_USER: ${BLUEPRINT_DB_USERNAME} 45 | MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 46 | MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD} 47 | ports: 48 | - "${BLUEPRINT_DB_PORT}:3306" 49 | volumes: 50 | - mysql_volume_bp:/var/lib/mysql 51 | {{- if .AdvancedOptions.docker }} 52 | healthcheck: 53 | test: ["CMD", "mysqladmin", "ping", "-h", "${BLUEPRINT_DB_HOST}", "-u", "${BLUEPRINT_DB_USERNAME}", "--password=${BLUEPRINT_DB_PASSWORD}"] 54 | interval: 5s 55 | timeout: 5s 56 | retries: 3 57 | start_period: 15s 58 | networks: 59 | - blueprint 60 | {{- end }} 61 | 62 | volumes: 63 | mysql_volume_bp: 64 | {{- if .AdvancedOptions.docker }} 65 | networks: 66 | blueprint: 67 | {{- end }} 68 | -------------------------------------------------------------------------------- /cmd/template/docker/files/docker-compose/scylla.tmpl: -------------------------------------------------------------------------------- 1 | services: 2 | {{- if .AdvancedOptions.docker }} 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | target: prod 8 | restart: unless-stopped 9 | ports: 10 | - ${PORT}:${PORT} 11 | environment: 12 | APP_ENV: ${APP_ENV} 13 | PORT: ${PORT} 14 | BLUEPRINT_DB_HOSTS: ${BLUEPRINT_DB_HOSTS} 15 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 16 | BLUEPRINT_DB_CONSISTENCY: ${BLUEPRINT_DB_CONSISTENCY} 17 | BLUEPRINT_DB_KEYSPACE: ${BLUEPRINT_DB_KEYSPACE} 18 | BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME} 19 | BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 20 | BLUEPRINT_DB_CONNECTIONS: ${BLUEPRINT_DB_CONNECTIONS} 21 | depends_on: 22 | scylla_bp: 23 | condition: service_healthy 24 | networks: 25 | - blueprint 26 | {{- end }} 27 | {{- if and .AdvancedOptions.react .AdvancedOptions.docker }} 28 | frontend: 29 | build: 30 | context: . 31 | dockerfile: Dockerfile 32 | target: frontend 33 | restart: unless-stopped 34 | depends_on: 35 | - app 36 | ports: 37 | - 5173:5173 38 | networks: 39 | - blueprint 40 | {{- end }} 41 | scylla_bp: 42 | image: scylladb/scylla:6.2 43 | restart: unless-stopped 44 | command: 45 | - --smp=2 46 | - --memory=1GB 47 | - --overprovisioned=1 48 | - --developer-mode=1 # Disable for production 49 | - --seeds=scylla_bp 50 | ports: 51 | - "9042:9042" 52 | - "19042:19042" 53 | volumes: 54 | - scylla_bp:/var/lib/scylla 55 | {{- if .AdvancedOptions.docker }} 56 | healthcheck: 57 | test: ["CMD-SHELL", 'cqlsh -e "SHOW VERSION" || exit 1'] 58 | interval: 15s 59 | timeout: 30s 60 | retries: 15 61 | start_period: 30s 62 | networks: 63 | - blueprint 64 | {{- end }} 65 | volumes: 66 | scylla_bp: 67 | {{- if .AdvancedOptions.docker }} 68 | networks: 69 | blueprint: 70 | {{- end }} -------------------------------------------------------------------------------- /docs/docs/endpoints-test/server.md: -------------------------------------------------------------------------------- 1 | ## Testing Endpoints with CURL and WebSocat 2 | 3 | Testing endpoints is an essential part of ensuring the correctness and functionality of your app. Depending on what options are used for go-blueprint project creation, you have various endpoints for testing your init application status. 4 | 5 | 6 | Before proceeding, ensure you have the following tools installed: 7 | 8 | - [CURL](https://curl.se/docs/manpage.html): A command-line tool for transferring data with URLs. 9 | - [WebSocat](https://github.com/vi/websocat): A command-line WebSocket client. 10 | 11 | You can utilize alternative tools that support the WebSocket protocol to establish connections with the server. WebSocat is an open-source CLI tool, while [POSTMAN](https://www.postman.com/) serves as a GUI tool specifically designed for testing APIs and WebSocket functionality. 12 | 13 | ## Hello World Endpoint 14 | 15 | To test the Hello World endpoint, execute the following curl command: 16 | 17 | ```bash 18 | curl http://localhost:PORT 19 | ``` 20 | 21 | Sample Output: 22 | ```json 23 | {"message": "Hello World"} 24 | ``` 25 | If the server is running and it is healthy, you should see the message 'Hello World' in the response. 26 | Also, depending on the framework you are using, there will be logs in the terminal: 27 | 28 | ```bash 29 | make run 30 | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. 31 | 32 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. 33 | - using env: export GIN_MODE=release 34 | - using code: gin.SetMode(gin.ReleaseMode) 35 | 36 | [GIN-debug] GET / --> websocket-test/internal/server.(*Server).HelloWorldHandler-fm (3 handlers) 37 | [GIN-debug] GET /health --> websocket-test/internal/server.(*Server).healthHandler-fm (3 handlers) 38 | [GIN-debug] GET /websocket --> websocket-test/internal/server.(*Server).websocketHandler-fm (3 handlers) 39 | [GIN] 2024/05/28 - 17:44:31 | 200 | 27.93µs | 127.0.0.1 | GET "/" 40 | ``` 41 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/fiber.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | {{if .AdvancedOptions.websocket}} 5 | "context" 6 | "log" 7 | "fmt" 8 | "time" 9 | {{end}} 10 | "github.com/gofiber/fiber/v2" 11 | "github.com/gofiber/fiber/v2/middleware/cors" 12 | {{.AdvancedTemplates.TemplateImports}} 13 | ) 14 | 15 | func (s *FiberServer) RegisterFiberRoutes() { 16 | // Apply CORS middleware 17 | s.App.Use(cors.New(cors.Config{ 18 | AllowOrigins: "*", 19 | AllowMethods: "GET,POST,PUT,DELETE,OPTIONS,PATCH", 20 | AllowHeaders: "Accept,Authorization,Content-Type", 21 | AllowCredentials: false, // credentials require explicit origins 22 | MaxAge: 300, 23 | })) 24 | 25 | s.App.Get("/", s.HelloWorldHandler) 26 | {{if ne .DBDriver "none"}} 27 | s.App.Get("/health", s.healthHandler) 28 | {{end}} 29 | {{if .AdvancedOptions.websocket}} 30 | s.App.Get("/websocket", websocket.New(s.websocketHandler)) 31 | {{end}} 32 | 33 | {{.AdvancedTemplates.TemplateRoutes}} 34 | } 35 | 36 | func (s *FiberServer) HelloWorldHandler(c *fiber.Ctx) error { 37 | resp := fiber.Map{ 38 | "message": "Hello World", 39 | } 40 | 41 | return c.JSON(resp) 42 | } 43 | 44 | {{if ne .DBDriver "none"}} 45 | func (s *FiberServer) healthHandler(c *fiber.Ctx) error { 46 | return c.JSON(s.db.Health()) 47 | } 48 | {{end}} 49 | 50 | {{if .AdvancedOptions.websocket}} 51 | func (s *FiberServer) websocketHandler(con *websocket.Conn) { 52 | ctx, cancel := context.WithCancel(context.Background()) 53 | 54 | go func() { 55 | for { 56 | _, _, err := con.ReadMessage() 57 | if err != nil { 58 | cancel() 59 | log.Println("Receiver Closing", err) 60 | break 61 | } 62 | } 63 | }() 64 | 65 | for { 66 | select { 67 | case <-ctx.Done(): 68 | return 69 | default: 70 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 71 | if err := con.WriteMessage(websocket.TextMessage, []byte(payload)); err != nil { 72 | log.Printf("could not write to socket: %v", err) 73 | return 74 | } 75 | time.Sleep(time.Second * 2) 76 | } 77 | } 78 | } 79 | {{end}} 80 | 81 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/gin.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | {{if .AdvancedOptions.websocket}} 6 | "log" 7 | "fmt" 8 | "time" 9 | {{end}} 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/gin-contrib/cors" 13 | 14 | {{.AdvancedTemplates.TemplateImports}} 15 | ) 16 | 17 | func (s *Server) RegisterRoutes() http.Handler { 18 | r := gin.Default() 19 | 20 | r.Use(cors.New(cors.Config{ 21 | AllowOrigins: []string{"http://localhost:5173"}, // Add your frontend URL 22 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, 23 | AllowHeaders: []string{"Accept", "Authorization", "Content-Type"}, 24 | AllowCredentials: true, // Enable cookies/auth 25 | })) 26 | 27 | r.GET("/", s.HelloWorldHandler) 28 | {{if ne .DBDriver "none"}} 29 | r.GET("/health", s.healthHandler) 30 | {{end}} 31 | {{if .AdvancedOptions.websocket}} 32 | r.GET("/websocket", s.websocketHandler) 33 | {{end}} 34 | 35 | {{.AdvancedTemplates.TemplateRoutes}} 36 | 37 | return r 38 | } 39 | 40 | func (s *Server) HelloWorldHandler(c *gin.Context) { 41 | resp := make(map[string]string) 42 | resp["message"] = "Hello World" 43 | 44 | c.JSON(http.StatusOK, resp) 45 | } 46 | 47 | {{if ne .DBDriver "none"}} 48 | func (s *Server) healthHandler(c *gin.Context) { 49 | c.JSON(http.StatusOK, s.db.Health()) 50 | } 51 | {{end}} 52 | 53 | {{if .AdvancedOptions.websocket}} 54 | func (s *Server) websocketHandler(c *gin.Context) { 55 | w := c.Writer 56 | r := c.Request 57 | socket, err := websocket.Accept(w, r, nil) 58 | 59 | if err != nil { 60 | log.Printf("could not open websocket: %v", err) 61 | _, _ = w.Write([]byte("could not open websocket")) 62 | w.WriteHeader(http.StatusInternalServerError) 63 | return 64 | } 65 | 66 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 67 | 68 | ctx := r.Context() 69 | socketCtx := socket.CloseRead(ctx) 70 | 71 | for { 72 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 73 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 74 | if err != nil { 75 | break 76 | } 77 | time.Sleep(time.Second * 2) 78 | } 79 | } 80 | {{end}} 81 | 82 | -------------------------------------------------------------------------------- /cmd/template/advanced/files/react/tailwind/app.tsx.tmpl: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | function App() { 4 | const [count, setCount] = useState(0) 5 | const [message, setMessage] = useState('') 6 | 7 | const fetchData = () => { 8 | fetch(`http://localhost:${import.meta.env.VITE_PORT}/`) 9 | .then(response => response.text()) 10 | .then(data => setMessage(data)) 11 | .catch(error => console.error('Error fetching data:', error)) 12 | } 13 | 14 | return ( 15 |
16 |
17 |
18 |

19 | Welcome to Vite + React 20 |

21 |

22 | Get started by editing src/App.tsx 23 |

24 |
25 | 26 |
27 |
28 | 34 | 35 | 41 | 42 | {message && ( 43 |
44 |

Server Response:

45 |

{message}

46 |
47 | )} 48 |
49 |
50 | 51 |
52 | Built with Vite, React, and Tailwind CSS 53 |
54 |
55 |
56 | ) 57 | } 58 | 59 | export default App -------------------------------------------------------------------------------- /docs/docs/creating-project/makefile.md: -------------------------------------------------------------------------------- 1 | ## Makefile Project Management 2 | 3 | Makefile is designed for building, running, and testing a Go project. It includes support for advanced options like HTMX and Tailwind CSS, and handles OS-specific operations for Unix-based systems (Linux/macOS) and Windows. 4 | 5 | ## Targets 6 | 7 | ***`all`*** 8 | 9 | The default target that builds and test the application by running the `build` and `test` target. 10 | 11 | ***`templ-install`*** 12 | 13 | This target installs the Go-based templating tool, `templ`, if it is not already installed. It supports: 14 | 15 | - **Unix-based systems**: Prompts the user to install `templ` if it is missing. 16 | - **Windows**: Uses PowerShell to check for and install `templ`. 17 | 18 | ***`tailwind-install`*** 19 | 20 | This target downloads and sets up `tailwindcss`, depending on the user's operating system: 21 | 22 | - **Linux**: Downloads the Linux binary. 23 | - **macOS**: Downloads the macOS binary. 24 | - **Windows**: Uses PowerShell to download the Windows executable. 25 | 26 | ***`build`*** 27 | 28 | Builds the Go application and generates assets with `templ` and `tailwind`, if the corresponding advanced options are enabled: 29 | 30 | - Uses `templ` to generate templates. 31 | - Runs `tailwindcss` to compile CSS. 32 | 33 | ***`run`*** 34 | 35 | Runs the Go application by executing the `cmd/api/main.go` file and npm install with run dev if React flag is used. 36 | 37 | ***`docker-run`*** and ***`docker-down`*** 38 | 39 | These targets manage a database container: 40 | 41 | - **Unix-based systems**: Tries Docker Compose V2 first, falls back to V1 if needed. 42 | - **Windows**: Uses Docker Compose without version fallback. 43 | 44 | ***`test`*** 45 | 46 | Runs unit tests for the application using `go test`. 47 | 48 | ***`itest`*** 49 | 50 | Runs integration tests if a database, with the exception of SQLite, is used. 51 | 52 | ***`clean`*** 53 | 54 | Removes the compiled binary (`main` or `main.exe` depending on the OS). 55 | 56 | ***`watch`*** 57 | 58 | Enables live reload for the project using the `air` tool: 59 | 60 | - **Unix-based systems**: Checks if `air` is installed and prompts for installation if missing. 61 | - **Windows**: Uses PowerShell to manage `air` installation and execution. 62 | 63 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/echo.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | {{if .AdvancedOptions.websocket}} 6 | "log" 7 | "fmt" 8 | "time" 9 | {{end}} 10 | 11 | "github.com/labstack/echo/v4" 12 | "github.com/labstack/echo/v4/middleware" 13 | {{.AdvancedTemplates.TemplateImports}} 14 | ) 15 | func (s *Server) RegisterRoutes() http.Handler { 16 | e := echo.New() 17 | e.Use(middleware.Logger()) 18 | e.Use(middleware.Recover()) 19 | 20 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 21 | AllowOrigins: []string{"https://*", "http://*"}, 22 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, 23 | AllowHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, 24 | AllowCredentials: true, 25 | MaxAge: 300, 26 | })) 27 | 28 | {{.AdvancedTemplates.TemplateRoutes}} 29 | 30 | e.GET("/", s.HelloWorldHandler) 31 | {{if ne .DBDriver "none"}} 32 | e.GET("/health", s.healthHandler) 33 | {{end}} 34 | {{if .AdvancedOptions.websocket}} 35 | e.GET("/websocket", s.websocketHandler) 36 | {{end}} 37 | 38 | return e 39 | } 40 | 41 | func (s *Server) HelloWorldHandler(c echo.Context) error { 42 | resp := map[string]string{ 43 | "message": "Hello World", 44 | } 45 | 46 | return c.JSON(http.StatusOK, resp) 47 | } 48 | 49 | {{if ne .DBDriver "none"}} 50 | func (s *Server) healthHandler(c echo.Context) error { 51 | return c.JSON(http.StatusOK, s.db.Health()) 52 | } 53 | {{end}} 54 | 55 | {{if .AdvancedOptions.websocket}} 56 | func (s *Server) websocketHandler(c echo.Context) error { 57 | w := c.Response().Writer 58 | r := c.Request() 59 | socket, err := websocket.Accept(w, r, nil) 60 | 61 | if err != nil { 62 | log.Printf("could not open websocket: %v", err) 63 | _, _ = w.Write([]byte("could not open websocket")) 64 | w.WriteHeader(http.StatusInternalServerError) 65 | return nil 66 | } 67 | 68 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 69 | 70 | ctx := r.Context() 71 | socketCtx := socket.CloseRead(ctx) 72 | 73 | for { 74 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 75 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 76 | if err != nil { 77 | break 78 | } 79 | time.Sleep(time.Second * 2) 80 | } 81 | return nil 82 | } 83 | {{end}} 84 | 85 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/tests/mysql.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | "time" 8 | 9 | "github.com/testcontainers/testcontainers-go" 10 | "github.com/testcontainers/testcontainers-go/modules/mysql" 11 | "github.com/testcontainers/testcontainers-go/wait" 12 | ) 13 | 14 | func mustStartMySQLContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) { 15 | var ( 16 | dbName = "database" 17 | dbPwd = "password" 18 | dbUser = "user" 19 | ) 20 | 21 | dbContainer, err := mysql.Run(context.Background(), 22 | "mysql:8.0.36", 23 | mysql.WithDatabase(dbName), 24 | mysql.WithUsername(dbUser), 25 | mysql.WithPassword(dbPwd), 26 | testcontainers.WithWaitStrategy(wait.ForLog("port: 3306 MySQL Community Server - GPL").WithStartupTimeout(30*time.Second)), 27 | ) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | dbname = dbName 33 | password = dbPwd 34 | username = dbUser 35 | 36 | dbHost, err := dbContainer.Host(context.Background()) 37 | if err != nil { 38 | return dbContainer.Terminate, err 39 | } 40 | 41 | dbPort, err := dbContainer.MappedPort(context.Background(), "3306/tcp") 42 | if err != nil { 43 | return dbContainer.Terminate, err 44 | } 45 | 46 | host = dbHost 47 | port = dbPort.Port() 48 | 49 | return dbContainer.Terminate, err 50 | } 51 | 52 | func TestMain(m *testing.M) { 53 | teardown, err := mustStartMySQLContainer() 54 | if err != nil { 55 | log.Fatalf("could not start mysql container: %v", err) 56 | } 57 | 58 | m.Run() 59 | 60 | if teardown != nil && teardown(context.Background()) != nil { 61 | log.Fatalf("could not teardown mysql container: %v", err) 62 | } 63 | } 64 | 65 | func TestNew(t *testing.T) { 66 | srv := New() 67 | if srv == nil { 68 | t.Fatal("New() returned nil") 69 | } 70 | } 71 | 72 | func TestHealth(t *testing.T) { 73 | srv := New() 74 | 75 | stats := srv.Health() 76 | 77 | if stats["status"] != "up" { 78 | t.Fatalf("expected status to be up, got %s", stats["status"]) 79 | } 80 | 81 | if _, ok := stats["error"]; ok { 82 | t.Fatalf("expected error not to be present") 83 | } 84 | 85 | if stats["message"] != "It's healthy" { 86 | t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) 87 | } 88 | } 89 | 90 | func TestClose(t *testing.T) { 91 | srv := New() 92 | 93 | if srv.Close() != nil { 94 | t.Fatalf("expected Close() to return nil") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/chi.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | {{if .AdvancedOptions.websocket}} 8 | "fmt" 9 | "time" 10 | {{end}} 11 | 12 | "github.com/go-chi/chi/v5" 13 | "github.com/go-chi/chi/v5/middleware" 14 | "github.com/go-chi/cors" 15 | {{.AdvancedTemplates.TemplateImports}} 16 | 17 | ) 18 | 19 | func (s *Server) RegisterRoutes() http.Handler { 20 | r := chi.NewRouter() 21 | r.Use(middleware.Logger) 22 | 23 | r.Use(cors.Handler(cors.Options{ 24 | AllowedOrigins: []string{"https://*", "http://*"}, 25 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, 26 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, 27 | AllowCredentials: true, 28 | MaxAge: 300, 29 | })) 30 | 31 | r.Get("/", s.HelloWorldHandler) 32 | {{if ne .DBDriver "none"}} 33 | r.Get("/health", s.healthHandler) 34 | {{end}} 35 | {{if .AdvancedOptions.websocket}} 36 | r.Get("/websocket", s.websocketHandler) 37 | {{end}} 38 | {{.AdvancedTemplates.TemplateRoutes}} 39 | 40 | return r 41 | } 42 | 43 | func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) { 44 | resp := make(map[string]string) 45 | resp["message"] = "Hello World" 46 | 47 | jsonResp, err := json.Marshal(resp) 48 | if err != nil { 49 | log.Fatalf("error handling JSON marshal. Err: %v", err) 50 | } 51 | 52 | _, _ = w.Write(jsonResp) 53 | } 54 | 55 | {{if ne .DBDriver "none"}} 56 | func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 57 | jsonResp, _ := json.Marshal(s.db.Health()) 58 | _, _ = w.Write(jsonResp) 59 | } 60 | {{end}} 61 | 62 | {{if .AdvancedOptions.websocket}} 63 | func (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) { 64 | socket, err := websocket.Accept(w, r, nil) 65 | 66 | if err != nil { 67 | log.Printf("could not open websocket: %v", err) 68 | _, _ = w.Write([]byte("could not open websocket")) 69 | w.WriteHeader(http.StatusInternalServerError) 70 | return 71 | } 72 | 73 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 74 | 75 | ctx := r.Context() 76 | socketCtx := socket.CloseRead(ctx) 77 | 78 | for { 79 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 80 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 81 | if err != nil { 82 | break 83 | } 84 | time.Sleep(time.Second * 2) 85 | } 86 | } 87 | {{end}} 88 | 89 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/htmx-templ.md: -------------------------------------------------------------------------------- 1 | The WEB directory contains the web-related components and assets for the project. It leverages [htmx](https://github.com/bigskysoftware/htmx) and [templ](https://github.com/a-h/templ) in Go for dynamic web content generation. 2 | 3 | ## Structure 4 | 5 | ``` 6 | web/ 7 | │ 8 | │ 9 | ├── assets/ 10 | │ └── js/ 11 | │ └── htmx.min.js # htmx library for dynamic HTML content 12 | │ 13 | ├── base.templ # Base template for HTML structure 14 | ├── base_templ.go # Generated Go code for base template 15 | ├── efs.go # Embeds static files into the Go binary 16 | │ 17 | ├── hello.go # Handler for the Hello Web functionality 18 | ├── hello.templ # Template for rendering the Hello form and post data 19 | └── hello_templ.go # Generated Go code for hello template 20 | ``` 21 | 22 | ## Usage 23 | 24 | - **Navigate to Project Directory:** 25 | ```bash 26 | cd my-project 27 | ``` 28 | 29 | - **Install Templ CLI:** 30 | ```bash 31 | go install github.com/a-h/templ/cmd/templ@latest 32 | ``` 33 | 34 | - **Generate Templ Function Files:** 35 | ```bash 36 | templ generate 37 | ``` 38 | 39 | - **Start Server:** 40 | ```bash 41 | make run 42 | ``` 43 | 44 | ## Makefile 45 | 46 | Automates templ with Makefile entries, which are automatically created if the htmx advanced flag is used. 47 | It detects if templ is installed or not and generates templates with the make build command. 48 | Both Windows and Unix-like OS are supported. 49 | 50 | ```bash 51 | all: build 52 | 53 | templ-install: 54 | @if ! command -v templ > /dev/null; then \ 55 | read -p "Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] " choice; \ 56 | if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ 57 | go install github.com/a-h/templ/cmd/templ@latest; \ 58 | if [ ! -x "$$(command -v templ)" ]; then \ 59 | echo "templ installation failed. Exiting..."; \ 60 | exit 1; \ 61 | fi; \ 62 | else \ 63 | echo "You chose not to install templ. Exiting..."; \ 64 | exit 1; \ 65 | fi; \ 66 | fi 67 | 68 | build: templ-install 69 | @echo "Building..." 70 | @templ generate 71 | @go build -o main cmd/api/main.go 72 | ``` 73 | 74 | ## Templating 75 | 76 | Templates are generated using the `templ generate` command after project creation. These templates are then compiled into Go code for efficient execution. 77 | 78 | You can test HTMX functionality on `localhost:PORT/web` endpoint. 79 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | ### Site metadata ### 2 | 3 | site_name: Go-Blueprint Docs 4 | site_description: Official documentation for Go-Blueprint project 5 | site_url: https://docs.go-blueprint.dev/ 6 | 7 | repo_url: https://github.com/Melkeydev/go-blueprint 8 | edit_uri: edit/main/docs/docs 9 | 10 | ### Build settings ### 11 | 12 | theme: 13 | name: material 14 | custom_dir: custom_theme/ 15 | theme: 16 | features: 17 | - navigation.instant 18 | - navigation.sections 19 | - navigation.footer 20 | - toc.flow 21 | palette: 22 | - scheme: default 23 | toggle: 24 | icon: material/brightness-7 25 | name: Switch to dark mode 26 | - scheme: slate 27 | toggle: 28 | icon: material/brightness-4 29 | name: Switch to light mode 30 | 31 | nav: 32 | - Home: index.md 33 | - Installation: installation.md 34 | - Blueprint UI: blueprint-ui.md 35 | - Project creation & default config: 36 | - Project init: creating-project/project-init.md 37 | - Makefile: creating-project/makefile.md 38 | - Air: creating-project/air.md 39 | - Blueprint Core: 40 | - Frameworks: blueprint-core/frameworks.md 41 | - DB Drivers: blueprint-core/db-drivers.md 42 | - Advanced Flag: 43 | - AF Usage: advanced-flag/advanced-flag.md 44 | - HTMX and Templ: advanced-flag/htmx-templ.md 45 | - Tailwind CSS: advanced-flag/tailwind.md 46 | - GoReleaser & GoTest CI: advanced-flag/goreleaser.md 47 | - Websocket: advanced-flag/websocket.md 48 | - Docker: advanced-flag/docker.md 49 | - React & Vite (TypeScript): advanced-flag/react-vite.md 50 | - Testing endpoints: 51 | - Server: endpoints-test/server.md 52 | - DB Health Endpoints: 53 | - SQL DBs: endpoints-test/sql.md 54 | - Redis: endpoints-test/redis.md 55 | - MongoDB: endpoints-test/mongo.md 56 | - ScyllaDB: endpoints-test/scylladb.md 57 | - Websocket: endpoints-test/websocket.md 58 | - Web Endpoint: endpoints-test/web.md 59 | 60 | extra: 61 | social: 62 | - icon: fontawesome/brands/discord 63 | link: https://discord.com/invite/HHZMSCu 64 | name: Discord 65 | - icon: fontawesome/brands/twitch 66 | link: https://www.twitch.tv/melkey 67 | name: Twitch 68 | - icon: fontawesome/brands/youtube 69 | link: https://www.youtube.com/@MelkeyDev 70 | name: YouTube 71 | - icon: fontawesome/brands/twitter 72 | link: https://x.com/MelkeyDev 73 | name: Twitter 74 | generator: false 75 | 76 | copyright: Copyright © 2025 Melkey 77 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Go blueprint version 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "runtime/debug" 9 | "time" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // GoBlueprintVersion is the version of the cli to be overwritten by goreleaser in the CI run with the version of the release in github 15 | var GoBlueprintVersion string 16 | 17 | // Go Blueprint needs to be built in a specific way to provide useful version information. 18 | // First we try to get the version from ldflags embedded into GoBlueprintVersion. 19 | // Then we try to get the version from from the go.mod build info. 20 | // If Go Blueprint is installed with a specific version tag or using @latest then that version will be included in bi.Main.Version. 21 | // This won't give any version info when running 'go install' with the source code locally. 22 | // Finally we try to get the version from other embedded VCS info. 23 | func getGoBlueprintVersion() string { 24 | noVersionAvailable := "No version info available for this build, run 'go-blueprint help version' for additional info" 25 | 26 | if len(GoBlueprintVersion) != 0 { 27 | return GoBlueprintVersion 28 | } 29 | 30 | bi, ok := debug.ReadBuildInfo() 31 | if !ok { 32 | return noVersionAvailable 33 | } 34 | 35 | // If no main version is available, Go defaults it to (devel) 36 | if bi.Main.Version != "(devel)" { 37 | return bi.Main.Version 38 | } 39 | 40 | var vcsRevision string 41 | var vcsTime time.Time 42 | for _, setting := range bi.Settings { 43 | switch setting.Key { 44 | case "vcs.revision": 45 | vcsRevision = setting.Value 46 | case "vcs.time": 47 | vcsTime, _ = time.Parse(time.RFC3339, setting.Value) 48 | } 49 | } 50 | 51 | if vcsRevision != "" { 52 | return fmt.Sprintf("%s, (%s)", vcsRevision, vcsTime) 53 | } 54 | 55 | return noVersionAvailable 56 | } 57 | 58 | // versionCmd represents the version command 59 | var versionCmd = &cobra.Command{ 60 | Use: "version", 61 | Short: "Display application version information.", 62 | Long: ` 63 | The version command provides information about the application's version. 64 | 65 | Go Blueprint requires version information to be embedded at compile time. 66 | For detailed version information, Go Blueprint needs to be built as specified in the README installation instructions. 67 | If Go Blueprint is built within a version control repository and other version info isn't available, 68 | the revision hash will be used instead. 69 | `, 70 | Run: func(cmd *cobra.Command, args []string) { 71 | version := getGoBlueprintVersion() 72 | fmt.Printf("Go Blueprint CLI version: %v\n", version) 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /docs/docs/creating-project/air.md: -------------------------------------------------------------------------------- 1 | ## Air - Live Reloading Utility 2 | 3 | [Air](https://github.com/cosmtrek/air) is a live-reloading utility designed to enhance the development experience by automatically rebuilding and restarting your Go application whenever changes are detected in the source code. 4 | 5 | The Makefile provided in the project repository includes a command make watch, which triggers Air to monitor file changes and initiate rebuilds and restarts as necessary. Additionally, if Air is not installed on your machine, the Makefile provides an option to install it automatically. 6 | 7 | Air's `.air.toml` configuration file allows customization of various aspects of its behavior. 8 | 9 | ## Live Preview 10 | 11 | ```bash 12 | make watch 13 | 14 | __ _ ___ 15 | / /\ | | | |_) 16 | /_/--\ |_| |_| \_ v1.51.0, built with Go go1.22.0 17 | 18 | mkdir /home/ujstor/code/blueprint-version-test/ws-test4/tmp 19 | watching . 20 | watching cmd 21 | watching cmd/api 22 | watching cmd/web 23 | watching cmd/web/assets 24 | watching cmd/web/assets/js 25 | watching internal 26 | watching internal/database 27 | watching internal/server 28 | watching tests 29 | !exclude tmp 30 | building... 31 | make[1]: Entering directory '/home/ujstor/code/blueprint-version-test/ws-test4' 32 | Building... 33 | Processing path: /home/ujstor/code/blueprint-version-test/ws-test4 34 | Generated code for "/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/base.templ" in 914.556µs 35 | Generated code for "/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/hello.templ" in 963.157µs 36 | Generated code for 2 templates with 0 errors in 1.274392ms 37 | make[1]: Leaving directory '/home/ujstor/code/blueprint-version-test/ws-test4' 38 | running... 39 | internal/server/routes.go has changed 40 | building... 41 | make[1]: Entering directory '/home/ujstor/code/blueprint-version-test/ws-test4' 42 | Building... 43 | Processing path: /home/ujstor/code/blueprint-version-test/ws-test4 44 | Generated code for "/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/base.templ" in 907.426µs 45 | Generated code for "/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/hello.templ" in 1.16142ms 46 | Generated code for 2 templates with 0 errors in 1.527556ms 47 | make[1]: Leaving directory '/home/ujstor/code/blueprint-version-test/ws-test4' 48 | running... 49 | ``` 50 | 51 | Integrating Air into your development workflow alongside the provided Makefile enables a smooth and efficient process for building, testing, and running your Go applications. With automatic live-reloading, you can focus more on coding and less on manual build and restart steps. 52 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/tests/postgres.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | "time" 8 | 9 | "github.com/testcontainers/testcontainers-go" 10 | "github.com/testcontainers/testcontainers-go/modules/postgres" 11 | "github.com/testcontainers/testcontainers-go/wait" 12 | ) 13 | 14 | func mustStartPostgresContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) { 15 | var ( 16 | dbName = "database" 17 | dbPwd = "password" 18 | dbUser = "user" 19 | ) 20 | 21 | dbContainer, err := postgres.Run( 22 | context.Background(), 23 | "postgres:latest", 24 | postgres.WithDatabase(dbName), 25 | postgres.WithUsername(dbUser), 26 | postgres.WithPassword(dbPwd), 27 | testcontainers.WithWaitStrategy( 28 | wait.ForLog("database system is ready to accept connections"). 29 | WithOccurrence(2). 30 | WithStartupTimeout(5*time.Second)), 31 | ) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | database = dbName 37 | password = dbPwd 38 | username = dbUser 39 | 40 | dbHost, err := dbContainer.Host(context.Background()) 41 | if err != nil { 42 | return dbContainer.Terminate, err 43 | } 44 | 45 | dbPort, err := dbContainer.MappedPort(context.Background(), "5432/tcp") 46 | if err != nil { 47 | return dbContainer.Terminate, err 48 | } 49 | 50 | host = dbHost 51 | port = dbPort.Port() 52 | 53 | return dbContainer.Terminate, err 54 | } 55 | 56 | func TestMain(m *testing.M) { 57 | teardown, err := mustStartPostgresContainer() 58 | if err != nil { 59 | log.Fatalf("could not start postgres container: %v", err) 60 | } 61 | 62 | m.Run() 63 | 64 | if teardown != nil && teardown(context.Background()) != nil { 65 | log.Fatalf("could not teardown postgres container: %v", err) 66 | } 67 | } 68 | 69 | func TestNew(t *testing.T) { 70 | srv := New() 71 | if srv == nil { 72 | t.Fatal("New() returned nil") 73 | } 74 | } 75 | 76 | func TestHealth(t *testing.T) { 77 | srv := New() 78 | 79 | stats := srv.Health() 80 | 81 | if stats["status"] != "up" { 82 | t.Fatalf("expected status to be up, got %s", stats["status"]) 83 | } 84 | 85 | if _, ok := stats["error"]; ok { 86 | t.Fatalf("expected error not to be present") 87 | } 88 | 89 | if stats["message"] != "It's healthy" { 90 | t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) 91 | } 92 | } 93 | 94 | func TestClose(t *testing.T) { 95 | srv := New() 96 | 97 | if srv.Close() != nil { 98 | t.Fatalf("expected Close() to return nil") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/tailwind.md: -------------------------------------------------------------------------------- 1 | Tailwind is closely coupled with the advanced HTMX flag, and HTMX will be automatically used if you select Tailwind in your project. 2 | 3 | We do not introduce outside dependencies automatically, and you need compile output.css (file is empty by default) with the Tailwind CLI tool. 4 | 5 | The project tree would look like this: 6 | 7 | ```bash 8 | / (Root) 9 | ├── cmd/ 10 | │ ├── api/ 11 | │ │ └── main.go 12 | │ └── web/ 13 | │ ├── styles/ 14 | │ │ └── input.css 15 | │ ├── assets/ 16 | │ │ ├── css/ 17 | │ │ │ └── output.css 18 | │ │ └── js/ 19 | │ │ └── htmx.min.js 20 | │ ├── base.templ 21 | │ ├── base_templ.go 22 | │ ├── efs.go 23 | │ ├── hello.go 24 | │ ├── hello.templ 25 | │ └── hello_templ.go 26 | ├── internal/ 27 | │ └── server/ 28 | │ ├── routes.go 29 | │ ├── routes_test.go 30 | │ └── server.go 31 | ├── go.mod 32 | ├── go.sum 33 | ├── Makefile 34 | └── README.md 35 | ``` 36 | 37 | ## Standalone Tailwind CLI 38 | 39 | The idea is to avoid using Node.js and npm to build output.css. 40 | 41 | The Makefile will have entries for downloading and compiling CSS. It will automatically detect the OS and download the latest release from the [official repository](https://github.com/tailwindlabs/tailwindcss/releases). 42 | 43 | ## Linux Makefile Example 44 | 45 | ```bash 46 | all: build 47 | templ-install: 48 | @if ! command -v templ > /dev/null; then \ 49 | read -p "Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] " choice; \ 50 | if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ 51 | go install github.com/a-h/templ/cmd/templ@latest; \ 52 | if [ ! -x "$$(command -v templ)" ]; then \ 53 | echo "templ installation failed. Exiting..."; \ 54 | exit 1; \ 55 | fi; \ 56 | else \ 57 | echo "You chose not to install templ. Exiting..."; \ 58 | exit 1; \ 59 | fi; \ 60 | fi 61 | 62 | tailwind-install: 63 | @if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss; fi 64 | @chmod +x tailwindcss 65 | 66 | build: tailwind-install templ-install 67 | @echo "Building..." 68 | @templ generate 69 | @./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css 70 | @go build -o main cmd/api/main.go 71 | ``` 72 | 73 | ## Use Tailwind CSS in your project 74 | 75 | By default, simple CSS examples are included in the codebase. 76 | Update base.templ and hello.templ, then rerun templ generate to see the changes at the `localhost:PORT/web` endpoint. 77 | 78 | ![Tailwind](../public/tailwind.png) 79 | -------------------------------------------------------------------------------- /docs/docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - toc 4 | --- 5 | 6 | Go-Blueprint provides a convenient CLI tool to effortlessly set up your Go projects. Follow the steps below to install the tool on your system. 7 | 8 | ## Binary Installation 9 | 10 | To install the Go-Blueprint CLI tool as a binary, run the following command: 11 | 12 | ```sh 13 | go install github.com/melkeydev/go-blueprint@latest 14 | ``` 15 | 16 | This command installs the Go-Blueprint binary, automatically binding it to your `$GOPATH`. 17 | 18 | > If you’re using Zsh, you’ll need to add it manually to `~/.zshrc`. 19 | 20 | > After running the installation command, you need to update your `PATH` environment variable. To do this, you need to find out the correct `GOPATH` for your system. You can do this by running the following command: 21 | > Check your `GOPATH` 22 | > 23 | > ``` 24 | > go env GOPATH 25 | > ``` 26 | > 27 | > Then, add the following line to your `~/.zshrc` file: 28 | > 29 | > ``` 30 | > GOPATH=$HOME/go PATH=$PATH:/usr/local/go/bin:$GOPATH/bin 31 | > ``` 32 | > 33 | > Save the changes to your `~/.zshrc` file by running the following command: 34 | > 35 | > ``` 36 | > source ~/.zshrc 37 | > ``` 38 | 39 | ## NPM Install 40 | 41 | If you prefer using Node.js package manager, you can install Go-Blueprint via NPM. This method is convenient for developers who are already working in JavaScript/Node.js environments and want to integrate Go-Blueprint into their existing workflow. 42 | 43 | ```bash 44 | npm install -g @melkeydev/go-blueprint 45 | ``` 46 | 47 | The `-g` flag installs Go-Blueprint globally, making it accessible from any directory on your system. 48 | 49 | ## Homebrew Install 50 | 51 | For macOS and Linux users, Homebrew provides a simple way to install Go-Blueprint. Homebrew automatically handles dependencies and keeps the tool updated through its package management system. 52 | 53 | ```bash 54 | brew install go-blueprint 55 | ``` 56 | 57 | After installation via Homebrew, Go-Blueprint will be automatically added to your PATH, making it immediately available in your terminal. 58 | 59 | ## Building and Installing from Source 60 | 61 | If you prefer to build and install Go-Blueprint directly from the source code, you can follow these steps: 62 | 63 | Clone the Go-Blueprint repository from GitHub: 64 | 65 | ```sh 66 | git clone https://github.com/melkeydev/go-blueprint 67 | ``` 68 | 69 | Build the Go-Blueprint binary: 70 | 71 | ```sh 72 | go build 73 | ``` 74 | 75 | Install in your `$PATH` to make it accessible system-wide: 76 | 77 | ```sh 78 | go install 79 | ``` 80 | 81 | Verify the installation by running: 82 | 83 | ```sh 84 | go-blueprint version 85 | ``` 86 | 87 | This should display the version information of the installed Go-Blueprint. 88 | 89 | Now you have successfully built and installed Go-Blueprint from the source code. 90 | -------------------------------------------------------------------------------- /docs/docs/creating-project/project-init.md: -------------------------------------------------------------------------------- 1 | ## Creating a Project 2 | 3 | After installing the Go-Blueprint CLI tool, you can create a new project with the default settings by running the following command: 4 | 5 | ```bash 6 | go-blueprint create 7 | ``` 8 | 9 | This command will interactively guide you through the project setup process, allowing you to choose the project name, framework, and database driver. 10 | 11 | ![BlueprintInteractive](../public/blueprint_1.png) 12 | 13 | ## Using Flags for Non-Interactive Setup 14 | 15 | For a non-interactive setup, you can use flags to provide the necessary information during project creation. Here's an example: 16 | 17 | ``` 18 | go-blueprint create --name my-project --framework gin --driver postgres --git commit 19 | ``` 20 | 21 | In this example: 22 | 23 | - `--name`: Specifies the name of the project (replace "my-project" with your desired project name). 24 | - `--framework`: Specifies the Go framework to be used (e.g., "gin"). 25 | - `--driver`: Specifies the database driver to be integrated (e.g., "postgres"). 26 | - `--git`: Specifies the git configuration option of the project (e.g., "commit"). 27 | 28 | Customize the flags according to your project requirements. 29 | 30 | ## Advanced Flag 31 | 32 | By including the `--advanced` flag, users can choose one or all of the advanced features, HTMX, GitHub Actions for CI/CD, Websocket, Docker and TailwindCSS support, during the project creation process. The flag enhances the simplicity of Blueprint while offering flexibility for users who require additional functionality. 33 | 34 | ```bash 35 | go-blueprint create --advanced 36 | ``` 37 | 38 | To recreate the project using the same configuration semi-interactively, use the following command: 39 | ```bash 40 | go-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced 41 | ``` 42 | This approach opens interactive mode only for advanced features, which allows you to choose the one or combination of available features. 43 | 44 | ![AdvancedFlag](../public/blueprint_advanced.png) 45 | 46 | ## Non-Interactive Setup 47 | 48 | Advanced features can be enabled using the `--feature` flag along with the `--advanced` flag: 49 | 50 | HTMX: 51 | ```bash 52 | go-blueprint create --advanced --feature htmx 53 | ``` 54 | 55 | CI/CD workflow: 56 | ```bash 57 | go-blueprint create --advanced --feature githubaction 58 | ``` 59 | 60 | Websocket: 61 | ```bash 62 | go-blueprint create --advanced --feature websocket 63 | ``` 64 | TailwindCSS: 65 | ```bash 66 | go-blueprint create --advanced --feature tailwind 67 | ``` 68 | Docker: 69 | ```bash 70 | go-blueprint create --advanced --feature docker 71 | ``` 72 | 73 | Or all features at once: 74 | ```bash 75 | go-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker 76 | ``` 77 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/gorilla.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | {{if .AdvancedOptions.websocket}} 8 | "fmt" 9 | "time" 10 | {{end}} 11 | 12 | "github.com/gorilla/mux" 13 | {{.AdvancedTemplates.TemplateImports}} 14 | ) 15 | 16 | func (s *Server) RegisterRoutes() http.Handler { 17 | r := mux.NewRouter() 18 | 19 | // Apply CORS middleware 20 | r.Use(s.corsMiddleware) 21 | 22 | r.HandleFunc("/", s.HelloWorldHandler) 23 | {{if ne .DBDriver "none"}} 24 | r.HandleFunc("/health", s.healthHandler) 25 | {{end}} 26 | {{if .AdvancedOptions.websocket}} 27 | r.HandleFunc("/websocket", s.websocketHandler) 28 | {{end}} 29 | 30 | {{.AdvancedTemplates.TemplateRoutes}} 31 | 32 | return r 33 | } 34 | 35 | // CORS middleware 36 | func (s *Server) corsMiddleware(next http.Handler) http.Handler { 37 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 | // CORS Headers 39 | w.Header().Set("Access-Control-Allow-Origin", "*") // Wildcard allows all origins 40 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") 41 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type") 42 | w.Header().Set("Access-Control-Allow-Credentials", "false") // Credentials not allowed with wildcard origins 43 | 44 | // Handle preflight OPTIONS requests 45 | if r.Method == http.MethodOptions { 46 | w.WriteHeader(http.StatusNoContent) 47 | return 48 | } 49 | 50 | next.ServeHTTP(w, r) 51 | }) 52 | } 53 | 54 | func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) { 55 | resp := make(map[string]string) 56 | resp["message"] = "Hello World" 57 | 58 | jsonResp, err := json.Marshal(resp) 59 | if err != nil { 60 | log.Fatalf("error handling JSON marshal. Err: %v", err) 61 | } 62 | 63 | _, _ = w.Write(jsonResp) 64 | } 65 | 66 | {{if ne .DBDriver "none"}} 67 | func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 68 | jsonResp, err := json.Marshal(s.db.Health()) 69 | 70 | if err != nil { 71 | log.Fatalf("error handling JSON marshal. Err: %v", err) 72 | } 73 | 74 | _, _ = w.Write(jsonResp) 75 | } 76 | {{end}} 77 | 78 | {{if .AdvancedOptions.websocket}} 79 | func (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) { 80 | socket, err := websocket.Accept(w, r, nil) 81 | 82 | if err != nil { 83 | log.Printf("could not open websocket: %v", err) 84 | _, _ = w.Write([]byte("could not open websocket")) 85 | w.WriteHeader(http.StatusInternalServerError) 86 | return 87 | } 88 | 89 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 90 | 91 | ctx := r.Context() 92 | socketCtx := socket.CloseRead(ctx) 93 | 94 | for { 95 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 96 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 97 | if err != nil { 98 | break 99 | } 100 | time.Sleep(time.Second * 2) 101 | } 102 | } 103 | {{end}} 104 | 105 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/http_router.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | {{if .AdvancedOptions.websocket}} 8 | "fmt" 9 | "time" 10 | {{end}} 11 | 12 | "github.com/julienschmidt/httprouter" 13 | {{.AdvancedTemplates.TemplateImports}} 14 | ) 15 | 16 | func (s *Server) RegisterRoutes() http.Handler { 17 | r := httprouter.New() 18 | 19 | // Wrap all routes with CORS middleware 20 | corsWrapper := s.corsMiddleware(r) 21 | 22 | r.HandlerFunc(http.MethodGet, "/", s.HelloWorldHandler) 23 | {{if ne .DBDriver "none"}} 24 | r.HandlerFunc(http.MethodGet, "/health", s.healthHandler) 25 | {{end}} 26 | {{if .AdvancedOptions.websocket}} 27 | r.HandlerFunc(http.MethodGet, "/websocket", s.websocketHandler) 28 | {{end}} 29 | {{.AdvancedTemplates.TemplateRoutes}} 30 | 31 | return corsWrapper 32 | } 33 | 34 | // CORS middleware 35 | func (s *Server) corsMiddleware(next http.Handler) http.Handler { 36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | // CORS headers 38 | w.Header().Set("Access-Control-Allow-Origin", "*") // Use "*" for all origins, or replace with specific origins 39 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") 40 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, X-CSRF-Token") 41 | w.Header().Set("Access-Control-Allow-Credentials", "false") // Set to "true" if credentials are needed 42 | 43 | // Handle preflight OPTIONS requests 44 | if r.Method == http.MethodOptions { 45 | w.WriteHeader(http.StatusNoContent) 46 | return 47 | } 48 | 49 | next.ServeHTTP(w, r) 50 | }) 51 | } 52 | 53 | func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) { 54 | resp := make(map[string]string) 55 | resp["message"] = "Hello World" 56 | 57 | jsonResp, err := json.Marshal(resp) 58 | if err != nil { 59 | log.Fatalf("error handling JSON marshal. Err: %v", err) 60 | } 61 | 62 | _, _ = w.Write(jsonResp) 63 | } 64 | 65 | {{if ne .DBDriver "none"}} 66 | func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 67 | jsonResp, err := json.Marshal(s.db.Health()) 68 | 69 | if err != nil { 70 | log.Fatalf("error handling JSON marshal. Err: %v", err) 71 | } 72 | 73 | _, _ = w.Write(jsonResp) 74 | } 75 | {{end}} 76 | 77 | {{if .AdvancedOptions.websocket}} 78 | func (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) { 79 | socket, err := websocket.Accept(w, r, nil) 80 | 81 | if err != nil { 82 | log.Printf("could not open websocket: %v", err) 83 | _, _ = w.Write([]byte("could not open websocket")) 84 | w.WriteHeader(http.StatusInternalServerError) 85 | return 86 | } 87 | 88 | defer socket.Close(websocket.StatusGoingAway, "server closing websocket") 89 | 90 | ctx := r.Context() 91 | socketCtx := socket.CloseRead(ctx) 92 | 93 | for { 94 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 95 | err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)) 96 | if err != nil { 97 | break 98 | } 99 | time.Sleep(time.Second * 2) 100 | } 101 | } 102 | {{end}} 103 | 104 | -------------------------------------------------------------------------------- /.github/workflows/update-htmx-version.yml: -------------------------------------------------------------------------------- 1 | name: Check for new htmx release 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * Sun' 6 | 7 | jobs: 8 | check_release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Get version from file 14 | id: get_version_file 15 | run: | 16 | VERSION_FILE=$(curl -s https://raw.githubusercontent.com/Melkeydev/go-blueprint/main/cmd/template/advanced/files/htmx/htmx.min.js.tmpl | grep version | awk -F'"' '{print "v" $2}') 17 | echo "version file: $VERSION_FILE" 18 | if [[ "$VERSION_FILE" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 19 | echo "version_file=$VERSION_FILE" >> $GITHUB_OUTPUT 20 | else 21 | echo "Invalid VERSION_FILE format: $VERSION_FILE" >&2 22 | exit 1 23 | fi 24 | 25 | - name: Get version from GitHub API 26 | id: get_version_api 27 | run: | 28 | VERSION_API=$(curl -s https://api.github.com/repos/bigskysoftware/htmx/releases/latest | jq -r '.tag_name') 29 | echo "version api: $VERSION_API" 30 | if [[ "$VERSION_API" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 31 | echo "version_api=$VERSION_API" >> $GITHUB_OUTPUT 32 | else 33 | echo "Invalid VERSION_API format: $VERSION_API" >&2 34 | exit 1 35 | fi 36 | 37 | - name: Compare versions 38 | id: compare_versions 39 | run: | 40 | if [ "${{ steps.get_version_api.outputs.version_api }}" != "${{ steps.get_version_file.outputs.version_file }}" ]; then 41 | echo "release_changed=true" >> $GITHUB_OUTPUT 42 | echo "Release changed: true" 43 | else 44 | echo "release_changed=false" >> $GITHUB_OUTPUT 45 | echo "Release changed: false" 46 | fi 47 | 48 | - name: dump latest htmx version 49 | if: steps.compare_versions.outputs.release_changed == 'true' 50 | run: curl -L https://github.com/bigskysoftware/htmx/releases/latest/download/htmx.min.js -o cmd/template/advanced/files/htmx/htmx.min.js 51 | 52 | - name: Prettify code 53 | if: steps.compare_versions.outputs.release_changed == 'true' 54 | run: | 55 | npm install --save-dev --save-exact prettier 56 | npx prettier --write cmd/template/advanced/files/htmx/htmx.min.js 57 | rm -rf node_modules 58 | rm package-lock.json 59 | rm package.json 60 | 61 | - name: Create tmpl after Prettify 62 | if: steps.compare_versions.outputs.release_changed == 'true' 63 | run: mv cmd/template/advanced/files/htmx/htmx.min.js cmd/template/advanced/files/htmx/htmx.min.js.tmpl 64 | 65 | - name: Create Pull Request 66 | if: steps.compare_versions.outputs.release_changed == 'true' 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | commit-message: update htmx version ${{ steps.get_version_api.outputs.version_api }} 70 | title: Update htmx to version ${{ steps.get_version_api.outputs.version_api }} [Bot] 71 | body: New htmx ${{ steps.get_version_api.outputs.version_api }} version is available. This is an automatic PR to update changes. 72 | branch: htmx-version-update 73 | base: main 74 | -------------------------------------------------------------------------------- /cmd/template/framework/files/routes/standard_library.go.tmpl: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | {{if .AdvancedOptions.websocket}} 8 | "fmt" 9 | "time" 10 | {{end}} 11 | 12 | {{.AdvancedTemplates.TemplateImports}} 13 | ) 14 | 15 | func (s *Server) RegisterRoutes() http.Handler { 16 | mux := http.NewServeMux() 17 | 18 | // Register routes 19 | mux.HandleFunc("/", s.HelloWorldHandler) 20 | {{if ne .DBDriver "none"}} 21 | mux.HandleFunc("/health", s.healthHandler) 22 | {{end}} 23 | {{if .AdvancedOptions.websocket}} 24 | mux.HandleFunc("/websocket", s.websocketHandler) 25 | {{end}} 26 | {{.AdvancedTemplates.TemplateRoutes}} 27 | 28 | // Wrap the mux with CORS middleware 29 | return s.corsMiddleware(mux) 30 | } 31 | 32 | func (s *Server) corsMiddleware(next http.Handler) http.Handler { 33 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 | // Set CORS headers 35 | w.Header().Set("Access-Control-Allow-Origin", "*") // Replace "*" with specific origins if needed 36 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") 37 | w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, X-CSRF-Token") 38 | w.Header().Set("Access-Control-Allow-Credentials", "false") // Set to "true" if credentials are required 39 | 40 | // Handle preflight OPTIONS requests 41 | if r.Method == http.MethodOptions { 42 | w.WriteHeader(http.StatusNoContent) 43 | return 44 | } 45 | 46 | // Proceed with the next handler 47 | next.ServeHTTP(w, r) 48 | }) 49 | } 50 | 51 | func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) { 52 | resp := map[string]string{"message": "Hello World"} 53 | jsonResp, err := json.Marshal(resp) 54 | if err != nil { 55 | http.Error(w, "Failed to marshal response", http.StatusInternalServerError) 56 | return 57 | } 58 | w.Header().Set("Content-Type", "application/json") 59 | if _, err := w.Write(jsonResp); err != nil { 60 | log.Printf("Failed to write response: %v", err) 61 | } 62 | } 63 | 64 | {{if ne .DBDriver "none"}} 65 | func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { 66 | resp, err := json.Marshal(s.db.Health()) 67 | if err != nil { 68 | http.Error(w, "Failed to marshal health check response", http.StatusInternalServerError) 69 | return 70 | } 71 | w.Header().Set("Content-Type", "application/json") 72 | if _, err := w.Write(resp); err != nil { 73 | log.Printf("Failed to write response: %v", err) 74 | } 75 | } 76 | {{end}} 77 | 78 | {{if .AdvancedOptions.websocket}} 79 | func (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) { 80 | socket, err := websocket.Accept(w, r, nil) 81 | if err != nil { 82 | http.Error(w, "Failed to open websocket", http.StatusInternalServerError) 83 | return 84 | } 85 | defer socket.Close(websocket.StatusGoingAway, "Server closing websocket") 86 | 87 | ctx := r.Context() 88 | socketCtx := socket.CloseRead(ctx) 89 | 90 | for { 91 | payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano()) 92 | if err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)); err != nil { 93 | log.Printf("Failed to write to socket: %v", err) 94 | break 95 | } 96 | time.Sleep(2 * time.Second) 97 | } 98 | } 99 | {{end}} 100 | 101 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/tests/scylla.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/docker/go-connections/nat" 7 | "github.com/testcontainers/testcontainers-go" 8 | "github.com/testcontainers/testcontainers-go/wait" 9 | "io" 10 | "log" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | const ( 16 | port = nat.Port("19042/tcp") 17 | ) 18 | 19 | func mustStartScyllaDBContainer() (testcontainers.Container, error) { 20 | 21 | // Define the container 22 | image := "scylladb/scylla:6.2" 23 | exposedPorts := []string{"9042/tcp", "19042/tcp"} 24 | commands := []string{ 25 | "--smp=2", 26 | "--memory=1G", 27 | "--developer-mode=1", 28 | "--overprovisioned=1", 29 | } 30 | 31 | req := testcontainers.ContainerRequest{ 32 | FromDockerfile: testcontainers.FromDockerfile{}, 33 | Image: image, 34 | ExposedPorts: exposedPorts, 35 | Cmd: commands, 36 | WaitingFor: wait.ForAll( 37 | wait.ForLog(".*initialization completed.").AsRegexp(), 38 | wait.ForListeningPort(port), 39 | wait.ForExec([]string{"cqlsh", "-e", "SELECT bootstrapped FROM system.local"}).WithResponseMatcher(func(body io.Reader) bool { 40 | data, _ := io.ReadAll(body) 41 | return strings.Contains(string(data), "COMPLETED") 42 | }), 43 | ), 44 | } 45 | 46 | // Start the container 47 | scyllaDBContainer, err := testcontainers.GenericContainer( 48 | context.Background(), testcontainers.GenericContainerRequest{ 49 | ContainerRequest: req, 50 | Started: true, 51 | }) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | mappedPort, err := scyllaDBContainer.MappedPort(context.Background(), "19042/tcp") 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | hosts = fmt.Sprintf("localhost:%v", mappedPort.Port()) 62 | 63 | return scyllaDBContainer, nil 64 | } 65 | 66 | func TestMain(m *testing.M) { 67 | 68 | container, err := mustStartScyllaDBContainer() 69 | if err != nil { 70 | log.Fatalf("could not start scylla container: %v", err) 71 | } 72 | 73 | m.Run() 74 | 75 | err = container.Terminate(context.Background()) 76 | if err != nil { 77 | return 78 | } 79 | 80 | } 81 | 82 | func TestNew(t *testing.T) { 83 | srv := New() 84 | if srv == nil { 85 | t.Fatal("New() returned nil") 86 | } 87 | 88 | err := srv.Close() 89 | if err != nil { 90 | t.Fatalf("expected Close() to return nil") 91 | } 92 | } 93 | 94 | func TestHealth(t *testing.T) { 95 | srv := New() 96 | 97 | stats := srv.Health() 98 | 99 | if stats["status"] != "up" { 100 | t.Fatalf("expected status to be up, got %s", stats["status"]) 101 | } 102 | 103 | if _, ok := stats["error"]; ok { 104 | t.Fatalf("expected error not to be present") 105 | } 106 | 107 | if stats["message"] != "It's healthy" { 108 | t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) 109 | } 110 | 111 | if stats["scylla_cluster_nodes_up"] != "1" { 112 | t.Fatalf("expected nodes up '1', got %s", stats["scylla_cluster_nodes_up"]) 113 | } 114 | 115 | if stats["scylla_cluster_nodes_down"] != "0" { 116 | t.Fatalf("expected nodes down '0', got %s", stats["scylla_cluster_nodes_down"]) 117 | } 118 | 119 | if stats["scylla_current_datacenter"] != "datacenter1" { 120 | t.Fatalf("expected connected dc 'datacenter', got %s", stats["scylla_current_datacenter"]) 121 | } 122 | 123 | err := srv.Close() 124 | if err != nil { 125 | t.Fatalf("expected Close() to return nil") 126 | } 127 | } 128 | 129 | func TestClose(t *testing.T) { 130 | srv := New() 131 | 132 | if srv.Close() != nil { 133 | t.Fatalf("expected Close() to return nil") 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /docs/docs/endpoints-test/sql.md: -------------------------------------------------------------------------------- 1 | To test the SQL DB Health Check endpoint, use the following curl command: 2 | 3 | ```bash 4 | curl http://localhost:PORT/health 5 | ``` 6 | ## Health Function 7 | 8 | The `Health` function checks the health of the database connection by pinging the database and retrieving various statistics. It returns a map with keys indicating different health metrics. 9 | 10 | ### Functionality 11 | 12 | **Ping the Database**: The function pings the database to ensure it is reachable. 13 | 14 | - If the database is down, it logs the error, sets the status to "down," and terminates the program. 15 | - If the database is up, it proceeds to gather additional statistics. 16 | 17 | **Collect Database Statistics**: The function retrieves the following statistics from the database connection: 18 | 19 | - `open_connections`: Number of open connections to the database. 20 | - `in_use`: Number of connections currently in use. 21 | - `idle`: Number of idle connections. 22 | - `wait_count`: Number of times a connection has to wait. 23 | - `wait_duration`: Total time connections have spent waiting. 24 | - `max_idle_closed`: Number of connections closed due to exceeding idle time. 25 | - `max_lifetime_closed`: Number of connections closed due to exceeding their lifetime. 26 | 27 | **Evaluate Statistics**: Evaluates the collected statistics to provide a health message. Based on predefined thresholds, it updates the health message to indicate potential issues, such as heavy load or high wait events. 28 | 29 | ### Sample Output 30 | 31 | The `Health` function returns a JSON-like map structure with the following keys and example values: 32 | 33 | ```json 34 | { 35 | "idle": "1", 36 | "in_use": "0", 37 | "max_idle_closed": "0", 38 | "max_lifetime_closed": "0", 39 | "message": "It's healthy", 40 | "open_connections": "1", 41 | "status": "up", 42 | "wait_count": "0", 43 | "wait_duration": "0s" 44 | } 45 | ``` 46 | 47 | ## Code Implementation 48 | 49 | ```go 50 | func (s *service) Health() map[string]string { 51 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 52 | defer cancel() 53 | 54 | stats := make(map[string]string) 55 | 56 | err := s.db.PingContext(ctx) 57 | if err != nil { 58 | stats["status"] = "down" 59 | stats["error"] = fmt.Sprintf("db down: %v", err) 60 | log.Fatalf("db down: %v", err) 61 | return stats 62 | } 63 | 64 | stats["status"] = "up" 65 | stats["message"] = "It's healthy" 66 | 67 | dbStats := s.db.Stats() 68 | stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) 69 | stats["in_use"] = strconv.Itoa(dbStats.InUse) 70 | stats["idle"] = strconv.Itoa(dbStats.Idle) 71 | stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10) 72 | stats["wait_duration"] = dbStats.WaitDuration.String() 73 | stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10) 74 | stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10) 75 | 76 | if dbStats.OpenConnections > 40 { 77 | stats["message"] = "The database is experiencing heavy load." 78 | } 79 | 80 | if dbStats.WaitCount > 1000 { 81 | stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks." 82 | } 83 | 84 | if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 { 85 | stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings." 86 | } 87 | 88 | if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 { 89 | stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern." 90 | } 91 | 92 | return stats 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /cmd/ui/textinput/textinput.go: -------------------------------------------------------------------------------- 1 | // Package textinput provides functions that 2 | // help define and draw a text-input step 3 | package textinput 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "regexp" 9 | 10 | "github.com/charmbracelet/bubbles/textinput" 11 | tea "github.com/charmbracelet/bubbletea" 12 | "github.com/charmbracelet/lipgloss" 13 | "github.com/melkeydev/go-blueprint/cmd/program" 14 | ) 15 | 16 | var ( 17 | titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#01FAC6")).Foreground(lipgloss.Color("#030303")).Bold(true).Padding(0, 1, 0) 18 | errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF8700")).Bold(true).Padding(0, 0, 0) 19 | ) 20 | 21 | type ( 22 | errMsg error 23 | ) 24 | 25 | // Output represents the text provided in a textinput step 26 | type Output struct { 27 | Output string 28 | } 29 | 30 | // Output.update updates the value of the Output 31 | func (o *Output) update(val string) { 32 | o.Output = val 33 | } 34 | 35 | // A textnput.model contains the data for the textinput step. 36 | // 37 | // It has the required methods that make it a bubbletea.Model 38 | type model struct { 39 | textInput textinput.Model 40 | err error 41 | output *Output 42 | header string 43 | exit *bool 44 | } 45 | 46 | // sanitizeInput verifies that an input text string gets validated 47 | func sanitizeInput(input string) error { 48 | matched, err := regexp.MatchString("^[a-zA-Z0-9_\\/.-]+$", input) 49 | if !matched { 50 | return fmt.Errorf("string violates the input regex pattern, err: %v", err) 51 | } 52 | return nil 53 | } 54 | 55 | // InitialTextInputModel initializes a textinput step 56 | // with the given data 57 | func InitialTextInputModel(output *Output, header string, program *program.Project) model { 58 | ti := textinput.New() 59 | ti.Focus() 60 | ti.CharLimit = 156 61 | ti.Width = 20 62 | ti.Validate = sanitizeInput 63 | 64 | return model{ 65 | textInput: ti, 66 | err: nil, 67 | output: output, 68 | header: titleStyle.Render(header), 69 | exit: &program.Exit, 70 | } 71 | } 72 | 73 | // CreateErrorInputModel creates a textinput step 74 | // with the given error 75 | func CreateErrorInputModel(err error) model { 76 | ti := textinput.New() 77 | ti.Focus() 78 | ti.CharLimit = 156 79 | ti.Width = 20 80 | exit := true 81 | 82 | return model{ 83 | textInput: ti, 84 | err: errors.New(errorStyle.Render(err.Error())), 85 | output: nil, 86 | header: "", 87 | exit: &exit, 88 | } 89 | } 90 | 91 | // Init is called at the beginning of a textinput step 92 | // and sets the cursor to blink 93 | func (m model) Init() tea.Cmd { 94 | return textinput.Blink 95 | } 96 | 97 | // Update is called when "things happen", it checks for the users text input, 98 | // and for Ctrl+C or Esc to close the program. 99 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 100 | var cmd tea.Cmd 101 | 102 | switch msg := msg.(type) { 103 | case tea.KeyMsg: 104 | switch msg.Type { 105 | case tea.KeyEnter: 106 | if len(m.textInput.Value()) > 1 { 107 | m.output.update(m.textInput.Value()) 108 | return m, tea.Quit 109 | } 110 | case tea.KeyCtrlC, tea.KeyEsc: 111 | *m.exit = true 112 | return m, tea.Quit 113 | } 114 | 115 | // We handle errors just like any other message 116 | case errMsg: 117 | m.err = msg 118 | *m.exit = true 119 | return m, nil 120 | } 121 | 122 | m.textInput, cmd = m.textInput.Update(msg) 123 | return m, cmd 124 | } 125 | 126 | // View is called to draw the textinput step 127 | func (m model) View() string { 128 | return fmt.Sprintf("%s\n\n%s\n\n", 129 | m.header, 130 | m.textInput.View(), 131 | ) 132 | } 133 | 134 | func (m model) Err() string { 135 | return m.err.Error() 136 | } 137 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/service/sqlite.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | _ "github.com/mattn/go-sqlite3" 13 | _ "github.com/joho/godotenv/autoload" 14 | ) 15 | 16 | // Service represents a service that interacts with a database. 17 | type Service interface { 18 | // Health returns a map of health status information. 19 | // The keys and values in the map are service-specific. 20 | Health() map[string]string 21 | 22 | // Close terminates the database connection. 23 | // It returns an error if the connection cannot be closed. 24 | Close() error 25 | } 26 | 27 | type service struct { 28 | db *sql.DB 29 | } 30 | 31 | var ( 32 | dburl = os.Getenv("BLUEPRINT_DB_URL") 33 | dbInstance *service 34 | ) 35 | 36 | func New() Service { 37 | // Reuse Connection 38 | if dbInstance != nil { 39 | return dbInstance 40 | } 41 | 42 | db, err := sql.Open("sqlite3", dburl) 43 | if err != nil { 44 | // This will not be a connection error, but a DSN parse error or 45 | // another initialization error. 46 | log.Fatal(err) 47 | } 48 | 49 | dbInstance = &service{ 50 | db: db, 51 | } 52 | return dbInstance 53 | } 54 | 55 | // Health checks the health of the database connection by pinging the database. 56 | // It returns a map with keys indicating various health statistics. 57 | func (s *service) Health() map[string]string { 58 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 59 | defer cancel() 60 | 61 | stats := make(map[string]string) 62 | 63 | // Ping the database 64 | err := s.db.PingContext(ctx) 65 | if err != nil { 66 | stats["status"] = "down" 67 | stats["error"] = fmt.Sprintf("db down: %v", err) 68 | log.Fatalf("db down: %v", err) // Log the error and terminate the program 69 | return stats 70 | } 71 | 72 | // Database is up, add more statistics 73 | stats["status"] = "up" 74 | stats["message"] = "It's healthy" 75 | 76 | // Get database stats (like open connections, in use, idle, etc.) 77 | dbStats := s.db.Stats() 78 | stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) 79 | stats["in_use"] = strconv.Itoa(dbStats.InUse) 80 | stats["idle"] = strconv.Itoa(dbStats.Idle) 81 | stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10) 82 | stats["wait_duration"] = dbStats.WaitDuration.String() 83 | stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10) 84 | stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10) 85 | 86 | // Evaluate stats to provide a health message 87 | if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example 88 | stats["message"] = "The database is experiencing heavy load." 89 | } 90 | 91 | if dbStats.WaitCount > 1000 { 92 | stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks." 93 | } 94 | 95 | if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 { 96 | stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings." 97 | } 98 | 99 | if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 { 100 | stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern." 101 | } 102 | 103 | return stats 104 | } 105 | 106 | // Close closes the database connection. 107 | // It logs a message indicating the disconnection from the specific database. 108 | // If the connection is successfully closed, it returns nil. 109 | // If an error occurs while closing the connection, it returns the error. 110 | func (s *service) Close() error { 111 | log.Printf("Disconnected from database: %s", dburl) 112 | return s.db.Close() 113 | } 114 | -------------------------------------------------------------------------------- /docs/docs/blueprint-core/db-drivers.md: -------------------------------------------------------------------------------- 1 | To extend the project with database functionality, users can choose from a variety of Go database drivers. Each driver is tailored to work with specific database systems, providing flexibility based on project requirements: 2 | 3 | 1. [Mongo](https://go.mongodb.org/mongo-driver): Provides necessary tools for connecting and interacting with MongoDB databases. 4 | 2. [Mysql](https://github.com/go-sql-driver/mysql): Enables seamless integration with MySQL databases. 5 | 3. [Postgres](https://github.com/jackc/pgx/): Facilitates connectivity to PostgreSQL databases. 6 | 4. [Redis](https://github.com/redis/go-redis): Provides tools for connecting and interacting with Redis. 7 | 5. [Sqlite](https://github.com/mattn/go-sqlite3): Suitable for projects requiring a lightweight, self-contained database. 8 | 6. [ScyllaDB](https://github.com/scylladb/gocql): Facilitates connectivity to ScyllaDB databases. 9 | 10 | ## Updated Project Structure 11 | 12 | Integrating a database adds a new layer to the project structure, primarily in the `internal/database` directory: 13 | 14 | ```bash 15 | /(Root) 16 | ├── /cmd 17 | │ └── /api 18 | │ └── main.go 19 | ├── /internal 20 | │ ├── /database 21 | │ │ ├── database_test.go 22 | │ │ └── database.go 23 | │ └── /server 24 | │ ├── routes.go 25 | │ ├── routes_test.go 26 | │ └── server.go 27 | ├── go.mod 28 | ├── go.sum 29 | ├── Makefile 30 | └── README.md 31 | ``` 32 | 33 | ## Database Driver Implementation 34 | 35 | Users can select the desired database driver based on their project's specific needs. The chosen driver is then imported into the project, and the `database.go` file is adjusted accordingly to establish a connection and manage interactions with the selected database. 36 | 37 | ## Integration Tests for Database Operations 38 | 39 | For all the database drivers but `Sqlite`, integration tests are automatically generated to ensure that the database connection is working correctly. It uses [Testcontainers for Go](https://golang.testcontainers.org/) to spin up a containerized instance of the database server, run the tests, and then tear down the container. 40 | 41 | [Testcontainers for Go](https://golang.testcontainers.org/) is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done. 42 | 43 | 44 | ### Requirements 45 | 46 | You need a container runtime installed on your machine. Testcontainers supports Docker and any other container runtime that implements the Docker APIs. 47 | 48 | To install Docker: 49 | 50 | ```bash 51 | curl -sLO get.docker.com 52 | ``` 53 | 54 | ### Running the tests 55 | 56 | Go to the `internal/database` directory and run the following command: 57 | 58 | ```bash 59 | go test -v 60 | ``` 61 | 62 | Or, just run the following command from the root directory: 63 | 64 | ```bash 65 | make itest 66 | ``` 67 | 68 | Testcontainers automatically pulls the required Docker images and start the containers. The tests run against the containers, and once the tests are done, the containers are stopped and removed. For further information, refer to the [official documentation](https://golang.testcontainers.org/). 69 | 70 | ## Docker-Compose for Quick Database Spinup 71 | 72 | To facilitate quick setup and testing, a `docker-compose.yml` file is provided. This file defines a service for the chosen database system with the necessary environment variables. Running `docker-compose up` will quickly spin up a containerized instance of the database, allowing users to test their application against a real database server. 73 | 74 | This Docker Compose approach simplifies the process of setting up a database for development or testing purposes, providing a convenient and reproducible environment for the project. 75 | -------------------------------------------------------------------------------- /cmd/ui/multiSelect/multiSelect.go: -------------------------------------------------------------------------------- 1 | // Package multiSelect provides functions that 2 | // help define and draw a multi-select step 3 | package multiSelect 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/melkeydev/go-blueprint/cmd/program" 9 | "github.com/melkeydev/go-blueprint/cmd/steps" 10 | 11 | tea "github.com/charmbracelet/bubbletea" 12 | "github.com/charmbracelet/lipgloss" 13 | ) 14 | 15 | // Change this 16 | var ( 17 | focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#01FAC6")).Bold(true) 18 | titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#01FAC6")).Foreground(lipgloss.Color("#030303")).Bold(true).Padding(0, 1, 0) 19 | selectedItemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")).Bold(true) 20 | selectedItemDescStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")) 21 | descriptionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#40BDA3")) 22 | ) 23 | 24 | // A Selection represents a choice made in a multiSelect step 25 | type Selection struct { 26 | Choices map[string]bool 27 | } 28 | 29 | // Update changes the value of a Selection's Choice 30 | func (s *Selection) Update(optionName string, value bool) { 31 | s.Choices[optionName] = value 32 | } 33 | 34 | // A multiSelect.model contains the data for the multiSelect step. 35 | // 36 | // It has the required methods that make it a bubbletea.Model 37 | type model struct { 38 | cursor int 39 | options []steps.Item 40 | selected map[int]struct{} 41 | choices *Selection 42 | header string 43 | exit *bool 44 | } 45 | 46 | func (m model) Init() tea.Cmd { 47 | return nil 48 | } 49 | 50 | // InitialModelMulti initializes a multiSelect step with 51 | // the given data 52 | func InitialModelMultiSelect(options []steps.Item, selection *Selection, header string, program *program.Project) model { 53 | return model{ 54 | options: options, 55 | selected: make(map[int]struct{}), 56 | choices: selection, 57 | header: titleStyle.Render(header), 58 | exit: &program.Exit, 59 | } 60 | } 61 | 62 | // Update is called when "things happen", it checks for 63 | // important keystrokes to signal when to quit, change selection, 64 | // and confirm the selection. 65 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 66 | switch msg := msg.(type) { 67 | case tea.KeyMsg: 68 | switch msg.String() { 69 | case "ctrl+c", "q": 70 | *m.exit = true 71 | return m, tea.Quit 72 | case "up", "k": 73 | if m.cursor > 0 { 74 | m.cursor-- 75 | } 76 | case "down", "j": 77 | if m.cursor < len(m.options)-1 { 78 | m.cursor++ 79 | } 80 | case "enter", " ": 81 | _, ok := m.selected[m.cursor] 82 | if ok { 83 | delete(m.selected, m.cursor) 84 | } else { 85 | m.selected[m.cursor] = struct{}{} 86 | } 87 | case "y": 88 | for selectedKey := range m.selected { 89 | m.choices.Update(m.options[selectedKey].Flag, true) 90 | m.cursor = selectedKey 91 | } 92 | return m, tea.Quit 93 | } 94 | } 95 | return m, nil 96 | } 97 | 98 | // View is called to draw the multiSelect step 99 | func (m model) View() string { 100 | s := m.header + "\n\n" 101 | 102 | for i, option := range m.options { 103 | cursor := " " 104 | if m.cursor == i { 105 | cursor = focusedStyle.Render(">") 106 | option.Title = selectedItemStyle.Render(option.Title) 107 | option.Desc = selectedItemDescStyle.Render(option.Desc) 108 | } 109 | 110 | checked := " " 111 | if _, ok := m.selected[i]; ok { 112 | checked = focusedStyle.Render("*") 113 | } 114 | 115 | title := focusedStyle.Render(option.Title) 116 | description := descriptionStyle.Render(option.Desc) 117 | 118 | s += fmt.Sprintf("%s [%s] %s\n%s\n\n", cursor, checked, title, description) 119 | } 120 | 121 | s += fmt.Sprintf("Press %s to confirm choice.\n", focusedStyle.Render("y")) 122 | return s 123 | } 124 | -------------------------------------------------------------------------------- /docs/docs/advanced-flag/docker.md: -------------------------------------------------------------------------------- 1 | The Docker advanced flag provides the app's Dockerfile configuration and creates or updates the docker-compose.yml file, which is generated if a DB driver is used. 2 | The Dockerfile includes a two-stage build, and the final config depends on the use of advanced features. In the end, you will have a smaller image without unnecessary build dependencies. 3 | 4 | ## Dockerfile 5 | 6 | ```dockerfile 7 | FROM golang:1.24.4-alpine AS build 8 | 9 | RUN apk add --no-cache curl libstdc++ libgcc 10 | 11 | WORKDIR /app 12 | 13 | COPY go.mod go.sum ./ 14 | RUN go mod download 15 | 16 | COPY . . 17 | 18 | RUN go install github.com/a-h/templ/cmd/templ@latest && \ 19 | templ generate && \ 20 | curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64-musl -o tailwindcss && \ 21 | chmod +x tailwindcss && \ 22 | ./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css 23 | 24 | RUN go build -o main cmd/api/main.go 25 | 26 | FROM alpine:3.20.1 AS prod 27 | WORKDIR /app 28 | COPY --from=build /app/main /app/main 29 | EXPOSE ${PORT} 30 | CMD ["./main"] 31 | ``` 32 | 33 | Docker config if React flag is used: 34 | 35 | ```dockerfile 36 | FROM golang:1.24.4-alpine AS build 37 | 38 | WORKDIR /app 39 | 40 | COPY go.mod go.sum ./ 41 | RUN go mod download 42 | 43 | COPY . . 44 | 45 | RUN go build -o main cmd/api/main.go 46 | 47 | FROM alpine:3.20.1 AS prod 48 | WORKDIR /app 49 | COPY --from=build /app/main /app/main 50 | EXPOSE ${PORT} 51 | CMD ["./main"] 52 | 53 | 54 | FROM node:20 AS frontend_builder 55 | WORKDIR /frontend 56 | 57 | COPY frontend/package*.json ./ 58 | RUN npm install 59 | COPY frontend/. . 60 | RUN npm run build 61 | 62 | FROM node:20-slim AS frontend 63 | RUN npm install -g serve 64 | COPY --from=frontend_builder /frontend/dist /app/dist 65 | EXPOSE 5173 66 | CMD ["serve", "-s", "/app/dist", "-l", "5173"] 67 | ``` 68 | ## Docker compose 69 | Docker and docker-compose.yml pull environment variables from the .env file. 70 | 71 | Example if the Docker flag is used with the MySQL DB driver: 72 | ```yaml 73 | services: 74 | app: 75 | build: 76 | context: . 77 | dockerfile: Dockerfile 78 | target: prod 79 | restart: unless-stopped 80 | ports: 81 | - ${PORT}:${PORT} 82 | environment: 83 | APP_ENV: ${APP_ENV} 84 | PORT: ${PORT} 85 | BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST} 86 | BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT} 87 | BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE} 88 | BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME} 89 | BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 90 | depends_on: 91 | mysql_bp: 92 | condition: service_healthy 93 | networks: 94 | - blueprint 95 | mysql_bp: 96 | image: mysql:latest 97 | restart: unless-stopped 98 | environment: 99 | MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE} 100 | MYSQL_USER: ${BLUEPRINT_DB_USERNAME} 101 | MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD} 102 | MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD} 103 | ports: 104 | - "${BLUEPRINT_DB_PORT}:3306" 105 | volumes: 106 | - mysql_volume_bp:/var/lib/mysql 107 | healthcheck: 108 | test: ["CMD", "mysqladmin", "ping", "-h", "${BLUEPRINT_DB_HOST}", "-u", "${BLUEPRINT_DB_USERNAME}", "--password=${BLUEPRINT_DB_PASSWORD}"] 109 | interval: 5s 110 | timeout: 5s 111 | retries: 3 112 | start_period: 15s 113 | networks: 114 | - blueprint 115 | 116 | volumes: 117 | mysql_volume_bp: 118 | networks: 119 | blueprint: 120 | ``` 121 | 122 | ## Note 123 | If you are testing more than one framework locally, be aware of Docker leftovers such as volumes. 124 | For proper cleaning and building, use `docker compose down --volumes` and `docker compose up --build`. 125 | 126 | or 127 | 128 | ```bash 129 | docker compose build --no-cache && docker compose up 130 | ``` 131 | -------------------------------------------------------------------------------- /cmd/ui/multiInput/multiInput.go: -------------------------------------------------------------------------------- 1 | // Package multiInput provides functions that 2 | // help define and draw a multi-input step 3 | package multiInput 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/melkeydev/go-blueprint/cmd/program" 9 | "github.com/melkeydev/go-blueprint/cmd/steps" 10 | 11 | tea "github.com/charmbracelet/bubbletea" 12 | "github.com/charmbracelet/lipgloss" 13 | ) 14 | 15 | // Change this 16 | var ( 17 | focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#01FAC6")).Bold(true) 18 | titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#01FAC6")).Foreground(lipgloss.Color("#030303")).Bold(true).Padding(0, 1, 0) 19 | selectedItemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")).Bold(true) 20 | selectedItemDescStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")) 21 | descriptionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#40BDA3")) 22 | ) 23 | 24 | // A Selection represents a choice made in a multiInput step 25 | type Selection struct { 26 | Choice string 27 | } 28 | 29 | // Update changes the value of a Selection's Choice 30 | func (s *Selection) Update(value string) { 31 | s.Choice = value 32 | } 33 | 34 | // A multiInput.model contains the data for the multiInput step. 35 | // 36 | // It has the required methods that make it a bubbletea.Model 37 | type model struct { 38 | cursor int 39 | choices []steps.Item 40 | selected map[int]struct{} 41 | choice *Selection 42 | header string 43 | exit *bool 44 | } 45 | 46 | func (m model) Init() tea.Cmd { 47 | return nil 48 | } 49 | 50 | // InitialModelMulti initializes a multiInput step with 51 | // the given data 52 | func InitialModelMulti(choices []steps.Item, selection *Selection, header string, program *program.Project) model { 53 | return model{ 54 | choices: choices, 55 | selected: make(map[int]struct{}), 56 | choice: selection, 57 | header: titleStyle.Render(header), 58 | exit: &program.Exit, 59 | } 60 | } 61 | 62 | // Update is called when "things happen", it checks for 63 | // important keystrokes to signal when to quit, change selection, 64 | // and confirm the selection. 65 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 66 | switch msg := msg.(type) { 67 | case tea.KeyMsg: 68 | switch msg.String() { 69 | case "ctrl+c", "q": 70 | *m.exit = true 71 | return m, tea.Quit 72 | case "up", "k": 73 | if m.cursor > 0 { 74 | m.cursor-- 75 | } 76 | case "down", "j": 77 | if m.cursor < len(m.choices)-1 { 78 | m.cursor++ 79 | } 80 | case "enter", " ": 81 | if len(m.selected) == 1 { 82 | m.selected = make(map[int]struct{}) 83 | } 84 | _, ok := m.selected[m.cursor] 85 | if ok { 86 | delete(m.selected, m.cursor) 87 | } else { 88 | m.selected[m.cursor] = struct{}{} 89 | } 90 | case "y": 91 | if len(m.selected) == 1 { 92 | for selectedKey := range m.selected { 93 | m.choice.Update(m.choices[selectedKey].Title) 94 | m.cursor = selectedKey 95 | } 96 | return m, tea.Quit 97 | } 98 | } 99 | } 100 | return m, nil 101 | } 102 | 103 | // View is called to draw the multiInput step 104 | func (m model) View() string { 105 | s := m.header + "\n\n" 106 | 107 | for i, choice := range m.choices { 108 | cursor := " " 109 | if m.cursor == i { 110 | cursor = focusedStyle.Render(">") 111 | choice.Title = selectedItemStyle.Render(choice.Title) 112 | choice.Desc = selectedItemDescStyle.Render(choice.Desc) 113 | } 114 | 115 | checked := " " 116 | if _, ok := m.selected[i]; ok { 117 | checked = focusedStyle.Render("x") 118 | } 119 | 120 | title := focusedStyle.Render(choice.Title) 121 | description := descriptionStyle.Render(choice.Desc) 122 | 123 | s += fmt.Sprintf("%s [%s] %s\n%s\n\n", cursor, checked, title, description) 124 | } 125 | 126 | s += fmt.Sprintf("Press %s to confirm choice.\n\n", focusedStyle.Render("y")) 127 | return s 128 | } 129 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/service/postgres.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | _ "github.com/jackc/pgx/v5/stdlib" 13 | _ "github.com/joho/godotenv/autoload" 14 | ) 15 | 16 | // Service represents a service that interacts with a database. 17 | type Service interface { 18 | // Health returns a map of health status information. 19 | // The keys and values in the map are service-specific. 20 | Health() map[string]string 21 | 22 | // Close terminates the database connection. 23 | // It returns an error if the connection cannot be closed. 24 | Close() error 25 | } 26 | 27 | type service struct { 28 | db *sql.DB 29 | } 30 | 31 | var ( 32 | database = os.Getenv("BLUEPRINT_DB_DATABASE") 33 | password = os.Getenv("BLUEPRINT_DB_PASSWORD") 34 | username = os.Getenv("BLUEPRINT_DB_USERNAME") 35 | port = os.Getenv("BLUEPRINT_DB_PORT") 36 | host = os.Getenv("BLUEPRINT_DB_HOST") 37 | schema = os.Getenv("BLUEPRINT_DB_SCHEMA") 38 | dbInstance *service 39 | ) 40 | 41 | func New() Service { 42 | // Reuse Connection 43 | if dbInstance != nil { 44 | return dbInstance 45 | } 46 | connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&search_path=%s", username, password, host, port, database, schema) 47 | db, err := sql.Open("pgx", connStr) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | dbInstance = &service{ 52 | db: db, 53 | } 54 | return dbInstance 55 | } 56 | 57 | // Health checks the health of the database connection by pinging the database. 58 | // It returns a map with keys indicating various health statistics. 59 | func (s *service) Health() map[string]string { 60 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 61 | defer cancel() 62 | 63 | stats := make(map[string]string) 64 | 65 | // Ping the database 66 | err := s.db.PingContext(ctx) 67 | if err != nil { 68 | stats["status"] = "down" 69 | stats["error"] = fmt.Sprintf("db down: %v", err) 70 | log.Fatalf("db down: %v", err) // Log the error and terminate the program 71 | return stats 72 | } 73 | 74 | // Database is up, add more statistics 75 | stats["status"] = "up" 76 | stats["message"] = "It's healthy" 77 | 78 | // Get database stats (like open connections, in use, idle, etc.) 79 | dbStats := s.db.Stats() 80 | stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) 81 | stats["in_use"] = strconv.Itoa(dbStats.InUse) 82 | stats["idle"] = strconv.Itoa(dbStats.Idle) 83 | stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10) 84 | stats["wait_duration"] = dbStats.WaitDuration.String() 85 | stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10) 86 | stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10) 87 | 88 | // Evaluate stats to provide a health message 89 | if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example 90 | stats["message"] = "The database is experiencing heavy load." 91 | } 92 | 93 | if dbStats.WaitCount > 1000 { 94 | stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks." 95 | } 96 | 97 | if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 { 98 | stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings." 99 | } 100 | 101 | if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 { 102 | stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern." 103 | } 104 | 105 | return stats 106 | } 107 | 108 | // Close closes the database connection. 109 | // It logs a message indicating the disconnection from the specific database. 110 | // If the connection is successfully closed, it returns nil. 111 | // If an error occurs while closing the connection, it returns the error. 112 | func (s *service) Close() error { 113 | log.Printf("Disconnected from database: %s", database) 114 | return s.db.Close() 115 | } 116 | -------------------------------------------------------------------------------- /cmd/template/dbdriver/files/service/mysql.tmpl: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | _ "github.com/go-sql-driver/mysql" 13 | _ "github.com/joho/godotenv/autoload" 14 | ) 15 | 16 | // Service represents a service that interacts with a database. 17 | type Service interface { 18 | // Health returns a map of health status information. 19 | // The keys and values in the map are service-specific. 20 | Health() map[string]string 21 | 22 | // Close terminates the database connection. 23 | // It returns an error if the connection cannot be closed. 24 | Close() error 25 | } 26 | 27 | type service struct { 28 | db *sql.DB 29 | } 30 | 31 | var ( 32 | dbname = os.Getenv("BLUEPRINT_DB_DATABASE") 33 | password = os.Getenv("BLUEPRINT_DB_PASSWORD") 34 | username = os.Getenv("BLUEPRINT_DB_USERNAME") 35 | port = os.Getenv("BLUEPRINT_DB_PORT") 36 | host = os.Getenv("BLUEPRINT_DB_HOST") 37 | dbInstance *service 38 | ) 39 | 40 | func New() Service { 41 | // Reuse Connection 42 | if dbInstance != nil { 43 | return dbInstance 44 | } 45 | 46 | // Opening a driver typically will not attempt to connect to the database. 47 | db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname)) 48 | if err != nil { 49 | // This will not be a connection error, but a DSN parse error or 50 | // another initialization error. 51 | log.Fatal(err) 52 | } 53 | db.SetConnMaxLifetime(0) 54 | db.SetMaxIdleConns(50) 55 | db.SetMaxOpenConns(50) 56 | 57 | dbInstance = &service{ 58 | db: db, 59 | } 60 | return dbInstance 61 | } 62 | 63 | // Health checks the health of the database connection by pinging the database. 64 | // It returns a map with keys indicating various health statistics. 65 | func (s *service) Health() map[string]string { 66 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 67 | defer cancel() 68 | 69 | stats := make(map[string]string) 70 | 71 | // Ping the database 72 | err := s.db.PingContext(ctx) 73 | if err != nil { 74 | stats["status"] = "down" 75 | stats["error"] = fmt.Sprintf("db down: %v", err) 76 | log.Fatalf("db down: %v", err) // Log the error and terminate the program 77 | return stats 78 | } 79 | 80 | // Database is up, add more statistics 81 | stats["status"] = "up" 82 | stats["message"] = "It's healthy" 83 | 84 | // Get database stats (like open connections, in use, idle, etc.) 85 | dbStats := s.db.Stats() 86 | stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) 87 | stats["in_use"] = strconv.Itoa(dbStats.InUse) 88 | stats["idle"] = strconv.Itoa(dbStats.Idle) 89 | stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10) 90 | stats["wait_duration"] = dbStats.WaitDuration.String() 91 | stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10) 92 | stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10) 93 | 94 | // Evaluate stats to provide a health message 95 | if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example 96 | stats["message"] = "The database is experiencing heavy load." 97 | } 98 | if dbStats.WaitCount > 1000 { 99 | stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks." 100 | } 101 | 102 | if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 { 103 | stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings." 104 | } 105 | 106 | if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 { 107 | stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern." 108 | } 109 | 110 | return stats 111 | } 112 | 113 | // Close closes the database connection. 114 | // It logs a message indicating the disconnection from the specific database. 115 | // If the connection is successfully closed, it returns nil. 116 | // If an error occurs while closing the connection, it returns the error. 117 | func (s *service) Close() error { 118 | log.Printf("Disconnected from database: %s", dbname) 119 | return s.db.Close() 120 | } 121 | -------------------------------------------------------------------------------- /cmd/template/advanced/routes.go: -------------------------------------------------------------------------------- 1 | package advanced 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed files/htmx/hello.templ.tmpl 8 | var helloTemplTemplate []byte 9 | 10 | //go:embed files/htmx/base.templ.tmpl 11 | var baseTemplTemplate []byte 12 | 13 | //go:embed files/react/tailwind/index.css.tmpl 14 | var inputCssTemplateReact []byte 15 | 16 | //go:embed files/react/tailwind/vite.config.ts.tmpl 17 | var viteTailwindConfigFile []byte 18 | 19 | //go:embed files/react/tailwind/app.tsx.tmpl 20 | var reactTailwindAppFile []byte 21 | 22 | //go:embed files/react/app.tsx.tmpl 23 | var reactAppFile []byte 24 | 25 | //go:embed files/tailwind/input.css.tmpl 26 | var inputCssTemplate []byte 27 | 28 | //go:embed files/tailwind/output.css.tmpl 29 | var outputCssTemplate []byte 30 | 31 | //go:embed files/htmx/tailwind/tailwind.config.js.tmpl 32 | var htmxTailwindConfigJsTemplate []byte 33 | 34 | //go:embed files/htmx/htmx.min.js.tmpl 35 | var htmxMinJsTemplate []byte 36 | 37 | //go:embed files/htmx/efs.go.tmpl 38 | var efsTemplate []byte 39 | 40 | //go:embed files/htmx/hello.go.tmpl 41 | var helloGoTemplate []byte 42 | 43 | //go:embed files/htmx/hello_fiber.go.tmpl 44 | var helloFiberGoTemplate []byte 45 | 46 | //go:embed files/htmx/routes/http_router.tmpl 47 | var httpRouterHtmxTemplRoutes []byte 48 | 49 | //go:embed files/htmx/routes/standard_library.tmpl 50 | var stdLibHtmxTemplRoutes []byte 51 | 52 | //go:embed files/htmx/imports/standard_library.tmpl 53 | var stdLibHtmxTemplImports []byte 54 | 55 | //go:embed files/websocket/imports/standard_library.tmpl 56 | var stdLibWebsocketImports []byte 57 | 58 | //go:embed files/htmx/routes/chi.tmpl 59 | var chiHtmxTemplRoutes []byte 60 | 61 | //go:embed files/htmx/routes/gin.tmpl 62 | var ginHtmxTemplRoutes []byte 63 | 64 | //go:embed files/htmx/imports/gin.tmpl 65 | var ginHtmxTemplImports []byte 66 | 67 | //go:embed files/htmx/routes/gorilla.tmpl 68 | var gorillaHtmxTemplRoutes []byte 69 | 70 | //go:embed files/htmx/routes/echo.tmpl 71 | var echoHtmxTemplRoutes []byte 72 | 73 | //go:embed files/htmx/routes/fiber.tmpl 74 | var fiberHtmxTemplRoutes []byte 75 | 76 | //go:embed files/htmx/imports/fiber.tmpl 77 | var fiberHtmxTemplImports []byte 78 | 79 | //go:embed files/websocket/imports/fiber.tmpl 80 | var fiberWebsocketTemplImports []byte 81 | 82 | func EchoHtmxTemplRoutesTemplate() []byte { 83 | return echoHtmxTemplRoutes 84 | } 85 | 86 | func GorillaHtmxTemplRoutesTemplate() []byte { 87 | return gorillaHtmxTemplRoutes 88 | } 89 | 90 | func ChiHtmxTemplRoutesTemplate() []byte { 91 | return chiHtmxTemplRoutes 92 | } 93 | 94 | func GinHtmxTemplRoutesTemplate() []byte { 95 | return ginHtmxTemplRoutes 96 | } 97 | 98 | func HttpRouterHtmxTemplRoutesTemplate() []byte { 99 | return httpRouterHtmxTemplRoutes 100 | } 101 | 102 | func StdLibHtmxTemplRoutesTemplate() []byte { 103 | return stdLibHtmxTemplRoutes 104 | } 105 | 106 | func StdLibHtmxTemplImportsTemplate() []byte { 107 | return stdLibHtmxTemplImports 108 | } 109 | 110 | func StdLibWebsocketTemplImportsTemplate() []byte { 111 | return stdLibWebsocketImports 112 | } 113 | 114 | func HelloTemplTemplate() []byte { 115 | return helloTemplTemplate 116 | } 117 | 118 | func BaseTemplTemplate() []byte { 119 | return baseTemplTemplate 120 | } 121 | 122 | func ReactTailwindAppfile() []byte { 123 | return reactTailwindAppFile 124 | } 125 | 126 | func ReactAppfile() []byte { 127 | return reactAppFile 128 | } 129 | 130 | func InputCssTemplateReact() []byte { 131 | return inputCssTemplateReact 132 | } 133 | 134 | func ViteTailwindConfigFile() []byte { 135 | return viteTailwindConfigFile 136 | } 137 | 138 | func InputCssTemplate() []byte { 139 | return inputCssTemplate 140 | } 141 | 142 | func OutputCssTemplate() []byte { 143 | return outputCssTemplate 144 | } 145 | 146 | func HtmxTailwindConfigJsTemplate() []byte { 147 | return htmxTailwindConfigJsTemplate 148 | } 149 | 150 | func HtmxJSTemplate() []byte { 151 | return htmxMinJsTemplate 152 | } 153 | 154 | func EfsTemplate() []byte { 155 | return efsTemplate 156 | } 157 | 158 | func HelloGoTemplate() []byte { 159 | return helloGoTemplate 160 | } 161 | 162 | func HelloFiberGoTemplate() []byte { 163 | return helloFiberGoTemplate 164 | } 165 | 166 | func FiberHtmxTemplRoutesTemplate() []byte { 167 | return fiberHtmxTemplRoutes 168 | } 169 | 170 | func FiberHtmxTemplImportsTemplate() []byte { 171 | return fiberHtmxTemplImports 172 | } 173 | 174 | func FiberWebsocketTemplImportsTemplate() []byte { 175 | return fiberWebsocketTemplImports 176 | } 177 | 178 | func GinHtmxTemplImportsTemplate() []byte { 179 | return ginHtmxTemplImports 180 | } 181 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= 6 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= 7 | github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= 8 | github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= 9 | github.com/charmbracelet/lipgloss v0.9.0 h1:BHIM7U4vX77xGEld8GrTKspBMtSv7j0wxPCH73nrdxE= 10 | github.com/charmbracelet/lipgloss v0.9.0/go.mod h1:h8KDyaivONasw1Bhb4nWiKlk4P1wHPly+3+3v6EFMmA= 11 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 12 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 14 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 15 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 16 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 17 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 18 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 19 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 20 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 21 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 22 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 23 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 24 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 25 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= 26 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= 27 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 28 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 29 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 30 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 31 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 32 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 33 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 34 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 35 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 36 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 37 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 38 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 39 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 40 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 41 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 42 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 46 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= 48 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 49 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 50 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | -------------------------------------------------------------------------------- /cmd/steps/steps.go: -------------------------------------------------------------------------------- 1 | // Package steps provides utility for creating 2 | // each step of the CLI 3 | package steps 4 | 5 | import "github.com/melkeydev/go-blueprint/cmd/flags" 6 | 7 | // A StepSchema contains the data that is used 8 | // for an individual step of the CLI 9 | type StepSchema struct { 10 | StepName string // The name of a given step 11 | Options []Item // The slice of each option for a given step 12 | Headers string // The title displayed at the top of a given step 13 | Field string 14 | } 15 | 16 | // Steps contains a slice of steps 17 | type Steps struct { 18 | Steps map[string]StepSchema 19 | } 20 | 21 | // An Item contains the data for each option 22 | // in a StepSchema.Options 23 | type Item struct { 24 | Flag, Title, Desc string 25 | } 26 | 27 | // InitSteps initializes and returns the *Steps to be used in the CLI program 28 | func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps { 29 | steps := &Steps{ 30 | map[string]StepSchema{ 31 | "framework": { 32 | StepName: "Go Project Framework", 33 | Options: []Item{ 34 | { 35 | Title: "Standard-library", 36 | Desc: "The built-in Go standard library HTTP package", 37 | }, 38 | { 39 | Title: "Chi", 40 | Desc: "A lightweight, idiomatic and composable router for building Go HTTP services", 41 | }, 42 | { 43 | Title: "Gin", 44 | Desc: "Features a martini-like API with performance that is up to 40 times faster thanks to httprouter", 45 | }, 46 | { 47 | Title: "Fiber", 48 | Desc: "An Express inspired web framework built on top of Fasthttp", 49 | }, 50 | { 51 | Title: "Gorilla/Mux", 52 | Desc: "Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler", 53 | }, 54 | { 55 | Title: "HttpRouter", 56 | Desc: "HttpRouter is a lightweight high performance HTTP request router for Go", 57 | }, 58 | { 59 | Title: "Echo", 60 | Desc: "High performance, extensible, minimalist Go web framework", 61 | }, 62 | }, 63 | Headers: "What framework do you want to use in your Go project?", 64 | Field: projectType.String(), 65 | }, 66 | "driver": { 67 | StepName: "Go Project Database Driver", 68 | Options: []Item{ 69 | { 70 | Title: "Mysql", 71 | Desc: "MySQL-Driver for Go's database/sql package", 72 | }, 73 | { 74 | Title: "Postgres", 75 | Desc: "Go postgres driver for Go's database/sql package"}, 76 | { 77 | Title: "Sqlite", 78 | Desc: "sqlite3 driver conforming to the built-in database/sql interface"}, 79 | { 80 | Title: "Mongo", 81 | Desc: "The MongoDB supported driver for Go."}, 82 | { 83 | Title: "Redis", 84 | Desc: "Redis driver for Go."}, 85 | { 86 | Title: "Scylla", 87 | Desc: "ScyllaDB Enhanced driver from GoCQL."}, 88 | { 89 | Title: "None", 90 | Desc: "Choose this option if you don't wish to install a specific database driver."}, 91 | }, 92 | Headers: "What database driver do you want to use in your Go project?", 93 | Field: databaseType.String(), 94 | }, 95 | "advanced": { 96 | StepName: "Advanced Features", 97 | Headers: "Which advanced features do you want?", 98 | Options: []Item{ 99 | { 100 | Flag: "React", 101 | Title: "React", 102 | Desc: "Use Vite to spin up a React project in TypeScript. This disables selecting HTMX/Templ", 103 | }, 104 | { 105 | Flag: "Htmx", 106 | Title: "HTMX/Templ", 107 | Desc: "Add starter HTMX and Templ files. This disables selecting React", 108 | }, 109 | { 110 | Flag: "GitHubAction", 111 | Title: "Go Project Workflow", 112 | Desc: "Workflow templates for testing, cross-compiling and releasing Go projects", 113 | }, 114 | { 115 | Flag: "Websocket", 116 | Title: "Websocket endpoint", 117 | Desc: "Add a websocket endpoint", 118 | }, 119 | { 120 | Flag: "Tailwind", 121 | Title: "TailwindCSS", 122 | Desc: "A utility-first CSS framework (selecting this will automatically add HTMX unless React is specified)", 123 | }, 124 | { 125 | Flag: "Docker", 126 | Title: "Docker", 127 | Desc: "Dockerfile and docker-compose generic configuration for go project", 128 | }, 129 | }, 130 | }, 131 | "git": { 132 | StepName: "Git Repository", 133 | Headers: "Which git option would you like to select for your project?", 134 | Options: []Item{ 135 | { 136 | Title: "Commit", 137 | Desc: "Initialize a new git repository and commit all the changes", 138 | }, 139 | { 140 | Title: "Stage", 141 | Desc: "Initialize a new git repository but only stage the changes", 142 | }, 143 | { 144 | Title: "Skip", 145 | Desc: "Proceed without initializing a git repository", 146 | }, 147 | }, 148 | }, 149 | }, 150 | } 151 | 152 | return steps 153 | } 154 | --------------------------------------------------------------------------------