├── .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 | 
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 | 
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 |
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 |
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------