Showing preview only (390K chars total). Download the full file or copy to clipboard to get everything.
Repository: Melkeydev/go-blueprint
Branch: main
Commit: 81f56f8c2463
Files: 163
Total size: 350.9 KB
Directory structure:
gitextract_afvolof3/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ ├── config.yml
│ │ └── feature-request.yml
│ ├── pull_request_template.md
│ ├── semantic.yml
│ └── workflows/
│ ├── ci.yml
│ ├── docs.yml
│ ├── generate-linter-advanced.yml
│ ├── generate-linter-core.yml
│ ├── npm-publish.yml
│ ├── release.yml
│ ├── testcontainers.yml
│ └── update-htmx-version.yml
├── .gitignore
├── .goreleaser.yml
├── .pre-commit-config.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│ ├── create.go
│ ├── flags/
│ │ ├── advancedFeatures.go
│ │ ├── database.go
│ │ ├── frameworks.go
│ │ └── git.go
│ ├── program/
│ │ └── program.go
│ ├── root.go
│ ├── steps/
│ │ └── steps.go
│ ├── template/
│ │ ├── advanced/
│ │ │ ├── docker.go
│ │ │ ├── files/
│ │ │ │ ├── docker/
│ │ │ │ │ ├── docker_compose.yml.tmpl
│ │ │ │ │ └── dockerfile.tmpl
│ │ │ │ ├── htmx/
│ │ │ │ │ ├── base.templ.tmpl
│ │ │ │ │ ├── efs.go.tmpl
│ │ │ │ │ ├── hello.go.tmpl
│ │ │ │ │ ├── hello.templ.tmpl
│ │ │ │ │ ├── hello_fiber.go.tmpl
│ │ │ │ │ ├── htmx.min.js.tmpl
│ │ │ │ │ ├── imports/
│ │ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ │ ├── gin.tmpl
│ │ │ │ │ │ └── standard_library.tmpl
│ │ │ │ │ ├── routes/
│ │ │ │ │ │ ├── chi.tmpl
│ │ │ │ │ │ ├── echo.tmpl
│ │ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ │ ├── gin.tmpl
│ │ │ │ │ │ ├── gorilla.tmpl
│ │ │ │ │ │ ├── http_router.tmpl
│ │ │ │ │ │ └── standard_library.tmpl
│ │ │ │ │ └── tailwind/
│ │ │ │ │ └── tailwind.config.js.tmpl
│ │ │ │ ├── react/
│ │ │ │ │ ├── app.tsx.tmpl
│ │ │ │ │ └── tailwind/
│ │ │ │ │ ├── app.tsx.tmpl
│ │ │ │ │ ├── index.css.tmpl
│ │ │ │ │ └── vite.config.ts.tmpl
│ │ │ │ ├── tailwind/
│ │ │ │ │ ├── input.css.tmpl
│ │ │ │ │ └── output.css.tmpl
│ │ │ │ ├── websocket/
│ │ │ │ │ └── imports/
│ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ └── standard_library.tmpl
│ │ │ │ └── workflow/
│ │ │ │ └── github/
│ │ │ │ ├── github_action_goreleaser.yml.tmpl
│ │ │ │ ├── github_action_gotest.yml.tmpl
│ │ │ │ └── github_action_releaser_config.yml.tmpl
│ │ │ ├── gitHubAction.go
│ │ │ └── routes.go
│ │ ├── dbdriver/
│ │ │ ├── files/
│ │ │ │ ├── env/
│ │ │ │ │ ├── mongo.tmpl
│ │ │ │ │ ├── mysql.tmpl
│ │ │ │ │ ├── postgres.tmpl
│ │ │ │ │ ├── redis.tmpl
│ │ │ │ │ ├── scylla.tmpl
│ │ │ │ │ └── sqlite.tmpl
│ │ │ │ ├── service/
│ │ │ │ │ ├── mongo.tmpl
│ │ │ │ │ ├── mysql.tmpl
│ │ │ │ │ ├── postgres.tmpl
│ │ │ │ │ ├── redis.tmpl
│ │ │ │ │ ├── scylla.tmpl
│ │ │ │ │ └── sqlite.tmpl
│ │ │ │ └── tests/
│ │ │ │ ├── mongo.tmpl
│ │ │ │ ├── mysql.tmpl
│ │ │ │ ├── postgres.tmpl
│ │ │ │ ├── redis.tmpl
│ │ │ │ └── scylla.tmpl
│ │ │ ├── mongo.go
│ │ │ ├── mysql.go
│ │ │ ├── postgres.go
│ │ │ ├── redis.go
│ │ │ ├── scylla.go
│ │ │ └── sqlite.go
│ │ ├── docker/
│ │ │ ├── files/
│ │ │ │ └── docker-compose/
│ │ │ │ ├── mongo.tmpl
│ │ │ │ ├── mysql.tmpl
│ │ │ │ ├── postgres.tmpl
│ │ │ │ ├── redis.tmpl
│ │ │ │ └── scylla.tmpl
│ │ │ ├── mongo.go
│ │ │ ├── mysql.go
│ │ │ ├── postgres.go
│ │ │ ├── redis.go
│ │ │ └── scylla.go
│ │ ├── framework/
│ │ │ ├── chiRoutes.go
│ │ │ ├── echoRoutes.go
│ │ │ ├── fiberServer.go
│ │ │ ├── files/
│ │ │ │ ├── README.md.tmpl
│ │ │ │ ├── air.toml.tmpl
│ │ │ │ ├── gitignore.tmpl
│ │ │ │ ├── globalenv.tmpl
│ │ │ │ ├── main/
│ │ │ │ │ ├── fiber_main.go.tmpl
│ │ │ │ │ └── main.go.tmpl
│ │ │ │ ├── makefile.tmpl
│ │ │ │ ├── routes/
│ │ │ │ │ ├── chi.go.tmpl
│ │ │ │ │ ├── echo.go.tmpl
│ │ │ │ │ ├── fiber.go.tmpl
│ │ │ │ │ ├── gin.go.tmpl
│ │ │ │ │ ├── gorilla.go.tmpl
│ │ │ │ │ ├── http_router.go.tmpl
│ │ │ │ │ └── standard_library.go.tmpl
│ │ │ │ ├── server/
│ │ │ │ │ ├── fiber.go.tmpl
│ │ │ │ │ └── standard_library.go.tmpl
│ │ │ │ └── tests/
│ │ │ │ ├── default-test.go.tmpl
│ │ │ │ ├── echo-test.go.tmpl
│ │ │ │ ├── fiber-test.go.tmpl
│ │ │ │ └── gin-test.go.tmpl
│ │ │ ├── ginRoutes.go
│ │ │ ├── gorillaRoutes.go
│ │ │ ├── httpRoutes.go
│ │ │ ├── main.go
│ │ │ └── routerRoutes.go
│ │ └── globalEnv.go
│ ├── ui/
│ │ ├── multiInput/
│ │ │ └── multiInput.go
│ │ ├── multiSelect/
│ │ │ └── multiSelect.go
│ │ ├── spinner/
│ │ │ └── spinner.go
│ │ └── textinput/
│ │ ├── textinput.go
│ │ └── textinput_test.go
│ ├── utils/
│ │ ├── utils.go
│ │ └── utils_test.go
│ └── version.go
├── contributors.yml
├── docs/
│ ├── Makefile
│ ├── custom_theme/
│ │ └── main.html
│ ├── docs/
│ │ ├── advanced-flag/
│ │ │ ├── advanced-flag.md
│ │ │ ├── docker.md
│ │ │ ├── goreleaser.md
│ │ │ ├── htmx-templ.md
│ │ │ ├── react-vite.md
│ │ │ ├── tailwind.md
│ │ │ └── websocket.md
│ │ ├── blueprint-core/
│ │ │ ├── db-drivers.md
│ │ │ └── frameworks.md
│ │ ├── blueprint-ui.md
│ │ ├── creating-project/
│ │ │ ├── air.md
│ │ │ ├── makefile.md
│ │ │ └── project-init.md
│ │ ├── endpoints-test/
│ │ │ ├── mongo.md
│ │ │ ├── redis.md
│ │ │ ├── scylladb.md
│ │ │ ├── server.md
│ │ │ ├── sql.md
│ │ │ ├── web.md
│ │ │ └── websocket.md
│ │ ├── index.md
│ │ └── installation.md
│ ├── mkdocs.yml
│ └── requirements.txt
├── go.mod
├── go.sum
├── main.go
└── scripts/
├── completions.sh
└── create-npm-packages.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [melkeydev]
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: Bug Report
description: Found a bug? Please let us know!
title: "[Bug]"
labels: ["Bug"]
body:
- type: markdown
attributes:
value: |
Please provide as much information as possible. This will help us resolve the Bug quickly and accurately.
- type: markdown
attributes:
value: ---
- type: textarea
id: what-happened
attributes:
label: What is the problem?
description: |
Description of what needs to be fixed.
placeholder: Tell us what you see!
validations:
required: true
- type: input
id: os
attributes:
label: Operating System
description: What is the affected operating system?
validations:
required: true
- type: input
id: arch
attributes:
label: Architecture Version (x86, x64, arm, etc)
description: (x86, x64, arm, etc)
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: |
This includes the steps for reproducing the problem, the expected result, and the actual result.
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
- name: Melkeydev Discord
url: https://discord.gg/HHZMSCu
about: Chat with the community.
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: Feature Request
description: Suggest a new idea for go-blueprint.
title: "[Feature Request] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Please provide as much information as possible by filling out form below.
- type: markdown
attributes:
value: ---
- type: textarea
id: idea
attributes:
label: Tell us about your feature request
description: |
Describe what you would like go-blueprint to be able to do.
validations:
required: true
- type: checkboxes
id: disclaimer
attributes:
label: Disclaimer
description: I have verified that this has not been suggested before.
options:
- label: I agree
required: true
================================================
FILE: .github/pull_request_template.md
================================================
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.
## Problem/Feature
Please include a description of the problem or feature this PR is addressing.
## Description of Changes:
- Item 1
- Item 2
## Checklist
- [ ] I have self-reviewed the changes being requested
- [ ] I have updated the documentation (check issue ticket #218)
================================================
FILE: .github/semantic.yml
================================================
titleOnly: true
================================================
FILE: .github/workflows/ci.yml
================================================
name: continuous integration
on:
push:
paths:
- '**.go'
- go.sum
- go.mod
branches-ignore:
- main
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
- name: Deps cache
id: cache-go-deps
uses: actions/cache@v4
env:
cache-name: go-deps-cache
with:
path: ~/godeps
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- if: ${{ steps.cache-go-deps.outputs.cache-hit != 'true' }}
name: List the state of go modules
continue-on-error: true
run: go mod graph
- name: Install dependencies
run: |
go mod tidy
go mod download
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
- name: Run golangci-lint
run: golangci-lint run
- name: Run tests
run: |
go test ./...
================================================
FILE: .github/workflows/docs.yml
================================================
name: Deploy Docs
on:
push:
branches:
- main
jobs:
build-deploy:
name: Build and deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Install dependencies
working-directory: docs
run: make install
- name: Build and deploy to GitHub Pages
working-directory: docs
run: make deploy
# config for custom domain on gh-pages
# - name: Create and push CNAME file to gh-pages
# run: |
# git config --local user.email "actions@github.com"
# git config --local user.name "GitHub Actions"
# git checkout gh-pages
# echo "<domain>" > CNAME
# git add CNAME
# git commit -m "Add CNAME file"
# git push origin gh-pages
================================================
FILE: .github/workflows/generate-linter-advanced.yml
================================================
name: Linting Generated Blueprints Advanced
on:
pull_request: {}
workflow_dispatch: {}
jobs:
framework_matrix:
strategy:
matrix:
framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]
driver: [postgres]
git: [commit]
advanced: [htmx, githubaction, websocket, tailwind, docker, react]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
- name: Commit report
run: |
git config --global user.name 'testname'
git config --global user.email 'testemail@users.noreply.github.com'
- name: Set framework variable
id: set-proejct-directory
run: echo "PROJECT_DIRECTORY=${{ matrix.framework }}" | sed 's/\//-/g' >> $GITHUB_ENV
- name: build templates
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 }}"
- if: ${{ matrix.advanced == 'htmx' || matrix.advanced == 'tailwind' }}
name: Install Templ & gen templates
run: |
go install github.com/a-h/templ/cmd/templ@latest
/home/runner/go/bin/templ generate -path ${{ env.PROJECT_DIRECTORY }}
- name: golangci-lint
run: |
cd ${{ env.PROJECT_DIRECTORY }}
golangci-lint run
- name: remove templates
run: rm -rf ${{ env.PROJECT_DIRECTORY }}
================================================
FILE: .github/workflows/generate-linter-core.yml
================================================
name: Linting Generated Blueprints Core
on:
pull_request: {}
workflow_dispatch: {}
jobs:
framework_matrix:
strategy:
matrix:
framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]
driver: [mysql, postgres, sqlite, mongo, redis, scylla, none]
git: [commit, stage, skip]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
- name: Commit report
run: |
git config --global user.name 'testname'
git config --global user.email 'testemail@users.noreply.github.com'
- name: Set framework variable
id: set-proejct-directory
run: echo "PROJECT_DIRECTORY=${{ matrix.framework }}" | sed 's/\//-/g' >> $GITHUB_ENV
- name: build templates
run: script -q /dev/null -c "go run main.go create -n ${{ env.PROJECT_DIRECTORY }} -f ${{ matrix.framework}} -d ${{ matrix.driver }} -g ${{ matrix.git}}"
- name: golangci-lint
run: |
cd ${{ env.PROJECT_DIRECTORY }}
golangci-lint run
- name: remove templates
run: rm -rf ${{ env.PROJECT_DIRECTORY }}
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: npm-publish
on:
workflow_call:
inputs:
tag:
description: "Release tag to publish (e.g., v1.0.0)"
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish (e.g., v1.0.0)"
required: true
type: string
jobs:
npm-publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
- name: Download release assets
run: |
TAG="${{ inputs.tag }}"
VERSION=${TAG#v}
mkdir -p dist
gh release download "$TAG" --dir dist
echo "VERSION=$VERSION" >> $GITHUB_ENV
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create npm packages
run: |
chmod +x ./scripts/create-npm-packages.sh
./scripts/create-npm-packages.sh "$VERSION"
- name: Publish platform-specific packages to npm
run: |
for platform_dir in platform-packages/go-blueprint-*; do
if [ -d "$platform_dir" ]; then
cd "$platform_dir"
npm publish --provenance --access public
cd - > /dev/null
fi
done
- name: Publish main package to npm
run: |
cd npm-package
npm publish --provenance --access public
================================================
FILE: .github/workflows/release.yml
================================================
name: goreleaser
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21.1"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5.0.0
with:
distribution: goreleaser
version: ${{ env.GITHUB_REF_NAME }}
args: release --clean
workdir: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
npm-publish:
needs: goreleaser
permissions:
contents: read
id-token: write
uses: ./.github/workflows/npm-publish.yml
with:
tag: ${{ github.ref_name }}
secrets: inherit
================================================
FILE: .github/workflows/testcontainers.yml
================================================
name: Integrations Test for the Generated Blueprints
on:
pull_request: {}
workflow_dispatch: {}
jobs:
itests_matrix:
strategy:
matrix:
driver:
[mysql, postgres, mongo, redis, scylla]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
- name: Commit report
run: |
git config --global user.name 'testname'
git config --global user.email 'testemail@users.noreply.github.com'
- name: build ${{ matrix.driver }} template
run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -g commit -f fiber -d ${{matrix.driver}}"
- name: run ${{ matrix.driver }} integration tests
working-directory: ${{ matrix.driver }}
run: make itest
- name: remove ${{ matrix.driver }} template
run: rm -rf ${{ matrix.driver }}
================================================
FILE: .github/workflows/update-htmx-version.yml
================================================
name: Check for new htmx release
on:
schedule:
- cron: '0 0 * * Sun'
jobs:
check_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get version from file
id: get_version_file
run: |
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}')
echo "version file: $VERSION_FILE"
if [[ "$VERSION_FILE" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "version_file=$VERSION_FILE" >> $GITHUB_OUTPUT
else
echo "Invalid VERSION_FILE format: $VERSION_FILE" >&2
exit 1
fi
- name: Get version from GitHub API
id: get_version_api
run: |
VERSION_API=$(curl -s https://api.github.com/repos/bigskysoftware/htmx/releases/latest | jq -r '.tag_name')
echo "version api: $VERSION_API"
if [[ "$VERSION_API" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "version_api=$VERSION_API" >> $GITHUB_OUTPUT
else
echo "Invalid VERSION_API format: $VERSION_API" >&2
exit 1
fi
- name: Compare versions
id: compare_versions
run: |
if [ "${{ steps.get_version_api.outputs.version_api }}" != "${{ steps.get_version_file.outputs.version_file }}" ]; then
echo "release_changed=true" >> $GITHUB_OUTPUT
echo "Release changed: true"
else
echo "release_changed=false" >> $GITHUB_OUTPUT
echo "Release changed: false"
fi
- name: dump latest htmx version
if: steps.compare_versions.outputs.release_changed == 'true'
run: curl -L https://github.com/bigskysoftware/htmx/releases/latest/download/htmx.min.js -o cmd/template/advanced/files/htmx/htmx.min.js
- name: Prettify code
if: steps.compare_versions.outputs.release_changed == 'true'
run: |
npm install --save-dev --save-exact prettier
npx prettier --write cmd/template/advanced/files/htmx/htmx.min.js
rm -rf node_modules
rm package-lock.json
rm package.json
- name: Create tmpl after Prettify
if: steps.compare_versions.outputs.release_changed == 'true'
run: mv cmd/template/advanced/files/htmx/htmx.min.js cmd/template/advanced/files/htmx/htmx.min.js.tmpl
- name: Create Pull Request
if: steps.compare_versions.outputs.release_changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
commit-message: update htmx version ${{ steps.get_version_api.outputs.version_api }}
title: Update htmx to version ${{ steps.get_version_api.outputs.version_api }} [Bot]
body: New htmx ${{ steps.get_version_api.outputs.version_api }} version is available. This is an automatic PR to update changes.
branch: htmx-version-update
base: main
================================================
FILE: .gitignore
================================================
go-blueprint
site
================================================
FILE: .goreleaser.yml
================================================
before:
hooks:
- go mod tidy
- ./scripts/completions.sh
builds:
- binary: go-blueprint
main: ./
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X github.com/melkeydev/go-blueprint/cmd.GoBlueprintVersion={{.Version}}
release:
prerelease: auto
universal_binaries:
- replace: true
archives:
- name_template: >-
{{- .ProjectName }}_ {{- .Version }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end -}}
format_overrides:
- goos: windows
format: zip
builds_info:
group: root
owner: root
files:
- README.md
- LICENSE
- completions/*
checksum:
name_template: "checksums.txt"
================================================
FILE: .pre-commit-config.yaml
================================================
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- repo: https://github.com/golangci/golangci-lint
rev: v1.55.2
hooks:
- id: golangci-lint
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Thanks for helping make go-blueprint better!
- [Contributing](#contributing)
- [Design Principles](#design-principles)
- [Report an Issue](#report-an-issue)
- [Contributing Code with Pull Requests](#contributing-code-with-pull-requests)
- [Requirements](#requirements)
- [Licensing](#licensing)
## Design Principles
Contributions to go-blueprint should align with the project’s design principles:
* Maintain backwards compatibility whenever possible.
## Report an Issue
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).
## Contributing Code with Pull Requests
go-blueprint uses [Github pull requests](https://github.com/Melkeydev/go-blueprint/pulls). Feel free to fork, hack away at your changes and submit.
### Requirements
* All commands and functionality should be documented appropriately
* All new functionality/features should have appropriate unit testing
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.
## Licensing
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.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2023 Melkeydev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

<div style="text-align: center;">
<h1>
Introducing the Ultimate Golang Blueprint Library
</h1>
</div>
Go Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly. It also
gives the option to integrate with one of the more popular Go frameworks (and the list is growing with new features)!
### Why Would I use this?
- Easy to set up and install
- Have the entire Go structure already established
- Setting up a Go HTTP server (or Fasthttp with Fiber)
- Integrate with a popular frameworks
- Focus on the actual code of your application
## Table of Contents
- [Install](#install)
- [Frameworks Supported](#frameworks-supported)
- [Database Support](#database-support)
- [Advanced Features](#advanced-features)
- [Blueprint UI](#blueprint-ui)
- [Usage Example](#usage-example)
- [GitHub Stats](#github-stats)
- [License](#license)
<a id="install"></a>
<h2>
<picture>
<img src="./public/install.gif?raw=true" width="60px" style="margin-right: 1px;">
</picture>
Install
</h2>
### Go Install
```bash
go install github.com/melkeydev/go-blueprint@latest
```
This installs a go binary that will automatically bind to your $GOPATH
> if you’re using Zsh, you’ll need to add it manually to `~/.zshrc`.
```bash
GOPATH=$HOME/go PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
```
don't forget to update
```bash
source ~/.zshrc
```
### NPM Install
```bash
npm install -g @melkeydev/go-blueprint
```
### Homebrew Install
```bash
brew install go-blueprint
```
Then in a new terminal run:
```bash
go-blueprint create
```
You can also use the provided flags to set up a project without interacting with the UI.
```bash
go-blueprint create --name my-project --framework gin --driver postgres --git commit
```
See `go-blueprint create -h` for all the options and shorthands.
<a id="frameworks-supported"></a>
<h2>
<picture>
<img src="./public/frameworks.gif?raw=true" width="60px" style="margin-right: 1px;">
</picture>
Frameworks Supported
</h2>
- [Chi](https://github.com/go-chi/chi)
- [Gin](https://github.com/gin-gonic/gin)
- [Fiber](https://github.com/gofiber/fiber)
- [HttpRouter](https://github.com/julienschmidt/httprouter)
- [Gorilla/mux](https://github.com/gorilla/mux)
- [Echo](https://github.com/labstack/echo)
<a id="database-support"></a>
<h2>
<picture>
<img src="./public/database.gif?raw=true" width="45px" style="margin-right: 15px;">
</picture>
Database Support
</h2>
Go Blueprint now offers enhanced database support, allowing you to choose your preferred database driver during project setup. Use the `--driver` or `-d` flag to specify the database driver you want to integrate into your project.
### Supported Database Drivers
Choose from a variety of supported database drivers:
- [Mysql](https://github.com/go-sql-driver/mysql)
- [Postgres](https://github.com/jackc/pgx/)
- [Sqlite](https://github.com/mattn/go-sqlite3)
- [Mongo](https://go.mongodb.org/mongo-driver)
- [Redis](https://github.com/redis/go-redis)
- [ScyllaDB GoCQL](https://github.com/scylladb/gocql)
<a id="advanced-features"></a>
<h2>
<picture>
<img src="./public/advanced.gif?raw=true" width="70px" style="margin-right: 1px;">
</picture>
Advanced Features
</h2>
Blueprint is focused on being as minimalistic as possible. That being said, we wanted to offer the ability to add other features people may want without bloating the overall experience.
You can now use the `--advanced` flag when running the `create` command to get access to the following features. This is a multi-option prompt; one or more features can be used at the same time:
- [HTMX](https://htmx.org/) support using [Templ](https://templ.guide/)
- CI/CD workflow setup using [Github Actions](https://docs.github.com/en/actions)
- [Websocket](https://pkg.go.dev/github.com/coder/websocket) sets up a websocket endpoint
- [Tailwind](https://tailwindcss.com/) css framework
- Docker configuration for go project
- [React](https://react.dev/) frontend written in TypeScript, including an example fetch request to the backend
Note: Selecting Tailwind option will automatically select HTMX unless React is explicitly selected
<a id="blueprint-ui"></a>
<h2>
<picture>
<img src="./public/ui.gif?raw=true" width="100px" style="margin-right: 1px;">
</picture>
Blueprint UI
</h2>
Blueprint UI is a web application that allows you to create commands for the CLI and preview the structure of your project. You will be able to see directories and files that will be created upon command execution. Check it out at [go-blueprint.dev](https://go-blueprint.dev)
<a id="usage-example"></a>
<h2>
<picture>
<img src="./public/example.gif?raw=true" width="60px" style="margin-right: 1px;">
</picture>
Usage Example
</h2>
Here's an example of setting up a project with a specific database driver:
```bash
go-blueprint create --name my-project --framework gin --driver postgres --git commit
```
<p align="center">
<img src="./public/blueprint_1.png" alt="Starter Image" width="800"/>
</p>
Advanced features are accessible with the --advanced flag
```bash
go-blueprint create --advanced
```
Advanced features can be enabled using the `--feature` flag along with the `--advanced` flag.
HTMX:
```bash
go-blueprint create --advanced --feature htmx
```
CI/CD workflow:
```bash
go-blueprint create --advanced --feature githubaction
```
Websocket:
```bash
go-blueprint create --advanced --feature websocket
```
Tailwind:
```bash
go-blueprint create --advanced --feature tailwind
```
Docker:
```bash
go-blueprint create --advanced --feature docker
```
React:
```bash
go-blueprint create --advanced --feature react
```
Or all features at once:
```bash
go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker --git commit --feature react
```
<p align="center">
<img src="./public/blueprint_advanced.png" alt="Advanced Options" width="800"/>
</p>
**Visit [documentation](https://docs.go-blueprint.dev) to learn more about blueprint and its features.**
<a id="github-stats"></a>
<h2>
<picture>
<img src="./public/stats.gif?raw=true" width="45px" style="margin-right: 10px;">
</picture>
GitHub Stats
</h2>
<p align="center">
<img alt="Alt" src="https://repobeats.axiom.co/api/embed/7c4be18864d441f961be61186ce49b5471a9e7bf.svg" title="Repobeats analytics image"/>
</p>
<a id="license"></a>
<h2>
<picture>
<img src="./public/license.gif?raw=true" width="50px" style="margin-right: 1px;">
</picture>
License
</h2>
Licensed under [MIT License](./LICENSE)
================================================
FILE: cmd/create.go
================================================
package cmd
import (
"fmt"
"log"
"os"
"strings"
"sync"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/melkeydev/go-blueprint/cmd/flags"
"github.com/melkeydev/go-blueprint/cmd/program"
"github.com/melkeydev/go-blueprint/cmd/steps"
"github.com/melkeydev/go-blueprint/cmd/ui/multiInput"
"github.com/melkeydev/go-blueprint/cmd/ui/multiSelect"
"github.com/melkeydev/go-blueprint/cmd/ui/spinner"
"github.com/melkeydev/go-blueprint/cmd/ui/textinput"
"github.com/melkeydev/go-blueprint/cmd/utils"
"github.com/spf13/cobra"
)
const logo = `
____ _ _ _
| _ \| | (_) | |
| |_) | |_ _ ___ _ __ _ __ _ _ __ | |_
| _ <| | | | |/ _ \ '_ \| '__| | '_ \| __|
| |_) | | |_| | __/ |_) | | | | | | | |_
|____/|_|\__,_|\___| .__/|_| |_|_| |_|\__|
| |
|_|
`
var (
logoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#01FAC6")).Bold(true)
tipMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("190")).Italic(true)
endingMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("170")).Bold(true)
)
func init() {
var flagFramework flags.Framework
var flagDBDriver flags.Database
var advancedFeatures flags.AdvancedFeatures
var flagGit flags.Git
rootCmd.AddCommand(createCmd)
createCmd.Flags().StringP("name", "n", "", "Name of project to create")
createCmd.Flags().VarP(&flagFramework, "framework", "f", fmt.Sprintf("Framework to use. Allowed values: %s", strings.Join(flags.AllowedProjectTypes, ", ")))
createCmd.Flags().VarP(&flagDBDriver, "driver", "d", fmt.Sprintf("Database drivers to use. Allowed values: %s", strings.Join(flags.AllowedDBDrivers, ", ")))
createCmd.Flags().BoolP("advanced", "a", false, "Get prompts for advanced features")
createCmd.Flags().Var(&advancedFeatures, "feature", fmt.Sprintf("Advanced feature to use. Allowed values: %s", strings.Join(flags.AllowedAdvancedFeatures, ", ")))
createCmd.Flags().VarP(&flagGit, "git", "g", fmt.Sprintf("Git to use. Allowed values: %s", strings.Join(flags.AllowedGitsOptions, ", ")))
utils.RegisterStaticCompletions(createCmd, "framework", flags.AllowedProjectTypes)
utils.RegisterStaticCompletions(createCmd, "driver", flags.AllowedDBDrivers)
utils.RegisterStaticCompletions(createCmd, "feature", flags.AllowedAdvancedFeatures)
utils.RegisterStaticCompletions(createCmd, "git", flags.AllowedGitsOptions)
}
type Options struct {
ProjectName *textinput.Output
ProjectType *multiInput.Selection
DBDriver *multiInput.Selection
Advanced *multiSelect.Selection
Workflow *multiInput.Selection
Git *multiInput.Selection
}
// createCmd defines the "create" command for the CLI
var createCmd = &cobra.Command{
Use: "create",
Short: "Create a Go project and don't worry about the structure",
Long: "Go Blueprint is a CLI tool that allows you to focus on the actual Go code, and not the project structure. Perfect for someone new to the Go language",
Run: func(cmd *cobra.Command, args []string) {
var tprogram *tea.Program
var err error
isInteractive := false
flagName := cmd.Flag("name").Value.String()
if flagName != "" && !utils.ValidateModuleName(flagName) {
err = fmt.Errorf("'%s' is not a valid module name. Please choose a different name", flagName)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
rootDirName := utils.GetRootDir(flagName)
if rootDirName != "" && doesDirectoryExistAndIsNotEmpty(rootDirName) {
err = fmt.Errorf("directory '%s' already exists and is not empty. Please choose a different name", rootDirName)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
// VarP already validates the contents of the framework flag.
// If this flag is filled, it is always valid
flagFramework := flags.Framework(cmd.Flag("framework").Value.String())
flagDBDriver := flags.Database(cmd.Flag("driver").Value.String())
flagGit := flags.Git(cmd.Flag("git").Value.String())
options := Options{
ProjectName: &textinput.Output{},
ProjectType: &multiInput.Selection{},
DBDriver: &multiInput.Selection{},
Advanced: &multiSelect.Selection{
Choices: make(map[string]bool),
},
Git: &multiInput.Selection{},
}
project := &program.Project{
ProjectName: flagName,
ProjectType: flagFramework,
DBDriver: flagDBDriver,
FrameworkMap: make(map[flags.Framework]program.Framework),
DBDriverMap: make(map[flags.Database]program.Driver),
AdvancedOptions: make(map[string]bool),
GitOptions: flagGit,
}
steps := steps.InitSteps(flagFramework, flagDBDriver)
fmt.Printf("%s\n", logoStyle.Render(logo))
// Advanced option steps:
flagAdvanced, err := cmd.Flags().GetBool("advanced")
if err != nil {
log.Fatal("failed to retrieve advanced flag")
}
if flagAdvanced {
fmt.Println(tipMsgStyle.Render("*** You are in advanced mode ***\n\n"))
}
if project.ProjectName == "" {
isInteractive = true
tprogram := tea.NewProgram(textinput.InitialTextInputModel(options.ProjectName, "What is the name of your project?", project))
if _, err := tprogram.Run(); err != nil {
log.Printf("Name of project contains an error: %v", err)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
if options.ProjectName.Output != "" && !utils.ValidateModuleName(options.ProjectName.Output) {
err = fmt.Errorf("'%s' is not a valid module name. Please choose a different name", options.ProjectName.Output)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
rootDirName = utils.GetRootDir(options.ProjectName.Output)
if doesDirectoryExistAndIsNotEmpty(rootDirName) {
err = fmt.Errorf("directory '%s' already exists and is not empty. Please choose a different name", rootDirName)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.ExitCLI(tprogram)
project.ProjectName = options.ProjectName.Output
err := cmd.Flag("name").Value.Set(project.ProjectName)
if err != nil {
log.Fatal("failed to set the name flag value", err)
}
}
if project.ProjectType == "" {
isInteractive = true
step := steps.Steps["framework"]
tprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.ProjectType, step.Headers, project))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.ExitCLI(tprogram)
step.Field = options.ProjectType.Choice
// this type casting is always safe since the user interface can
// only pass strings that can be cast to a flags.Framework instance
project.ProjectType = flags.Framework(strings.ToLower(options.ProjectType.Choice))
err := cmd.Flag("framework").Value.Set(project.ProjectType.String())
if err != nil {
log.Fatal("failed to set the framework flag value", err)
}
}
if project.DBDriver == "" {
isInteractive = true
step := steps.Steps["driver"]
tprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.DBDriver, step.Headers, project))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.ExitCLI(tprogram)
// this type casting is always safe since the user interface can
// only pass strings that can be cast to a flags.Database instance
project.DBDriver = flags.Database(strings.ToLower(options.DBDriver.Choice))
err := cmd.Flag("driver").Value.Set(project.DBDriver.String())
if err != nil {
log.Fatal("failed to set the driver flag value", err)
}
}
if flagAdvanced {
featureFlags := cmd.Flag("feature").Value.String()
if featureFlags != "" {
featuresFlagValues := strings.Split(featureFlags, ",")
for _, key := range featuresFlagValues {
project.AdvancedOptions[key] = true
}
} else {
isInteractive = true
step := steps.Steps["advanced"]
tprogram = tea.NewProgram((multiSelect.InitialModelMultiSelect(step.Options, options.Advanced, step.Headers, project)))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.ExitCLI(tprogram)
for key, opt := range options.Advanced.Choices {
project.AdvancedOptions[strings.ToLower(key)] = opt
err := cmd.Flag("feature").Value.Set(strings.ToLower(key))
if err != nil {
log.Fatal("failed to set the feature flag value", err)
}
}
if err != nil {
log.Fatal("failed to set the htmx option", err)
}
}
}
if project.GitOptions == "" {
isInteractive = true
step := steps.Steps["git"]
tprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.Git, step.Headers, project))
if _, err := tprogram.Run(); err != nil {
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.ExitCLI(tprogram)
project.GitOptions = flags.Git(strings.ToLower(options.Git.Choice))
err := cmd.Flag("git").Value.Set(project.GitOptions.String())
if err != nil {
log.Fatal("failed to set the git flag value", err)
}
}
currentWorkingDir, err := os.Getwd()
if err != nil {
log.Printf("could not get current working directory: %v", err)
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
project.AbsolutePath = currentWorkingDir
spinner := tea.NewProgram(spinner.InitialModelNew())
// add synchronization to wait for spinner to finish
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
if _, err := spinner.Run(); err != nil {
cobra.CheckErr(err)
}
}()
defer func() {
if r := recover(); r != nil {
fmt.Println("The program encountered an unexpected issue and had to exit. The error was:", r)
fmt.Println("If you continue to experience this issue, please post a message on our GitHub page or join our Discord server for support.")
if releaseErr := spinner.ReleaseTerminal(); releaseErr != nil {
log.Printf("Problem releasing terminal: %v", releaseErr)
}
}
}()
// This calls the templates
err = project.CreateMainFile()
if err != nil {
if releaseErr := spinner.ReleaseTerminal(); releaseErr != nil {
log.Printf("Problem releasing terminal: %v", releaseErr)
}
log.Printf("Problem creating files for project.")
cobra.CheckErr(textinput.CreateErrorInputModel(err).Err())
}
fmt.Println(endingMsgStyle.Render("\nNext steps:"))
fmt.Println(endingMsgStyle.Render(fmt.Sprintf("• cd into the newly created project with: `cd %s`\n", utils.GetRootDir(project.ProjectName))))
if options.Advanced.Choices["React"] {
options.Advanced.Choices["Htmx"] = false
options.Advanced.Choices["Tailwind"] = false
fmt.Println(endingMsgStyle.Render("• cd into frontend\n"))
fmt.Println(endingMsgStyle.Render("• npm install\n"))
fmt.Println(endingMsgStyle.Render("• npm run dev\n"))
}
if options.Advanced.Choices["Tailwind"] {
options.Advanced.Choices["Htmx"] = true
fmt.Println(endingMsgStyle.Render("• Install the tailwind standalone cli if you haven't already, grab the executable for your platform from the latest release on GitHub\n"))
fmt.Println(endingMsgStyle.Render("• More info about the Tailwind CLI: https://tailwindcss.com/blog/standalone-cli\n"))
}
if options.Advanced.Choices["Htmx"] {
options.Advanced.Choices["react"] = false
fmt.Println(endingMsgStyle.Render("• Install the templ cli if you haven't already by running `go install github.com/a-h/templ/cmd/templ@latest`\n"))
fmt.Println(endingMsgStyle.Render("• Generate templ function files by running `templ generate`\n"))
}
if isInteractive {
nonInteractiveCommand := utils.NonInteractiveCommand(cmd.Use, cmd.Flags())
fmt.Println(tipMsgStyle.Render("Tip: Repeat the equivalent Blueprint with the following non-interactive command:"))
fmt.Println(tipMsgStyle.Italic(false).Render(fmt.Sprintf("• %s\n", nonInteractiveCommand)))
}
err = spinner.ReleaseTerminal()
if err != nil {
log.Printf("Could not release terminal: %v", err)
cobra.CheckErr(err)
}
},
}
// doesDirectoryExistAndIsNotEmpty checks if the directory exists and is not empty
func doesDirectoryExistAndIsNotEmpty(name string) bool {
if _, err := os.Stat(name); err == nil {
dirEntries, err := os.ReadDir(name)
if err != nil {
log.Printf("could not read directory: %v", err)
cobra.CheckErr(textinput.CreateErrorInputModel(err))
}
if len(dirEntries) > 0 {
return true
}
}
return false
}
================================================
FILE: cmd/flags/advancedFeatures.go
================================================
package flags
import (
"fmt"
"strings"
)
type AdvancedFeatures []string
const (
Htmx string = "htmx"
GoProjectWorkflow string = "githubaction"
Websocket string = "websocket"
Tailwind string = "tailwind"
React string = "react"
Docker string = "docker"
)
var AllowedAdvancedFeatures = []string{string(React), string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Docker)}
func (f AdvancedFeatures) String() string {
return strings.Join(f, ",")
}
func (f *AdvancedFeatures) Type() string {
return "AdvancedFeatures"
}
func (f *AdvancedFeatures) Set(value string) error {
// Contains isn't available in 1.20 yet
// if AdvancedFeatures.Contains(value) {
for _, advancedFeature := range AllowedAdvancedFeatures {
if advancedFeature == value {
*f = append(*f, advancedFeature)
return nil
}
}
return fmt.Errorf("advanced Feature to use. Allowed values: %s", strings.Join(AllowedAdvancedFeatures, ", "))
}
================================================
FILE: cmd/flags/database.go
================================================
package flags
import (
"fmt"
"strings"
)
type Database string
// These are all the current databases supported. If you want to add one, you
// can simply copy and paste a line here. Do not forget to also add it into the
// AllowedDBDrivers slice too!
const (
MySql Database = "mysql"
Postgres Database = "postgres"
Sqlite Database = "sqlite"
Mongo Database = "mongo"
Redis Database = "redis"
Scylla Database = "scylla"
None Database = "none"
)
var AllowedDBDrivers = []string{string(MySql), string(Postgres), string(Sqlite), string(Mongo), string(Redis), string(Scylla), string(None)}
func (f Database) String() string {
return string(f)
}
func (f *Database) Type() string {
return "Database"
}
func (f *Database) Set(value string) error {
// Contains isn't available in 1.20 yet
// if AllowedDBDrivers.Contains(value) {
for _, database := range AllowedDBDrivers {
if database == value {
*f = Database(value)
return nil
}
}
return fmt.Errorf("Database to use. Allowed values: %s", strings.Join(AllowedDBDrivers, ", "))
}
================================================
FILE: cmd/flags/frameworks.go
================================================
package flags
import (
"fmt"
"strings"
)
type Framework string
// These are all the current frameworks supported. If you want to add one, you
// can simply copy and paste a line here. Do not forget to also add it into the
// AllowedProjectTypes slice too!
const (
Chi Framework = "chi"
Gin Framework = "gin"
Fiber Framework = "fiber"
GorillaMux Framework = "gorilla/mux"
HttpRouter Framework = "httprouter"
StandardLibrary Framework = "standard-library"
Echo Framework = "echo"
)
var AllowedProjectTypes = []string{string(Chi), string(Gin), string(Fiber), string(GorillaMux), string(HttpRouter), string(StandardLibrary), string(Echo)}
func (f Framework) String() string {
return string(f)
}
func (f *Framework) Type() string {
return "Framework"
}
func (f *Framework) Set(value string) error {
// Contains isn't available in 1.20 yet
// if AllowedProjectTypes.Contains(value) {
for _, project := range AllowedProjectTypes {
if project == value {
*f = Framework(value)
return nil
}
}
return fmt.Errorf("Framework to use. Allowed values: %s", strings.Join(AllowedProjectTypes, ", "))
}
================================================
FILE: cmd/flags/git.go
================================================
package flags
import (
"fmt"
"strings"
)
type Git string
const (
Commit = "commit"
Stage = "stage"
Skip = "skip"
)
var AllowedGitsOptions = []string{string(Commit), string(Stage), string(Skip)}
func (f Git) String() string {
return string(f)
}
func (f *Git) Type() string {
return "Git"
}
func (f *Git) Set(value string) error {
for _, gitOption := range AllowedGitsOptions {
if gitOption == value {
*f = Git(value)
return nil
}
}
return fmt.Errorf("Git to use. Allowed values: %s", strings.Join(AllowedGitsOptions, ", "))
}
================================================
FILE: cmd/program/program.go
================================================
// Package program provides the
// main functionality of Blueprint
package program
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
tea "github.com/charmbracelet/bubbletea"
"github.com/melkeydev/go-blueprint/cmd/flags"
tpl "github.com/melkeydev/go-blueprint/cmd/template"
"github.com/melkeydev/go-blueprint/cmd/template/advanced"
"github.com/melkeydev/go-blueprint/cmd/template/dbdriver"
"github.com/melkeydev/go-blueprint/cmd/template/docker"
"github.com/melkeydev/go-blueprint/cmd/template/framework"
"github.com/melkeydev/go-blueprint/cmd/utils"
)
// A Project contains the data for the project folder
// being created, and methods that help with that process
type Project struct {
ProjectName string
Exit bool
AbsolutePath string
ProjectType flags.Framework
DBDriver flags.Database
Docker flags.Database
FrameworkMap map[flags.Framework]Framework
DBDriverMap map[flags.Database]Driver
DockerMap map[flags.Database]Docker
AdvancedOptions map[string]bool
AdvancedTemplates AdvancedTemplates
GitOptions flags.Git
OSCheck map[string]bool
}
type AdvancedTemplates struct {
TemplateRoutes string
TemplateImports string
}
// A Framework contains the name and templater for a
// given Framework
type Framework struct {
packageName []string
templater Templater
}
type Driver struct {
packageName []string
templater DBDriverTemplater
}
type Docker struct {
packageName []string
templater DockerTemplater
}
// A Templater has the methods that help build the files
// in the Project folder, and is specific to a Framework
type Templater interface {
Main() []byte
Server() []byte
Routes() []byte
TestHandler() []byte
HtmxTemplRoutes() []byte
HtmxTemplImports() []byte
WebsocketImports() []byte
}
type DBDriverTemplater interface {
Service() []byte
Env() []byte
Tests() []byte
}
type DockerTemplater interface {
Docker() []byte
}
type WorkflowTemplater interface {
Releaser() []byte
Test() []byte
ReleaserConfig() []byte
}
var (
chiPackage = []string{"github.com/go-chi/chi/v5"}
gorillaPackage = []string{"github.com/gorilla/mux"}
routerPackage = []string{"github.com/julienschmidt/httprouter"}
ginPackage = []string{"github.com/gin-gonic/gin"}
fiberPackage = []string{"github.com/gofiber/fiber/v2"}
echoPackage = []string{"github.com/labstack/echo/v4", "github.com/labstack/echo/v4/middleware"}
mysqlDriver = []string{"github.com/go-sql-driver/mysql"}
postgresDriver = []string{"github.com/jackc/pgx/v5/stdlib"}
sqliteDriver = []string{"github.com/mattn/go-sqlite3"}
redisDriver = []string{"github.com/redis/go-redis/v9"}
mongoDriver = []string{"go.mongodb.org/mongo-driver"}
gocqlDriver = []string{"github.com/gocql/gocql"}
scyllaDriver = "github.com/scylladb/gocql@v1.14.4" // Replacement for GoCQL
godotenvPackage = []string{"github.com/joho/godotenv"}
templPackage = []string{"github.com/a-h/templ"}
)
const (
root = "/"
cmdApiPath = "cmd/api"
cmdWebPath = "cmd/web"
internalServerPath = "internal/server"
internalDatabasePath = "internal/database"
gitHubActionPath = ".github/workflows"
)
// CheckOs checks Operation system and generates MakeFile and `go build` command
// Based on Project.Unixbase
func (p *Project) CheckOS() {
p.OSCheck = make(map[string]bool)
if runtime.GOOS != "windows" {
p.OSCheck["UnixBased"] = true
}
if runtime.GOOS == "linux" {
p.OSCheck["linux"] = true
}
if runtime.GOOS == "darwin" {
p.OSCheck["darwin"] = true
}
}
// ExitCLI checks if the Project has been exited, and closes
// out of the CLI if it has
func (p *Project) ExitCLI(tprogram *tea.Program) {
if p.Exit {
// logo render here
if err := tprogram.ReleaseTerminal(); err != nil {
log.Fatal(err)
}
os.Exit(1)
}
}
// createFrameWorkMap adds the current supported
// Frameworks into a Project's FrameworkMap
func (p *Project) createFrameworkMap() {
p.FrameworkMap[flags.Chi] = Framework{
packageName: chiPackage,
templater: framework.ChiTemplates{},
}
p.FrameworkMap[flags.StandardLibrary] = Framework{
packageName: []string{},
templater: framework.StandardLibTemplate{},
}
p.FrameworkMap[flags.Gin] = Framework{
packageName: ginPackage,
templater: framework.GinTemplates{},
}
p.FrameworkMap[flags.Fiber] = Framework{
packageName: fiberPackage,
templater: framework.FiberTemplates{},
}
p.FrameworkMap[flags.GorillaMux] = Framework{
packageName: gorillaPackage,
templater: framework.GorillaTemplates{},
}
p.FrameworkMap[flags.HttpRouter] = Framework{
packageName: routerPackage,
templater: framework.RouterTemplates{},
}
p.FrameworkMap[flags.Echo] = Framework{
packageName: echoPackage,
templater: framework.EchoTemplates{},
}
}
func (p *Project) createDBDriverMap() {
p.DBDriverMap[flags.MySql] = Driver{
packageName: mysqlDriver,
templater: dbdriver.MysqlTemplate{},
}
p.DBDriverMap[flags.Postgres] = Driver{
packageName: postgresDriver,
templater: dbdriver.PostgresTemplate{},
}
p.DBDriverMap[flags.Sqlite] = Driver{
packageName: sqliteDriver,
templater: dbdriver.SqliteTemplate{},
}
p.DBDriverMap[flags.Mongo] = Driver{
packageName: mongoDriver,
templater: dbdriver.MongoTemplate{},
}
p.DBDriverMap[flags.Redis] = Driver{
packageName: redisDriver,
templater: dbdriver.RedisTemplate{},
}
p.DBDriverMap[flags.Scylla] = Driver{
packageName: gocqlDriver,
templater: dbdriver.ScyllaTemplate{},
}
}
func (p *Project) createDockerMap() {
p.DockerMap = make(map[flags.Database]Docker)
p.DockerMap[flags.MySql] = Docker{
packageName: []string{},
templater: docker.MysqlDockerTemplate{},
}
p.DockerMap[flags.Postgres] = Docker{
packageName: []string{},
templater: docker.PostgresDockerTemplate{},
}
p.DockerMap[flags.Mongo] = Docker{
packageName: []string{},
templater: docker.MongoDockerTemplate{},
}
p.DockerMap[flags.Redis] = Docker{
packageName: []string{},
templater: docker.RedisDockerTemplate{},
}
p.DockerMap[flags.Scylla] = Docker{
packageName: []string{},
templater: docker.ScyllaDockerTemplate{},
}
}
// CreateMainFile creates the project folders and files,
// and writes to them depending on the selected options
func (p *Project) CreateMainFile() error {
// check if AbsolutePath exists
if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) {
// create directory
if err := os.Mkdir(p.AbsolutePath, 0o754); err != nil {
log.Printf("Could not create directory: %v", err)
return err
}
}
// Check if user.email is set.
if p.GitOptions.String() != flags.Skip {
emailSet, err := utils.CheckGitConfig("user.email")
if err != nil {
return err
}
if !emailSet {
fmt.Println("user.email is not set in git config.")
fmt.Println("Please set up git config before trying again.")
panic("\nGIT CONFIG ISSUE: user.email is not set in git config.\n")
}
}
p.ProjectName = strings.TrimSpace(p.ProjectName)
// Create a new directory with the project name
projectPath := filepath.Join(p.AbsolutePath, utils.GetRootDir(p.ProjectName))
if _, err := os.Stat(projectPath); os.IsNotExist(err) {
err := os.MkdirAll(projectPath, 0o751)
if err != nil {
log.Printf("Error creating root project directory %v\n", err)
return err
}
}
// Define Operating system
p.CheckOS()
// Create the map for our program
p.createFrameworkMap()
// Create go.mod
err := utils.InitGoMod(p.ProjectName, projectPath)
if err != nil {
log.Printf("Could not initialize go.mod in new project %v\n", err)
return err
}
// Install the correct package for the selected framework
if p.ProjectType != flags.StandardLibrary {
err = utils.GoGetPackage(projectPath, p.FrameworkMap[p.ProjectType].packageName)
if err != nil {
log.Println("Could not install go dependency for the chosen framework")
return err
}
}
// Install the correct package for the selected driver
if p.DBDriver != "none" {
p.createDBDriverMap()
err = utils.GoGetPackage(projectPath, p.DBDriverMap[p.DBDriver].packageName)
if err != nil {
log.Println("Could not install go dependency for chosen driver")
return err
}
err = p.CreatePath(internalDatabasePath, projectPath)
if err != nil {
log.Printf("Error creating path: %s", internalDatabasePath)
return err
}
err = p.CreateFileWithInjection(internalDatabasePath, projectPath, "database.go", "database")
if err != nil {
log.Printf("Error injecting database.go file: %v", err)
return err
}
if p.DBDriver != "sqlite" {
err = p.CreateFileWithInjection(internalDatabasePath, projectPath, "database_test.go", "integration-tests")
if err != nil {
log.Printf("Error injecting database_test.go file: %v", err)
return err
}
}
}
// Create correct docker compose for the selected driver
if p.DBDriver != "none" {
if p.DBDriver != "sqlite" {
p.createDockerMap()
p.Docker = p.DBDriver
err = p.CreateFileWithInjection(root, projectPath, "docker-compose.yml", "db-docker")
if err != nil {
log.Printf("Error injecting docker-compose.yml file: %v", err)
return err
}
} else {
fmt.Println(" SQLite doesn't support docker-compose.yml configuration")
}
}
// Install the godotenv package
err = utils.GoGetPackage(projectPath, godotenvPackage)
if err != nil {
log.Println("Could not install go dependency")
return err
}
if p.DBDriver == flags.Scylla {
replace := fmt.Sprintf("%s=%s", gocqlDriver[0], scyllaDriver)
err = utils.GoModReplace(projectPath, replace)
if err != nil {
log.Printf("Could not replace go dependency %v\n", err)
return err
}
}
err = p.CreatePath(cmdApiPath, projectPath)
if err != nil {
log.Printf("Error creating path: %s", projectPath)
return err
}
err = p.CreateFileWithInjection(cmdApiPath, projectPath, "main.go", "main")
if err != nil {
return err
}
makeFile, err := os.Create(filepath.Join(projectPath, "Makefile"))
if err != nil {
return err
}
defer makeFile.Close()
// inject makefile template
makeFileTemplate := template.Must(template.New("makefile").Parse(string(framework.MakeTemplate())))
err = makeFileTemplate.Execute(makeFile, p)
if err != nil {
return err
}
readmeFile, err := os.Create(filepath.Join(projectPath, "README.md"))
if err != nil {
return err
}
defer readmeFile.Close()
// inject readme template
readmeFileTemplate := template.Must(template.New("readme").Parse(string(framework.ReadmeTemplate())))
err = readmeFileTemplate.Execute(readmeFile, p)
if err != nil {
return err
}
err = p.CreatePath(internalServerPath, projectPath)
if err != nil {
log.Printf("Error creating path: %s", internalServerPath)
return err
}
if p.AdvancedOptions[string(flags.React)] {
// deselect htmx option automatically since react is selected
p.AdvancedOptions[string(flags.Htmx)] = false
if err := p.CreateViteReactProject(projectPath); err != nil {
return fmt.Errorf("failed to set up React project: %w", err)
}
// if everything went smoothly, remove tailwing flag option
p.AdvancedOptions[string(flags.Tailwind)] = false
}
if p.AdvancedOptions[string(flags.Tailwind)] {
// select htmx option automatically since tailwind is selected
p.AdvancedOptions[string(flags.Htmx)] = true
err = os.MkdirAll(fmt.Sprintf("%s/%s/assets/css", projectPath, cmdWebPath), 0o755)
if err != nil {
return err
}
err = os.MkdirAll(fmt.Sprintf("%s/%s/styles", projectPath, cmdWebPath), 0o755)
if err != nil {
return fmt.Errorf("failed to create styles directory: %w", err)
}
inputCssFile, err := os.Create(fmt.Sprintf("%s/%s/styles/input.css", projectPath, cmdWebPath))
if err != nil {
return err
}
defer inputCssFile.Close()
inputCssTemplate := advanced.InputCssTemplate()
err = os.WriteFile(fmt.Sprintf("%s/%s/styles/input.css", projectPath, cmdWebPath), inputCssTemplate, 0o644)
if err != nil {
return err
}
outputCssFile, err := os.Create(fmt.Sprintf("%s/%s/assets/css/output.css", projectPath, cmdWebPath))
if err != nil {
return err
}
defer outputCssFile.Close()
outputCssTemplate := advanced.OutputCssTemplate()
err = os.WriteFile(fmt.Sprintf("%s/%s/assets/css/output.css", projectPath, cmdWebPath), outputCssTemplate, 0o644)
if err != nil {
return err
}
}
if p.AdvancedOptions[string(flags.Htmx)] {
// create folders and hello world file
err = p.CreatePath(cmdWebPath, projectPath)
if err != nil {
return err
}
helloTemplFile, err := os.Create(fmt.Sprintf("%s/%s/hello.templ", projectPath, cmdWebPath))
if err != nil {
return err
}
defer helloTemplFile.Close()
// inject hello.templ template
helloTemplTemplate := template.Must(template.New("hellotempl").Parse((string(advanced.HelloTemplTemplate()))))
err = helloTemplTemplate.Execute(helloTemplFile, p)
if err != nil {
return err
}
baseTemplFile, err := os.Create(fmt.Sprintf("%s/%s/base.templ", projectPath, cmdWebPath))
if err != nil {
return err
}
defer baseTemplFile.Close()
baseTemplTemplate := template.Must(template.New("basetempl").Parse((string(advanced.BaseTemplTemplate()))))
err = baseTemplTemplate.Execute(baseTemplFile, p)
if err != nil {
return err
}
err = os.MkdirAll(fmt.Sprintf("%s/%s/assets/js", projectPath, cmdWebPath), 0o755)
if err != nil {
return err
}
htmxMinJsFile, err := os.Create(fmt.Sprintf("%s/%s/assets/js/htmx.min.js", projectPath, cmdWebPath))
if err != nil {
return err
}
defer htmxMinJsFile.Close()
htmxMinJsTemplate := advanced.HtmxJSTemplate()
err = os.WriteFile(fmt.Sprintf("%s/%s/assets/js/htmx.min.js", projectPath, cmdWebPath), htmxMinJsTemplate, 0o644)
if err != nil {
return err
}
htmxTailwindConfigJsFile, err := os.Create(fmt.Sprintf("%s/tailwind.config.js", projectPath))
if err != nil {
return err
}
defer htmxTailwindConfigJsFile.Close()
htmxTailwindConfigJsTemplate := advanced.HtmxTailwindConfigJsTemplate()
err = os.WriteFile(fmt.Sprintf("%s/tailwind.config.js", projectPath), htmxTailwindConfigJsTemplate, 0o644)
if err != nil {
return err
}
efsFile, err := os.Create(fmt.Sprintf("%s/%s/efs.go", projectPath, cmdWebPath))
if err != nil {
return err
}
defer efsFile.Close()
efsTemplate := template.Must(template.New("efs").Parse((string(advanced.EfsTemplate()))))
err = efsTemplate.Execute(efsFile, p)
if err != nil {
return err
}
err = utils.GoGetPackage(projectPath, templPackage)
if err != nil {
log.Println("Could not install go dependency")
return err
}
helloGoFile, err := os.Create(fmt.Sprintf("%s/%s/hello.go", projectPath, cmdWebPath))
if err != nil {
return err
}
defer efsFile.Close()
if p.ProjectType == "fiber" {
helloGoTemplate := template.Must(template.New("efs").Parse((string(advanced.HelloFiberGoTemplate()))))
err = helloGoTemplate.Execute(helloGoFile, p)
if err != nil {
return err
}
err = utils.GoGetPackage(projectPath, []string{"github.com/gofiber/fiber/v2/middleware/adaptor"})
if err != nil {
log.Println("Could not install go dependency")
return err
}
} else {
helloGoTemplate := template.Must(template.New("efs").Parse((string(advanced.HelloGoTemplate()))))
err = helloGoTemplate.Execute(helloGoFile, p)
if err != nil {
return err
}
}
p.CreateHtmxTemplates()
}
// Create .github/workflows folder and inject release.yml and go-test.yml
if p.AdvancedOptions[string(flags.GoProjectWorkflow)] {
err = p.CreatePath(gitHubActionPath, projectPath)
if err != nil {
log.Printf("Error creating path: %s", gitHubActionPath)
return err
}
err = p.CreateFileWithInjection(gitHubActionPath, projectPath, "release.yml", "releaser")
if err != nil {
log.Printf("Error injecting release.yml file: %v", err)
return err
}
err = p.CreateFileWithInjection(gitHubActionPath, projectPath, "go-test.yml", "go-test")
if err != nil {
log.Printf("Error injecting go-test.yml file: %v", err)
return err
}
err = p.CreateFileWithInjection(root, projectPath, ".goreleaser.yml", "releaser-config")
if err != nil {
log.Printf("Error injecting .goreleaser.yml file: %v", err)
return err
}
}
// if the websocket option is checked, a websocket dependency needs to
// be added to the routes depending on the framework choosen.
// Only fiber uses a different websocket library, the other frameworks
// all work with the same one
if p.AdvancedOptions[string(flags.Websocket)] {
p.CreateWebsocketImports(projectPath)
}
if p.AdvancedOptions[string(flags.Docker)] {
Dockerfile, err := os.Create(filepath.Join(projectPath, "Dockerfile"))
if err != nil {
return err
}
defer Dockerfile.Close()
// inject Docker template
dockerfileTemplate := template.Must(template.New("Dockerfile").Parse(string(advanced.Dockerfile())))
err = dockerfileTemplate.Execute(Dockerfile, p)
if err != nil {
return err
}
if p.DBDriver == "none" || p.DBDriver == "sqlite" {
Dockercompose, err := os.Create(filepath.Join(projectPath, "docker-compose.yml"))
if err != nil {
return err
}
defer Dockercompose.Close()
// inject DockerCompose template
dockerComposeTemplate := template.Must(template.New("docker-compose.yml").Parse(string(advanced.DockerCompose())))
err = dockerComposeTemplate.Execute(Dockercompose, p)
if err != nil {
return err
}
}
}
err = p.CreateFileWithInjection(internalServerPath, projectPath, "routes.go", "routes")
if err != nil {
log.Printf("Error injecting routes.go file: %v", err)
return err
}
err = p.CreateFileWithInjection(internalServerPath, projectPath, "routes_test.go", "tests")
if err != nil {
return err
}
err = p.CreateFileWithInjection(internalServerPath, projectPath, "server.go", "server")
if err != nil {
log.Printf("Error injecting server.go file: %v", err)
return err
}
err = p.CreateFileWithInjection(root, projectPath, ".env", "env")
if err != nil {
log.Printf("Error injecting .env file: %v", err)
return err
}
// Create gitignore
gitignoreFile, err := os.Create(filepath.Join(projectPath, ".gitignore"))
if err != nil {
return err
}
defer gitignoreFile.Close()
// inject gitignore template
gitignoreTemplate := template.Must(template.New(".gitignore").Parse(string(framework.GitIgnoreTemplate())))
err = gitignoreTemplate.Execute(gitignoreFile, p)
if err != nil {
return err
}
// Create .air.toml file
airTomlFile, err := os.Create(filepath.Join(projectPath, ".air.toml"))
if err != nil {
return err
}
defer airTomlFile.Close()
// inject air.toml template
airTomlTemplate := template.Must(template.New("airtoml").Parse(string(framework.AirTomlTemplate())))
err = airTomlTemplate.Execute(airTomlFile, p)
if err != nil {
return err
}
err = utils.GoTidy(projectPath)
if err != nil {
log.Printf("Could not go tidy in new project %v\n", err)
return err
}
err = utils.GoFmt(projectPath)
if err != nil {
log.Printf("Could not gofmt in new project %v\n", err)
return err
}
if p.GitOptions != flags.Skip {
nameSet, err := utils.CheckGitConfig("user.name")
if err != nil {
return err
}
if !nameSet {
fmt.Println("user.name is not set in git config.")
fmt.Println("Please set up git config before trying again.")
panic("\nGIT CONFIG ISSUE: user.name is not set in git config.\n")
}
// Initialize git repo
err = utils.ExecuteCmd("git", []string{"init"}, projectPath)
if err != nil {
log.Printf("Error initializing git repo: %v", err)
return err
}
// Git add files
err = utils.ExecuteCmd("git", []string{"add", "."}, projectPath)
if err != nil {
log.Printf("Error adding files to git repo: %v", err)
return err
}
if p.GitOptions == flags.Commit {
// Git commit files
err = utils.ExecuteCmd("git", []string{"commit", "-m", "Initial commit"}, projectPath)
if err != nil {
log.Printf("Error committing files to git repo: %v", err)
return err
}
}
}
return nil
}
// CreatePath creates the given directory in the projectPath
func (p *Project) CreatePath(pathToCreate string, projectPath string) error {
path := filepath.Join(projectPath, pathToCreate)
if _, err := os.Stat(path); os.IsNotExist(err) {
err := os.MkdirAll(path, 0o751)
if err != nil {
log.Printf("Error creating directory %v\n", err)
return err
}
}
return nil
}
// CreateFileWithInjection creates the given file at the
// project path, and injects the appropriate template
func (p *Project) CreateFileWithInjection(pathToCreate string, projectPath string, fileName string, methodName string) error {
createdFile, err := os.Create(filepath.Join(projectPath, pathToCreate, fileName))
if err != nil {
return err
}
defer createdFile.Close()
switch methodName {
case "main":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.Main())))
err = createdTemplate.Execute(createdFile, p)
case "server":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.Server())))
err = createdTemplate.Execute(createdFile, p)
case "routes":
routeFileBytes := p.FrameworkMap[p.ProjectType].templater.Routes()
createdTemplate := template.Must(template.New(fileName).Parse(string(routeFileBytes)))
err = createdTemplate.Execute(createdFile, p)
case "releaser":
createdTemplate := template.Must(template.New(fileName).Parse(string(advanced.Releaser())))
err = createdTemplate.Execute(createdFile, p)
case "go-test":
createdTemplate := template.Must(template.New(fileName).Parse(string(advanced.Test())))
err = createdTemplate.Execute(createdFile, p)
case "releaser-config":
createdTemplate := template.Must(template.New(fileName).Parse(string(advanced.ReleaserConfig())))
err = createdTemplate.Execute(createdFile, p)
case "database":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.DBDriverMap[p.DBDriver].templater.Service())))
err = createdTemplate.Execute(createdFile, p)
case "db-docker":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.DockerMap[p.Docker].templater.Docker())))
err = createdTemplate.Execute(createdFile, p)
case "integration-tests":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.DBDriverMap[p.DBDriver].templater.Tests())))
err = createdTemplate.Execute(createdFile, p)
case "tests":
createdTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.TestHandler())))
err = createdTemplate.Execute(createdFile, p)
case "env":
if p.DBDriver != "none" {
envBytes := [][]byte{
tpl.GlobalEnvTemplate(),
p.DBDriverMap[p.DBDriver].templater.Env(),
}
createdTemplate := template.Must(template.New(fileName).Parse(string(bytes.Join(envBytes, []byte("\n")))))
err = createdTemplate.Execute(createdFile, p)
} else {
createdTemplate := template.Must(template.New(fileName).Parse(string(tpl.GlobalEnvTemplate())))
err = createdTemplate.Execute(createdFile, p)
}
}
if err != nil {
return err
}
return nil
}
func (p *Project) CreateViteReactProject(projectPath string) error {
if err := checkNpmInstalled(); err != nil {
return err
}
originalDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
defer func() {
if err := os.Chdir(originalDir); err != nil {
fmt.Fprintf(os.Stderr, "failed to change back to original directory: %v\n", err)
}
}()
// change into the project directory to run vite command
err = os.Chdir(projectPath)
if err != nil {
fmt.Println("failed to change into project directory: %w", err)
}
// the interactive vite command will not work as we can't interact with it
fmt.Println("Installing create-vite (using cache if available)...")
cmd := exec.Command("npm", "create", "vite@latest", "frontend", "--",
"--template", "react-ts",
"--prefer-offline",
"--no-fund")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to use create-vite: %w", err)
}
frontendPath := filepath.Join(projectPath, "frontend")
if err := os.MkdirAll(frontendPath, 0755); err != nil {
return fmt.Errorf("failed to create frontend directory: %w", err)
}
if err := os.Chdir(frontendPath); err != nil {
return fmt.Errorf("failed to change to frontend directory: %w", err)
}
srcDir := filepath.Join(frontendPath, "src")
if err := os.MkdirAll(srcDir, 0755); err != nil {
return fmt.Errorf("failed to create src directory: %w", err)
}
if err := os.WriteFile(filepath.Join(srcDir, "App.tsx"), advanced.ReactAppfile(), 0644); err != nil {
return fmt.Errorf("failed to write App.tsx template: %w", err)
}
// Create the global `.env` file from the template
err = p.CreateFileWithInjection("", projectPath, ".env", "env")
if err != nil {
return fmt.Errorf("failed to create global .env file: %w", err)
}
// Read from the global `.env` file and create the frontend-specific `.env`
globalEnvPath := filepath.Join(projectPath, ".env")
vitePort := "8080" // Default fallback
// Read the global .env file
if data, err := os.ReadFile(globalEnvPath); err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "PORT=") {
vitePort = strings.SplitN(line, "=", 2)[1] // Get the backend port value
break
}
}
}
// Use a template to generate the frontend .env file
frontendEnvContent := fmt.Sprintf("VITE_PORT=%s\n", vitePort)
if err := os.WriteFile(filepath.Join(frontendPath, ".env"), []byte(frontendEnvContent), 0644); err != nil {
return fmt.Errorf("failed to create frontend .env file: %w", err)
}
// Handle Tailwind configuration if selected
if p.AdvancedOptions[string(flags.Tailwind)] {
fmt.Println("Installing Tailwind dependencies (using cache if available)...")
cmd := exec.Command("npm", "install",
"--prefer-offline",
"--no-fund",
"tailwindcss@^4", "@tailwindcss/vite")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to install Tailwind: %w", err)
}
// Create the vite + react + Tailwind v4 configuration
if err := os.WriteFile(filepath.Join(frontendPath, "vite.config.ts"), advanced.ViteTailwindConfigFile(), 0644); err != nil {
return fmt.Errorf("failed to write vite.config.ts: %w", err)
}
srcDir := filepath.Join(frontendPath, "src")
if err := os.MkdirAll(srcDir, 0755); err != nil {
return fmt.Errorf("failed to create src directory: %w", err)
}
err = os.WriteFile(filepath.Join(srcDir, "index.css"), advanced.InputCssTemplateReact(), 0644)
if err != nil {
return fmt.Errorf("failed to update index.css: %w", err)
}
if err := os.WriteFile(filepath.Join(srcDir, "App.tsx"), advanced.ReactTailwindAppfile(), 0644); err != nil {
return fmt.Errorf("failed to write App.tsx template: %w", err)
}
if err := os.Remove(filepath.Join(srcDir, "App.css")); err != nil {
// Don't return error if file doesn't exist
if !os.IsNotExist(err) {
return fmt.Errorf("failed to remove App.css: %w", err)
}
}
// set to false to not re-do in next step
p.AdvancedOptions[string(flags.Tailwind)] = false
}
return nil
}
func (p *Project) CreateHtmxTemplates() {
routesPlaceHolder := ""
importsPlaceHolder := ""
if p.AdvancedOptions[string(flags.Htmx)] {
routesPlaceHolder += string(p.FrameworkMap[p.ProjectType].templater.HtmxTemplRoutes())
importsPlaceHolder += string(p.FrameworkMap[p.ProjectType].templater.HtmxTemplImports())
}
routeTmpl, err := template.New("routes").Parse(routesPlaceHolder)
if err != nil {
log.Fatal(err)
}
importTmpl, err := template.New("imports").Parse(importsPlaceHolder)
if err != nil {
log.Fatal(err)
}
var routeBuffer bytes.Buffer
var importBuffer bytes.Buffer
err = routeTmpl.Execute(&routeBuffer, p)
if err != nil {
log.Fatal(err)
}
err = importTmpl.Execute(&importBuffer, p)
if err != nil {
log.Fatal(err)
}
p.AdvancedTemplates.TemplateRoutes = routeBuffer.String()
p.AdvancedTemplates.TemplateImports = importBuffer.String()
}
func (p *Project) CreateWebsocketImports(appDir string) {
websocketDependency := []string{"github.com/coder/websocket"}
if p.ProjectType == flags.Fiber {
websocketDependency = []string{"github.com/gofiber/contrib/websocket"}
}
// Websockets require a different package depending on what framework is
// choosen. The application calls go mod tidy at the end so we don't
// have to here
err := utils.GoGetPackage(appDir, websocketDependency)
if err != nil {
log.Fatal(err)
}
importsPlaceHolder := string(p.FrameworkMap[p.ProjectType].templater.WebsocketImports())
importTmpl, err := template.New("imports").Parse(importsPlaceHolder)
if err != nil {
log.Fatalf("CreateWebsocketImports failed to create template: %v", err)
}
var importBuffer bytes.Buffer
err = importTmpl.Execute(&importBuffer, p)
if err != nil {
log.Fatalf("CreateWebsocketImports failed write template: %v", err)
}
newImports := strings.Join([]string{string(p.AdvancedTemplates.TemplateImports), importBuffer.String()}, "\n")
p.AdvancedTemplates.TemplateImports = newImports
}
func checkNpmInstalled() error {
cmd := exec.Command("npm", "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("npm is not installed: %w", err)
}
return nil
}
================================================
FILE: cmd/root.go
================================================
/*
Copyright © 2023 Melkey melkeydev@gmail.com
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "go-blueprint",
Short: "A program to spin up a quick Go project using a popular framework",
Long: `Go Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly.
It also gives the option to integrate with one of the more popular Go frameworks!`,
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.AddCommand(versionCmd)
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
================================================
FILE: cmd/steps/steps.go
================================================
// Package steps provides utility for creating
// each step of the CLI
package steps
import "github.com/melkeydev/go-blueprint/cmd/flags"
// A StepSchema contains the data that is used
// for an individual step of the CLI
type StepSchema struct {
StepName string // The name of a given step
Options []Item // The slice of each option for a given step
Headers string // The title displayed at the top of a given step
Field string
}
// Steps contains a slice of steps
type Steps struct {
Steps map[string]StepSchema
}
// An Item contains the data for each option
// in a StepSchema.Options
type Item struct {
Flag, Title, Desc string
}
// InitSteps initializes and returns the *Steps to be used in the CLI program
func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps {
steps := &Steps{
map[string]StepSchema{
"framework": {
StepName: "Go Project Framework",
Options: []Item{
{
Title: "Standard-library",
Desc: "The built-in Go standard library HTTP package",
},
{
Title: "Chi",
Desc: "A lightweight, idiomatic and composable router for building Go HTTP services",
},
{
Title: "Gin",
Desc: "Features a martini-like API with performance that is up to 40 times faster thanks to httprouter",
},
{
Title: "Fiber",
Desc: "An Express inspired web framework built on top of Fasthttp",
},
{
Title: "Gorilla/Mux",
Desc: "Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler",
},
{
Title: "HttpRouter",
Desc: "HttpRouter is a lightweight high performance HTTP request router for Go",
},
{
Title: "Echo",
Desc: "High performance, extensible, minimalist Go web framework",
},
},
Headers: "What framework do you want to use in your Go project?",
Field: projectType.String(),
},
"driver": {
StepName: "Go Project Database Driver",
Options: []Item{
{
Title: "Mysql",
Desc: "MySQL-Driver for Go's database/sql package",
},
{
Title: "Postgres",
Desc: "Go postgres driver for Go's database/sql package"},
{
Title: "Sqlite",
Desc: "sqlite3 driver conforming to the built-in database/sql interface"},
{
Title: "Mongo",
Desc: "The MongoDB supported driver for Go."},
{
Title: "Redis",
Desc: "Redis driver for Go."},
{
Title: "Scylla",
Desc: "ScyllaDB Enhanced driver from GoCQL."},
{
Title: "None",
Desc: "Choose this option if you don't wish to install a specific database driver."},
},
Headers: "What database driver do you want to use in your Go project?",
Field: databaseType.String(),
},
"advanced": {
StepName: "Advanced Features",
Headers: "Which advanced features do you want?",
Options: []Item{
{
Flag: "React",
Title: "React",
Desc: "Use Vite to spin up a React project in TypeScript. This disables selecting HTMX/Templ",
},
{
Flag: "Htmx",
Title: "HTMX/Templ",
Desc: "Add starter HTMX and Templ files. This disables selecting React",
},
{
Flag: "GitHubAction",
Title: "Go Project Workflow",
Desc: "Workflow templates for testing, cross-compiling and releasing Go projects",
},
{
Flag: "Websocket",
Title: "Websocket endpoint",
Desc: "Add a websocket endpoint",
},
{
Flag: "Tailwind",
Title: "TailwindCSS",
Desc: "A utility-first CSS framework (selecting this will automatically add HTMX unless React is specified)",
},
{
Flag: "Docker",
Title: "Docker",
Desc: "Dockerfile and docker-compose generic configuration for go project",
},
},
},
"git": {
StepName: "Git Repository",
Headers: "Which git option would you like to select for your project?",
Options: []Item{
{
Title: "Commit",
Desc: "Initialize a new git repository and commit all the changes",
},
{
Title: "Stage",
Desc: "Initialize a new git repository but only stage the changes",
},
{
Title: "Skip",
Desc: "Proceed without initializing a git repository",
},
},
},
},
}
return steps
}
================================================
FILE: cmd/template/advanced/docker.go
================================================
package advanced
import (
_ "embed"
)
//go:embed files/docker/dockerfile.tmpl
var dockerfileTemplate []byte
//go:embed files/docker/docker_compose.yml.tmpl
var dockerComposeTemplate []byte
func Dockerfile() []byte {
return dockerfileTemplate
}
func DockerCompose() []byte {
return dockerComposeTemplate
}
================================================
FILE: cmd/template/advanced/files/docker/docker_compose.yml.tmpl
================================================
services:
app:
build:
context: .
dockerfile: Dockerfile
target: prod
restart: unless-stopped
ports:
- ${PORT}:${PORT}
environment:
APP_ENV: ${APP_ENV}
PORT: ${PORT}
{{- if and (.AdvancedOptions.docker) (eq .DBDriver "sqlite") }}
BLUEPRINT_DB_URL: ${BLUEPRINT_DB_URL}
volumes:
- sqlite_bp:/app/db
{{- end }}
{{- if .AdvancedOptions.react }}
frontend:
build:
context: .
dockerfile: Dockerfile
target: frontend
restart: unless-stopped
ports:
- 5173:5173
depends_on:
- app
{{- end }}
{{- if and (.AdvancedOptions.docker) (eq .DBDriver "sqlite") }}
volumes:
sqlite_bp:
{{- end }}
================================================
FILE: cmd/template/advanced/files/docker/dockerfile.tmpl
================================================
FROM golang:1.24.4-alpine AS build
{{- if or (.AdvancedOptions.tailwind) (eq .DBDriver "sqlite") }}
RUN apk add --no-cache{{- if .AdvancedOptions.tailwind }} curl libstdc++ libgcc{{ end }}{{- if (eq .DBDriver "sqlite") }} alpine-sdk{{ end }}
{{- end }}
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
{{- if or .AdvancedOptions.htmx .AdvancedOptions.tailwind }}
RUN go install github.com/a-h/templ/cmd/templ@latest && \
templ generate{{- if .AdvancedOptions.tailwind}} && \{{- end}}
{{- end}}
{{- if .AdvancedOptions.tailwind}}
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64-musl -o tailwindcss && \
chmod +x tailwindcss && \
./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css
{{- end }}
RUN {{ if (eq .DBDriver "sqlite") }}CGO_ENABLED=1 GOOS=linux {{ end }}go build -o main cmd/api/main.go
FROM alpine:3.20.1 AS prod
WORKDIR /app
COPY --from=build /app/main /app/main
EXPOSE ${PORT}
CMD ["./main"]
{{ if .AdvancedOptions.react}}
FROM node:20 AS frontend_builder
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/. .
RUN npm run build
FROM node:23-slim AS frontend
RUN npm install -g serve
COPY --from=frontend_builder /frontend/dist /app/dist
EXPOSE 5173
CMD ["serve", "-s", "/app/dist", "-l", "5173"]
{{- end}}
================================================
FILE: cmd/template/advanced/files/htmx/base.templ.tmpl
================================================
package web
templ Base() {
<!DOCTYPE html>
<html lang="en" {{if .AdvancedOptions.tailwind}}class="h-screen"{{end}}>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Go Blueprint Hello</title>
<link href="assets/css/output.css" rel="stylesheet"/>
<script src="assets/js/htmx.min.js"></script>
</head>
<body {{if .AdvancedOptions.tailwind}}class="bg-gray-100"{{end}}>
<main {{if .AdvancedOptions.tailwind}}class="max-w-sm mx-auto p-4"{{end}}>
{ children... }
</main>
</body>
</html>
}
================================================
FILE: cmd/template/advanced/files/htmx/efs.go.tmpl
================================================
package web
import "embed"
//go:embed "assets"
var Files embed.FS
================================================
FILE: cmd/template/advanced/files/htmx/hello.go.tmpl
================================================
package web
import (
"log"
"net/http"
)
func HelloWebHandler(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
}
name := r.FormValue("name")
component := HelloPost(name)
err = component.Render(r.Context(), w)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Fatalf("Error rendering in HelloWebHandler: %e", err)
}
}
================================================
FILE: cmd/template/advanced/files/htmx/hello.templ.tmpl
================================================
package web
templ HelloForm() {
@Base() {
<form hx-post="/hello" method="POST" hx-target="#hello-container">
<input {{if .AdvancedOptions.tailwind}}class="bg-gray-200 text-black p-2 border border-gray-400 rounded-lg"{{end}}id="name" name="name" type="text"/>
<button type="submit" {{if .AdvancedOptions.tailwind}}class="bg-orange-500 hover:bg-orange-700 text-white py-2 px-4 rounded"{{end}}>Submit</button>
</form>
<div id="hello-container"></div>
}
}
templ HelloPost(name string) {
<div {{if .AdvancedOptions.tailwind}}class="bg-green-100 p-4 shadow-md rounded-lg mt-6"{{end}}>
<p>Hello, { name }</p>
</div>
}
================================================
FILE: cmd/template/advanced/files/htmx/hello_fiber.go.tmpl
================================================
package web
import (
"bytes"
"fmt"
"log"
"github.com/gofiber/fiber/v2"
)
func HelloWebHandler(c *fiber.Ctx) error {
// Parse form data
if err := c.BodyParser(c); err != nil {
innerErr := c.Status(fiber.StatusBadRequest).SendString("Bad Request")
if innerErr != nil {
log.Fatalf("Could not send error in HelloWebHandler: %e", innerErr)
}
}
// Get the name from the form data
name := c.FormValue("name")
// Render the component
component := HelloPost(name)
buf := new(bytes.Buffer)
err := component.Render(c.Context(), buf)
if err != nil {
errorString := fmt.Sprintf("Error rendering in HelloWebHandler: %e", err)
innerErr := c.Status(fiber.StatusBadRequest).SendString(errorString)
if innerErr != nil {
log.Fatalf("Could not send error in HelloWebHandler: %e", innerErr)
}
log.Fatalf("%s", errorString)
}
// Send the response
err = c.Status(fiber.StatusOK).SendString(buf.String())
if err != nil {
log.Fatalf("Could not send OK in HelloWebHandler: %e", err)
}
return nil
}
================================================
FILE: cmd/template/advanced/files/htmx/htmx.min.js.tmpl
================================================
var htmx = (function () {
"use strict";
const Q = {
onLoad: null,
process: null,
on: null,
off: null,
trigger: null,
ajax: null,
find: null,
findAll: null,
closest: null,
values: function (e, t) {
const n = dn(e, t || "post");
return n.values;
},
remove: null,
addClass: null,
removeClass: null,
toggleClass: null,
takeClass: null,
swap: null,
defineExtension: null,
removeExtension: null,
logAll: null,
logNone: null,
logger: null,
config: {
historyEnabled: true,
historyCacheSize: 10,
refreshOnHistoryMiss: false,
defaultSwapStyle: "innerHTML",
defaultSwapDelay: 0,
defaultSettleDelay: 20,
includeIndicatorStyles: true,
indicatorClass: "htmx-indicator",
requestClass: "htmx-request",
addedClass: "htmx-added",
settlingClass: "htmx-settling",
swappingClass: "htmx-swapping",
allowEval: true,
allowScriptTags: true,
inlineScriptNonce: "",
inlineStyleNonce: "",
attributesToSettle: ["class", "style", "width", "height"],
withCredentials: false,
timeout: 0,
wsReconnectDelay: "full-jitter",
wsBinaryType: "blob",
disableSelector: "[hx-disable], [data-hx-disable]",
scrollBehavior: "instant",
defaultFocusScroll: false,
getCacheBusterParam: false,
globalViewTransitions: false,
methodsThatUseUrlParams: ["get", "delete"],
selfRequestsOnly: true,
ignoreTitle: false,
scrollIntoViewOnBoost: true,
triggerSpecsCache: null,
disableInheritance: false,
responseHandling: [
{ code: "204", swap: false },
{ code: "[23]..", swap: true },
{ code: "[45]..", swap: false, error: true },
],
allowNestedOobSwaps: true,
historyRestoreAsHxRequest: true,
},
parseInterval: null,
location: location,
_: null,
version: "2.0.6",
};
Q.onLoad = j;
Q.process = Ft;
Q.on = xe;
Q.off = be;
Q.trigger = ae;
Q.ajax = Ln;
Q.find = f;
Q.findAll = x;
Q.closest = g;
Q.remove = z;
Q.addClass = K;
Q.removeClass = G;
Q.toggleClass = W;
Q.takeClass = Z;
Q.swap = $e;
Q.defineExtension = zn;
Q.removeExtension = $n;
Q.logAll = V;
Q.logNone = _;
Q.parseInterval = d;
Q._ = e;
const n = {
addTriggerHandler: St,
bodyContains: se,
canAccessLocalStorage: B,
findThisElement: Se,
filterValues: yn,
swap: $e,
hasAttribute: s,
getAttributeValue: a,
getClosestAttributeValue: ne,
getClosestMatch: q,
getExpressionVars: Tn,
getHeaders: mn,
getInputValues: dn,
getInternalData: oe,
getSwapSpecification: bn,
getTriggerSpecs: st,
getTarget: Ee,
makeFragment: P,
mergeObjects: le,
makeSettleInfo: Sn,
oobSwap: He,
querySelectorExt: ue,
settleImmediately: Yt,
shouldCancel: ht,
triggerEvent: ae,
triggerErrorEvent: fe,
withExtensions: jt,
};
const de = ["get", "post", "put", "delete", "patch"];
const T = de
.map(function (e) {
return "[hx-" + e + "], [data-hx-" + e + "]";
})
.join(", ");
function d(e) {
if (e == undefined) {
return undefined;
}
let t = NaN;
if (e.slice(-2) == "ms") {
t = parseFloat(e.slice(0, -2));
} else if (e.slice(-1) == "s") {
t = parseFloat(e.slice(0, -1)) * 1e3;
} else if (e.slice(-1) == "m") {
t = parseFloat(e.slice(0, -1)) * 1e3 * 60;
} else {
t = parseFloat(e);
}
return isNaN(t) ? undefined : t;
}
function ee(e, t) {
return e instanceof Element && e.getAttribute(t);
}
function s(e, t) {
return (
!!e.hasAttribute && (e.hasAttribute(t) || e.hasAttribute("data-" + t))
);
}
function a(e, t) {
return ee(e, t) || ee(e, "data-" + t);
}
function u(e) {
const t = e.parentElement;
if (!t && e.parentNode instanceof ShadowRoot) return e.parentNode;
return t;
}
function te() {
return document;
}
function y(e, t) {
return e.getRootNode ? e.getRootNode({ composed: t }) : te();
}
function q(e, t) {
while (e && !t(e)) {
e = u(e);
}
return e || null;
}
function o(e, t, n) {
const r = a(t, n);
const o = a(t, "hx-disinherit");
var i = a(t, "hx-inherit");
if (e !== t) {
if (Q.config.disableInheritance) {
if (i && (i === "*" || i.split(" ").indexOf(n) >= 0)) {
return r;
} else {
return null;
}
}
if (o && (o === "*" || o.split(" ").indexOf(n) >= 0)) {
return "unset";
}
}
return r;
}
function ne(t, n) {
let r = null;
q(t, function (e) {
return !!(r = o(t, ce(e), n));
});
if (r !== "unset") {
return r;
}
}
function h(e, t) {
return e instanceof Element && e.matches(t);
}
function A(e) {
const t = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i;
const n = t.exec(e);
if (n) {
return n[1].toLowerCase();
} else {
return "";
}
}
function L(e) {
const t = new DOMParser();
return t.parseFromString(e, "text/html");
}
function N(e, t) {
while (t.childNodes.length > 0) {
e.append(t.childNodes[0]);
}
}
function r(e) {
const t = te().createElement("script");
ie(e.attributes, function (e) {
t.setAttribute(e.name, e.value);
});
t.textContent = e.textContent;
t.async = false;
if (Q.config.inlineScriptNonce) {
t.nonce = Q.config.inlineScriptNonce;
}
return t;
}
function i(e) {
return (
e.matches("script") &&
(e.type === "text/javascript" || e.type === "module" || e.type === "")
);
}
function I(e) {
Array.from(e.querySelectorAll("script")).forEach((e) => {
if (i(e)) {
const t = r(e);
const n = e.parentNode;
try {
n.insertBefore(t, e);
} catch (e) {
R(e);
} finally {
e.remove();
}
}
});
}
function P(e) {
const t = e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, "");
const n = A(t);
let r;
if (n === "html") {
r = new DocumentFragment();
const i = L(e);
N(r, i.body);
r.title = i.title;
} else if (n === "body") {
r = new DocumentFragment();
const i = L(t);
N(r, i.body);
r.title = i.title;
} else {
const i = L(
'<body><template class="internal-htmx-wrapper">' +
t +
"</template></body>",
);
r = i.querySelector("template").content;
r.title = i.title;
var o = r.querySelector("title");
if (o && o.parentNode === r) {
o.remove();
r.title = o.innerText;
}
}
if (r) {
if (Q.config.allowScriptTags) {
I(r);
} else {
r.querySelectorAll("script").forEach((e) => e.remove());
}
}
return r;
}
function re(e) {
if (e) {
e();
}
}
function t(e, t) {
return Object.prototype.toString.call(e) === "[object " + t + "]";
}
function D(e) {
return typeof e === "function";
}
function k(e) {
return t(e, "Object");
}
function oe(e) {
const t = "htmx-internal-data";
let n = e[t];
if (!n) {
n = e[t] = {};
}
return n;
}
function M(t) {
const n = [];
if (t) {
for (let e = 0; e < t.length; e++) {
n.push(t[e]);
}
}
return n;
}
function ie(t, n) {
if (t) {
for (let e = 0; e < t.length; e++) {
n(t[e]);
}
}
}
function F(e) {
const t = e.getBoundingClientRect();
const n = t.top;
const r = t.bottom;
return n < window.innerHeight && r >= 0;
}
function se(e) {
return e.getRootNode({ composed: true }) === document;
}
function X(e) {
return e.trim().split(/\s+/);
}
function le(e, t) {
for (const n in t) {
if (t.hasOwnProperty(n)) {
e[n] = t[n];
}
}
return e;
}
function v(e) {
try {
return JSON.parse(e);
} catch (e) {
R(e);
return null;
}
}
function B() {
const e = "htmx:sessionStorageTest";
try {
sessionStorage.setItem(e, e);
sessionStorage.removeItem(e);
return true;
} catch (e) {
return false;
}
}
function U(e) {
const t = new URL(e, "http://x");
if (t) {
e = t.pathname + t.search;
}
if (e != "/") {
e = e.replace(/\/+$/, "");
}
return e;
}
function e(e) {
return On(te().body, function () {
return eval(e);
});
}
function j(t) {
const e = Q.on("htmx:load", function (e) {
t(e.detail.elt);
});
return e;
}
function V() {
Q.logger = function (e, t, n) {
if (console) {
console.log(t, e, n);
}
};
}
function _() {
Q.logger = null;
}
function f(e, t) {
if (typeof e !== "string") {
return e.querySelector(t);
} else {
return f(te(), e);
}
}
function x(e, t) {
if (typeof e !== "string") {
return e.querySelectorAll(t);
} else {
return x(te(), e);
}
}
function b() {
return window;
}
function z(e, t) {
e = w(e);
if (t) {
b().setTimeout(function () {
z(e);
e = null;
}, t);
} else {
u(e).removeChild(e);
}
}
function ce(e) {
return e instanceof Element ? e : null;
}
function $(e) {
return e instanceof HTMLElement ? e : null;
}
function J(e) {
return typeof e === "string" ? e : null;
}
function p(e) {
return e instanceof Element ||
e instanceof Document ||
e instanceof DocumentFragment
? e
: null;
}
function K(e, t, n) {
e = ce(w(e));
if (!e) {
return;
}
if (n) {
b().setTimeout(function () {
K(e, t);
e = null;
}, n);
} else {
e.classList && e.classList.add(t);
}
}
function G(e, t, n) {
let r = ce(w(e));
if (!r) {
return;
}
if (n) {
b().setTimeout(function () {
G(r, t);
r = null;
}, n);
} else {
if (r.classList) {
r.classList.remove(t);
if (r.classList.length === 0) {
r.removeAttribute("class");
}
}
}
}
function W(e, t) {
e = w(e);
e.classList.toggle(t);
}
function Z(e, t) {
e = w(e);
ie(e.parentElement.children, function (e) {
G(e, t);
});
K(ce(e), t);
}
function g(e, t) {
e = ce(w(e));
if (e) {
return e.closest(t);
}
return null;
}
function l(e, t) {
return e.substring(0, t.length) === t;
}
function Y(e, t) {
return e.substring(e.length - t.length) === t;
}
function pe(e) {
const t = e.trim();
if (l(t, "<") && Y(t, "/>")) {
return t.substring(1, t.length - 2);
} else {
return t;
}
}
function m(t, r, n) {
if (r.indexOf("global ") === 0) {
return m(t, r.slice(7), true);
}
t = w(t);
const o = [];
{
let t = 0;
let n = 0;
for (let e = 0; e < r.length; e++) {
const l = r[e];
if (l === "," && t === 0) {
o.push(r.substring(n, e));
n = e + 1;
continue;
}
if (l === "<") {
t++;
} else if (l === "/" && e < r.length - 1 && r[e + 1] === ">") {
t--;
}
}
if (n < r.length) {
o.push(r.substring(n));
}
}
const i = [];
const s = [];
while (o.length > 0) {
const r = pe(o.shift());
let e;
if (r.indexOf("closest ") === 0) {
e = g(ce(t), pe(r.slice(8)));
} else if (r.indexOf("find ") === 0) {
e = f(p(t), pe(r.slice(5)));
} else if (r === "next" || r === "nextElementSibling") {
e = ce(t).nextElementSibling;
} else if (r.indexOf("next ") === 0) {
e = ge(t, pe(r.slice(5)), !!n);
} else if (r === "previous" || r === "previousElementSibling") {
e = ce(t).previousElementSibling;
} else if (r.indexOf("previous ") === 0) {
e = me(t, pe(r.slice(9)), !!n);
} else if (r === "document") {
e = document;
} else if (r === "window") {
e = window;
} else if (r === "body") {
e = document.body;
} else if (r === "root") {
e = y(t, !!n);
} else if (r === "host") {
e = t.getRootNode().host;
} else {
s.push(r);
}
if (e) {
i.push(e);
}
}
if (s.length > 0) {
const e = s.join(",");
const c = p(y(t, !!n));
i.push(...M(c.querySelectorAll(e)));
}
return i;
}
var ge = function (t, e, n) {
const r = p(y(t, n)).querySelectorAll(e);
for (let e = 0; e < r.length; e++) {
const o = r[e];
if (o.compareDocumentPosition(t) === Node.DOCUMENT_POSITION_PRECEDING) {
return o;
}
}
};
var me = function (t, e, n) {
const r = p(y(t, n)).querySelectorAll(e);
for (let e = r.length - 1; e >= 0; e--) {
const o = r[e];
if (o.compareDocumentPosition(t) === Node.DOCUMENT_POSITION_FOLLOWING) {
return o;
}
}
};
function ue(e, t) {
if (typeof e !== "string") {
return m(e, t)[0];
} else {
return m(te().body, e)[0];
}
}
function w(e, t) {
if (typeof e === "string") {
return f(p(t) || document, e);
} else {
return e;
}
}
function ye(e, t, n, r) {
if (D(t)) {
return { target: te().body, event: J(e), listener: t, options: n };
} else {
return { target: w(e), event: J(t), listener: n, options: r };
}
}
function xe(t, n, r, o) {
Gn(function () {
const e = ye(t, n, r, o);
e.target.addEventListener(e.event, e.listener, e.options);
});
const e = D(n);
return e ? n : r;
}
function be(t, n, r) {
Gn(function () {
const e = ye(t, n, r);
e.target.removeEventListener(e.event, e.listener);
});
return D(n) ? n : r;
}
const ve = te().createElement("output");
function we(t, n) {
const e = ne(t, n);
if (e) {
if (e === "this") {
return [Se(t, n)];
} else {
const r = m(t, e);
const o = /(^|,)(\s*)inherit(\s*)($|,)/.test(e);
if (o) {
const i = ce(
q(t, function (e) {
return e !== t && s(ce(e), n);
}),
);
if (i) {
r.push(...we(i, n));
}
}
if (r.length === 0) {
R('The selector "' + e + '" on ' + n + " returned no matches!");
return [ve];
} else {
return r;
}
}
}
}
function Se(e, t) {
return ce(
q(e, function (e) {
return a(ce(e), t) != null;
}),
);
}
function Ee(e) {
const t = ne(e, "hx-target");
if (t) {
if (t === "this") {
return Se(e, "hx-target");
} else {
return ue(e, t);
}
} else {
const n = oe(e);
if (n.boosted) {
return te().body;
} else {
return e;
}
}
}
function Ce(e) {
return Q.config.attributesToSettle.includes(e);
}
function Oe(t, n) {
ie(t.attributes, function (e) {
if (!n.hasAttribute(e.name) && Ce(e.name)) {
t.removeAttribute(e.name);
}
});
ie(n.attributes, function (e) {
if (Ce(e.name)) {
t.setAttribute(e.name, e.value);
}
});
}
function Re(t, e) {
const n = Jn(e);
for (let e = 0; e < n.length; e++) {
const r = n[e];
try {
if (r.isInlineSwap(t)) {
return true;
}
} catch (e) {
R(e);
}
}
return t === "outerHTML";
}
function He(e, o, i, t) {
t = t || te();
let n = "#" + CSS.escape(ee(o, "id"));
let s = "outerHTML";
if (e === "true") {
} else if (e.indexOf(":") > 0) {
s = e.substring(0, e.indexOf(":"));
n = e.substring(e.indexOf(":") + 1);
} else {
s = e;
}
o.removeAttribute("hx-swap-oob");
o.removeAttribute("data-hx-swap-oob");
const r = m(t, n, false);
if (r.length) {
ie(r, function (e) {
let t;
const n = o.cloneNode(true);
t = te().createDocumentFragment();
t.appendChild(n);
if (!Re(s, e)) {
t = p(n);
}
const r = { shouldSwap: true, target: e, fragment: t };
if (!ae(e, "htmx:oobBeforeSwap", r)) return;
e = r.target;
if (r.shouldSwap) {
qe(t);
_e(s, e, e, t, i);
Te();
}
ie(i.elts, function (e) {
ae(e, "htmx:oobAfterSwap", r);
});
});
o.parentNode.removeChild(o);
} else {
o.parentNode.removeChild(o);
fe(te().body, "htmx:oobErrorNoTarget", { content: o });
}
return e;
}
function Te() {
const e = f("#--htmx-preserve-pantry--");
if (e) {
for (const t of [...e.children]) {
const n = f("#" + t.id);
n.parentNode.moveBefore(t, n);
n.remove();
}
e.remove();
}
}
function qe(e) {
ie(x(e, "[hx-preserve], [data-hx-preserve]"), function (e) {
const t = a(e, "id");
const n = te().getElementById(t);
if (n != null) {
if (e.moveBefore) {
let e = f("#--htmx-preserve-pantry--");
if (e == null) {
te().body.insertAdjacentHTML(
"afterend",
"<div id='--htmx-preserve-pantry--'></div>",
);
e = f("#--htmx-preserve-pantry--");
}
e.moveBefore(n, null);
} else {
e.parentNode.replaceChild(n, e);
}
}
});
}
function Ae(l, e, c) {
ie(e.querySelectorAll("[id]"), function (t) {
const n = ee(t, "id");
if (n && n.length > 0) {
const r = n.replace("'", "\\'");
const o = t.tagName.replace(":", "\\:");
const e = p(l);
const i = e && e.querySelector(o + "[id='" + r + "']");
if (i && i !== e) {
const s = t.cloneNode();
Oe(t, i);
c.tasks.push(function () {
Oe(t, s);
});
}
}
});
}
function Le(e) {
return function () {
G(e, Q.config.addedClass);
Ft(ce(e));
Ne(p(e));
ae(e, "htmx:load");
};
}
function Ne(e) {
const t = "[autofocus]";
const n = $(h(e, t) ? e : e.querySelector(t));
if (n != null) {
n.focus();
}
}
function c(e, t, n, r) {
Ae(e, n, r);
while (n.childNodes.length > 0) {
const o = n.firstChild;
K(ce(o), Q.config.addedClass);
e.insertBefore(o, t);
if (o.nodeType !== Node.TEXT_NODE && o.nodeType !== Node.COMMENT_NODE) {
r.tasks.push(Le(o));
}
}
}
function Ie(e, t) {
let n = 0;
while (n < e.length) {
t = ((t << 5) - t + e.charCodeAt(n++)) | 0;
}
return t;
}
function Pe(t) {
let n = 0;
for (let e = 0; e < t.attributes.length; e++) {
const r = t.attributes[e];
if (r.value) {
n = Ie(r.name, n);
n = Ie(r.value, n);
}
}
return n;
}
function De(t) {
const n = oe(t);
if (n.onHandlers) {
for (let e = 0; e < n.onHandlers.length; e++) {
const r = n.onHandlers[e];
be(t, r.event, r.listener);
}
delete n.onHandlers;
}
}
function ke(e) {
const t = oe(e);
if (t.timeout) {
clearTimeout(t.timeout);
}
if (t.listenerInfos) {
ie(t.listenerInfos, function (e) {
if (e.on) {
be(e.on, e.trigger, e.listener);
}
});
}
De(e);
ie(Object.keys(t), function (e) {
if (e !== "firstInitCompleted") delete t[e];
});
}
function S(e) {
ae(e, "htmx:beforeCleanupElement");
ke(e);
ie(e.children, function (e) {
S(e);
});
}
function Me(t, e, n) {
if (t.tagName === "BODY") {
return Ve(t, e, n);
}
let r;
const o = t.previousSibling;
const i = u(t);
if (!i) {
return;
}
c(i, t, e, n);
if (o == null) {
r = i.firstChild;
} else {
r = o.nextSibling;
}
n.elts = n.elts.filter(function (e) {
return e !== t;
});
while (r && r !== t) {
if (r instanceof Element) {
n.elts.push(r);
}
r = r.nextSibling;
}
S(t);
t.remove();
}
function Fe(e, t, n) {
return c(e, e.firstChild, t, n);
}
function Xe(e, t, n) {
return c(u(e), e, t, n);
}
function Be(e, t, n) {
return c(e, null, t, n);
}
function Ue(e, t, n) {
return c(u(e), e.nextSibling, t, n);
}
function je(e) {
S(e);
const t = u(e);
if (t) {
return t.removeChild(e);
}
}
function Ve(e, t, n) {
const r = e.firstChild;
c(e, r, t, n);
if (r) {
while (r.nextSibling) {
S(r.nextSibling);
e.removeChild(r.nextSibling);
}
S(r);
e.removeChild(r);
}
}
function _e(t, e, n, r, o) {
switch (t) {
case "none":
return;
case "outerHTML":
Me(n, r, o);
return;
case "afterbegin":
Fe(n, r, o);
return;
case "beforebegin":
Xe(n, r, o);
return;
case "beforeend":
Be(n, r, o);
return;
case "afterend":
Ue(n, r, o);
return;
case "delete":
je(n);
return;
default:
var i = Jn(e);
for (let e = 0; e < i.length; e++) {
const s = i[e];
try {
const l = s.handleSwap(t, n, r, o);
if (l) {
if (Array.isArray(l)) {
for (let e = 0; e < l.length; e++) {
const c = l[e];
if (
c.nodeType !== Node.TEXT_NODE &&
c.nodeType !== Node.COMMENT_NODE
) {
o.tasks.push(Le(c));
}
}
}
return;
}
} catch (e) {
R(e);
}
}
if (t === "innerHTML") {
Ve(n, r, o);
} else {
_e(Q.config.defaultSwapStyle, e, n, r, o);
}
}
}
function ze(e, n, r) {
var t = x(e, "[hx-swap-oob], [data-hx-swap-oob]");
ie(t, function (e) {
if (Q.config.allowNestedOobSwaps || e.parentElement === null) {
const t = a(e, "hx-swap-oob");
if (t != null) {
He(t, e, n, r);
}
} else {
e.removeAttribute("hx-swap-oob");
e.removeAttribute("data-hx-swap-oob");
}
});
return t.length > 0;
}
function $e(h, d, p, g) {
if (!g) {
g = {};
}
let m = null;
let n = null;
let e = function () {
re(g.beforeSwapCallback);
h = w(h);
const r = g.contextElement ? y(g.contextElement, false) : te();
const e = document.activeElement;
let t = {};
t = {
elt: e,
start: e ? e.selectionStart : null,
end: e ? e.selectionEnd : null,
};
const o = Sn(h);
if (p.swapStyle === "textContent") {
h.textContent = d;
} else {
let n = P(d);
o.title = g.title || n.title;
if (g.historyRequest) {
n = n.querySelector("[hx-history-elt],[data-hx-history-elt]") || n;
}
if (g.selectOOB) {
const i = g.selectOOB.split(",");
for (let t = 0; t < i.length; t++) {
const s = i[t].split(":", 2);
let e = s[0].trim();
if (e.indexOf("#") === 0) {
e = e.substring(1);
}
const l = s[1] || "true";
const c = n.querySelector("#" + e);
if (c) {
He(l, c, o, r);
}
}
}
ze(n, o, r);
ie(x(n, "template"), function (e) {
if (e.content && ze(e.content, o, r)) {
e.remove();
}
});
if (g.select) {
const u = te().createDocumentFragment();
ie(n.querySelectorAll(g.select), function (e) {
u.appendChild(e);
});
n = u;
}
qe(n);
_e(p.swapStyle, g.contextElement, h, n, o);
Te();
}
if (t.elt && !se(t.elt) && ee(t.elt, "id")) {
const f = document.getElementById(ee(t.elt, "id"));
const a = {
preventScroll:
p.focusScroll !== undefined
? !p.focusScroll
: !Q.config.defaultFocusScroll,
};
if (f) {
if (t.start && f.setSelectionRange) {
try {
f.setSelectionRange(t.start, t.end);
} catch (e) {}
}
f.focus(a);
}
}
h.classList.remove(Q.config.swappingClass);
ie(o.elts, function (e) {
if (e.classList) {
e.classList.add(Q.config.settlingClass);
}
ae(e, "htmx:afterSwap", g.eventInfo);
});
re(g.afterSwapCallback);
if (!p.ignoreTitle) {
Bn(o.title);
}
const n = function () {
ie(o.tasks, function (e) {
e.call();
});
ie(o.elts, function (e) {
if (e.classList) {
e.classList.remove(Q.config.settlingClass);
}
ae(e, "htmx:afterSettle", g.eventInfo);
});
if (g.anchor) {
const e = ce(w("#" + g.anchor));
if (e) {
e.scrollIntoView({ block: "start", behavior: "auto" });
}
}
En(o.elts, p);
re(g.afterSettleCallback);
re(m);
};
if (p.settleDelay > 0) {
b().setTimeout(n, p.settleDelay);
} else {
n();
}
};
let t = Q.config.globalViewTransitions;
if (p.hasOwnProperty("transition")) {
t = p.transition;
}
const r = g.contextElement || te();
if (
t &&
ae(r, "htmx:beforeTransition", g.eventInfo) &&
typeof Promise !== "undefined" &&
document.startViewTransition
) {
const o = new Promise(function (e, t) {
m = e;
n = t;
});
const i = e;
e = function () {
document.startViewTransition(function () {
i();
return o;
});
};
}
try {
if (p?.swapDelay && p.swapDelay > 0) {
b().setTimeout(e, p.swapDelay);
} else {
e();
}
} catch (e) {
fe(r, "htmx:swapError", g.eventInfo);
re(n);
throw e;
}
}
function Je(e, t, n) {
const r = e.getResponseHeader(t);
if (r.indexOf("{") === 0) {
const o = v(r);
for (const i in o) {
if (o.hasOwnProperty(i)) {
let e = o[i];
if (k(e)) {
n = e.target !== undefined ? e.target : n;
} else {
e = { value: e };
}
ae(n, i, e);
}
}
} else {
const s = r.split(",");
for (let e = 0; e < s.length; e++) {
ae(n, s[e].trim(), []);
}
}
}
const Ke = /\s/;
const E = /[\s,]/;
const Ge = /[_$a-zA-Z]/;
const We = /[_$a-zA-Z0-9]/;
const Ze = ['"', "'", "/"];
const C = /[^\s]/;
const Ye = /[{(]/;
const Qe = /[})]/;
function et(e) {
const t = [];
let n = 0;
while (n < e.length) {
if (Ge.exec(e.charAt(n))) {
var r = n;
while (We.exec(e.charAt(n + 1))) {
n++;
}
t.push(e.substring(r, n + 1));
} else if (Ze.indexOf(e.charAt(n)) !== -1) {
const o = e.charAt(n);
var r = n;
n++;
while (n < e.length && e.charAt(n) !== o) {
if (e.charAt(n) === "\\") {
n++;
}
n++;
}
t.push(e.substring(r, n + 1));
} else {
const i = e.charAt(n);
t.push(i);
}
n++;
}
return t;
}
function tt(e, t, n) {
return (
Ge.exec(e.charAt(0)) &&
e !== "true" &&
e !== "false" &&
e !== "this" &&
e !== n &&
t !== "."
);
}
function nt(r, o, i) {
if (o[0] === "[") {
o.shift();
let e = 1;
let t = " return (function(" + i + "){ return (";
let n = null;
while (o.length > 0) {
const s = o[0];
if (s === "]") {
e--;
if (e === 0) {
if (n === null) {
t = t + "true";
}
o.shift();
t += ")})";
try {
const l = On(
r,
function () {
return Function(t)();
},
function () {
return true;
},
);
l.source = t;
return l;
} catch (e) {
fe(te().body, "htmx:syntax:error", { error: e, source: t });
return null;
}
}
} else if (s === "[") {
e++;
}
if (tt(s, n, i)) {
t +=
"((" +
i +
"." +
s +
") ? (" +
i +
"." +
s +
") : (window." +
s +
"))";
} else {
t = t + s;
}
n = o.shift();
}
}
}
function O(e, t) {
let n = "";
while (e.length > 0 && !t.test(e[0])) {
n += e.shift();
}
return n;
}
function rt(e) {
let t;
if (e.length > 0 && Ye.test(e[0])) {
e.shift();
t = O(e, Qe).trim();
e.shift();
} else {
t = O(e, E);
}
return t;
}
const ot = "input, textarea, select";
function it(e, t, n) {
const r = [];
const o = et(t);
do {
O(o, C);
const l = o.length;
const c = O(o, /[,\[\s]/);
if (c !== "") {
if (c === "every") {
const u = { trigger: "every" };
O(o, C);
u.pollInterval = d(O(o, /[,\[\s]/));
O(o, C);
var i = nt(e, o, "event");
if (i) {
u.eventFilter = i;
}
r.push(u);
} else {
const f = { trigger: c };
var i = nt(e, o, "event");
if (i) {
f.eventFilter = i;
}
O(o, C);
while (o.length > 0 && o[0] !== ",") {
const a = o.shift();
if (a === "changed") {
f.changed = true;
} else if (a === "once") {
f.once = true;
} else if (a === "consume") {
f.consume = true;
} else if (a === "delay" && o[0] === ":") {
o.shift();
f.delay = d(O(o, E));
} else if (a === "from" && o[0] === ":") {
o.shift();
if (Ye.test(o[0])) {
var s = rt(o);
} else {
var s = O(o, E);
if (
s === "closest" ||
s === "find" ||
s === "next" ||
s === "previous"
) {
o.shift();
const h = rt(o);
if (h.length > 0) {
s += " " + h;
}
}
}
f.from = s;
} else if (a === "target" && o[0] === ":") {
o.shift();
f.target = rt(o);
} else if (a === "throttle" && o[0] === ":") {
o.shift();
f.throttle = d(O(o, E));
} else if (a === "queue" && o[0] === ":") {
o.shift();
f.queue = O(o, E);
} else if (a === "root" && o[0] === ":") {
o.shift();
f[a] = rt(o);
} else if (a === "threshold" && o[0] === ":") {
o.shift();
f[a] = O(o, E);
} else {
fe(e, "htmx:syntax:error", { token: o.shift() });
}
O(o, C);
}
r.push(f);
}
}
if (o.length === l) {
fe(e, "htmx:syntax:error", { token: o.shift() });
}
O(o, C);
} while (o[0] === "," && o.shift());
if (n) {
n[t] = r;
}
return r;
}
function st(e) {
const t = a(e, "hx-trigger");
let n = [];
if (t) {
const r = Q.config.triggerSpecsCache;
n = (r && r[t]) || it(e, t, r);
}
if (n.length > 0) {
return n;
} else if (h(e, "form")) {
return [{ trigger: "submit" }];
} else if (h(e, 'input[type="button"], input[type="submit"]')) {
return [{ trigger: "click" }];
} else if (h(e, ot)) {
return [{ trigger: "change" }];
} else {
return [{ trigger: "click" }];
}
}
function lt(e) {
oe(e).cancelled = true;
}
function ct(e, t, n) {
const r = oe(e);
r.timeout = b().setTimeout(function () {
if (se(e) && r.cancelled !== true) {
if (!pt(n, e, Bt("hx:poll:trigger", { triggerSpec: n, target: e }))) {
t(e);
}
ct(e, t, n);
}
}, n.pollInterval);
}
function ut(e) {
return (
location.hostname === e.hostname &&
ee(e, "href") &&
ee(e, "href").indexOf("#") !== 0
);
}
function ft(e) {
return g(e, Q.config.disableSelector);
}
function at(t, n, e) {
if (
(t instanceof HTMLAnchorElement &&
ut(t) &&
(t.target === "" || t.target === "_self")) ||
(t.tagName === "FORM" &&
String(ee(t, "method")).toLowerCase() !== "dialog")
) {
n.boosted = true;
let r, o;
if (t.tagName === "A") {
r = "get";
o = ee(t, "href");
} else {
const i = ee(t, "method");
r = i ? i.toLowerCase() : "get";
o = ee(t, "action");
if (o == null || o === "") {
o = location.href;
}
if (r === "get" && o.includes("?")) {
o = o.replace(/\?[^#]+/, "");
}
}
e.forEach(function (e) {
gt(
t,
function (e, t) {
const n = ce(e);
if (ft(n)) {
S(n);
return;
}
he(r, o, n, t);
},
n,
e,
true,
);
});
}
}
function ht(e, t) {
if (e.type === "submit" || e.type === "click") {
t = ce(e.target) || t;
if (t.tagName === "FORM") {
return true;
}
if (t.form && t.type === "submit") {
return true;
}
t = t.closest("a");
if (
t &&
t.href &&
(t.getAttribute("href") === "#" ||
t.getAttribute("href").indexOf("#") !== 0)
) {
return true;
}
}
return false;
}
function dt(e, t) {
return (
oe(e).boosted &&
e instanceof HTMLAnchorElement &&
t.type === "click" &&
(t.ctrlKey || t.metaKey)
);
}
function pt(e, t, n) {
const r = e.eventFilter;
if (r) {
try {
return r.call(t, n) !== true;
} catch (e) {
const o = r.source;
fe(te().body, "htmx:eventFilter:error", { error: e, source: o });
return true;
}
}
return false;
}
function gt(l, c, e, u, f) {
const a = oe(l);
let t;
if (u.from) {
t = m(l, u.from);
} else {
t = [l];
}
if (u.changed) {
if (!("lastValue" in a)) {
a.lastValue = new WeakMap();
}
t.forEach(function (e) {
if (!a.lastValue.has(u)) {
a.lastValue.set(u, new WeakMap());
}
a.lastValue.get(u).set(e, e.value);
});
}
ie(t, function (i) {
const s = function (e) {
if (!se(l)) {
i.removeEventListener(u.trigger, s);
return;
}
if (dt(l, e)) {
return;
}
if (f || ht(e, l)) {
e.preventDefault();
}
if (pt(u, l, e)) {
return;
}
const t = oe(e);
t.triggerSpec = u;
if (t.handledFor == null) {
t.handledFor = [];
}
if (t.handledFor.indexOf(l) < 0) {
t.handledFor.push(l);
if (u.consume) {
e.stopPropagation();
}
if (u.target && e.target) {
if (!h(ce(e.target), u.target)) {
return;
}
}
if (u.once) {
if (a.triggeredOnce) {
return;
} else {
a.triggeredOnce = true;
}
}
if (u.changed) {
const n = e.target;
const r = n.value;
const o = a.lastValue.get(u);
if (o.has(n) && o.get(n) === r) {
return;
}
o.set(n, r);
}
if (a.delayed) {
clearTimeout(a.delayed);
}
if (a.throttle) {
return;
}
if (u.throttle > 0) {
if (!a.throttle) {
ae(l, "htmx:trigger");
c(l, e);
a.throttle = b().setTimeout(function () {
a.throttle = null;
}, u.throttle);
}
} else if (u.delay > 0) {
a.delayed = b().setTimeout(function () {
ae(l, "htmx:trigger");
c(l, e);
}, u.delay);
} else {
ae(l, "htmx:trigger");
c(l, e);
}
}
};
if (e.listenerInfos == null) {
e.listenerInfos = [];
}
e.listenerInfos.push({ trigger: u.trigger, listener: s, on: i });
i.addEventListener(u.trigger, s);
});
}
let mt = false;
let yt = null;
function xt() {
if (!yt) {
yt = function () {
mt = true;
};
window.addEventListener("scroll", yt);
window.addEventListener("resize", yt);
setInterval(function () {
if (mt) {
mt = false;
ie(
te().querySelectorAll(
"[hx-trigger*='revealed'],[data-hx-trigger*='revealed']",
),
function (e) {
bt(e);
},
);
}
}, 200);
}
}
function bt(e) {
if (!s(e, "data-hx-revealed") && F(e)) {
e.setAttribute("data-hx-revealed", "true");
const t = oe(e);
if (t.initHash) {
ae(e, "revealed");
} else {
e.addEventListener(
"htmx:afterProcessNode",
function () {
ae(e, "revealed");
},
{ once: true },
);
}
}
}
function vt(e, t, n, r) {
const o = function () {
if (!n.loaded) {
n.loaded = true;
ae(e, "htmx:trigger");
t(e);
}
};
if (r > 0) {
b().setTimeout(o, r);
} else {
o();
}
}
function wt(t, n, e) {
let i = false;
ie(de, function (r) {
if (s(t, "hx-" + r)) {
const o = a(t, "hx-" + r);
i = true;
n.path = o;
n.verb = r;
e.forEach(function (e) {
St(t, e, n, function (e, t) {
const n = ce(e);
if (ft(n)) {
S(n);
return;
}
he(r, o, n, t);
});
});
}
});
return i;
}
function St(r, e, t, n) {
if (e.trigger === "revealed") {
xt();
gt(r, n, t, e);
bt(ce(r));
} else if (e.trigger === "intersect") {
const o = {};
if (e.root) {
o.root = ue(r, e.root);
}
if (e.threshold) {
o.threshold = parseFloat(e.threshold);
}
const i = new IntersectionObserver(function (t) {
for (let e = 0; e < t.length; e++) {
const n = t[e];
if (n.isIntersecting) {
ae(r, "intersect");
break;
}
}
}, o);
i.observe(ce(r));
gt(ce(r), n, t, e);
} else if (!t.firstInitCompleted && e.trigger === "load") {
if (!pt(e, r, Bt("load", { elt: r }))) {
vt(ce(r), n, t, e.delay);
}
} else if (e.pollInterval > 0) {
t.polling = true;
ct(ce(r), n, e);
} else {
gt(r, n, t, e);
}
}
function Et(e) {
const t = ce(e);
if (!t) {
return false;
}
const n = t.attributes;
for (let e = 0; e < n.length; e++) {
const r = n[e].name;
if (
l(r, "hx-on:") ||
l(r, "data-hx-on:") ||
l(r, "hx-on-") ||
l(r, "data-hx-on-")
) {
return true;
}
}
return false;
}
const Ct = new XPathEvaluator().createExpression(
'.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or' +
' starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]',
);
function Ot(e, t) {
if (Et(e)) {
t.push(ce(e));
}
const n = Ct.evaluate(e);
let r = null;
while ((r = n.iterateNext())) t.push(ce(r));
}
function Rt(e) {
const t = [];
if (e instanceof DocumentFragment) {
for (const n of e.childNodes) {
Ot(n, t);
}
} else {
Ot(e, t);
}
return t;
}
function Ht(e) {
if (e.querySelectorAll) {
const n =
", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]";
const r = [];
for (const i in Vn) {
const s = Vn[i];
if (s.getSelectors) {
var t = s.getSelectors();
if (t) {
r.push(t);
}
}
}
const o = e.querySelectorAll(
T +
n +
", form, [type='submit']," +
" [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]" +
r
.flat()
.map((e) => ", " + e)
.join(""),
);
return o;
} else {
return [];
}
}
function Tt(e) {
const t = At(e.target);
const n = Nt(e);
if (n) {
n.lastButtonClicked = t;
}
}
function qt(e) {
const t = Nt(e);
if (t) {
t.lastButtonClicked = null;
}
}
function At(e) {
return g(ce(e), "button, input[type='submit']");
}
function Lt(e) {
return e.form || g(e, "form");
}
function Nt(e) {
const t = At(e.target);
if (!t) {
return;
}
const n = Lt(t);
return oe(n);
}
function It(e) {
e.addEventListener("click", Tt);
e.addEventListener("focusin", Tt);
e.addEventListener("focusout", qt);
}
function Pt(t, e, n) {
const r = oe(t);
if (!Array.isArray(r.onHandlers)) {
r.onHandlers = [];
}
let o;
const i = function (e) {
On(t, function () {
if (ft(t)) {
return;
}
if (!o) {
o = new Function("event", n);
}
o.call(t, e);
});
};
t.addEventListener(e, i);
r.onHandlers.push({ event: e, listener: i });
}
function Dt(t) {
De(t);
for (let e = 0; e < t.attributes.length; e++) {
const n = t.attributes[e].name;
const r = t.attributes[e].value;
if (l(n, "hx-on") || l(n, "data-hx-on")) {
const o = n.indexOf("-on") + 3;
const i = n.slice(o, o + 1);
if (i === "-" || i === ":") {
let e = n.slice(o + 1);
if (l(e, ":")) {
e = "htmx" + e;
} else if (l(e, "-")) {
e = "htmx:" + e.slice(1);
} else if (l(e, "htmx-")) {
e = "htmx:" + e.slice(5);
}
Pt(t, e, r);
}
}
}
}
function kt(t) {
ae(t, "htmx:beforeProcessNode");
const n = oe(t);
const e = st(t);
const r = wt(t, n, e);
if (!r) {
if (ne(t, "hx-boost") === "true") {
at(t, n, e);
} else if (s(t, "hx-trigger")) {
e.forEach(function (e) {
St(t, e, n, function () {});
});
}
}
if (t.tagName === "FORM" || (ee(t, "type") === "submit" && s(t, "form"))) {
It(t);
}
n.firstInitCompleted = true;
ae(t, "htmx:afterProcessNode");
}
function Mt(e) {
if (!(e instanceof Element)) {
return false;
}
const t = oe(e);
const n = Pe(e);
if (t.initHash !== n) {
ke(e);
t.initHash = n;
return true;
}
return false;
}
function Ft(e) {
e = w(e);
if (ft(e)) {
S(e);
return;
}
const t = [];
if (Mt(e)) {
t.push(e);
}
ie(Ht(e), function (e) {
if (ft(e)) {
S(e);
return;
}
if (Mt(e)) {
t.push(e);
}
});
ie(Rt(e), Dt);
ie(t, kt);
}
function Xt(e) {
return e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
}
function Bt(e, t) {
return new CustomEvent(e, {
bubbles: true,
cancelable: true,
composed: true,
detail: t,
});
}
function fe(e, t, n) {
ae(e, t, le({ error: t }, n));
}
function Ut(e) {
return e === "htmx:afterProcessNode";
}
function jt(e, t, n) {
ie(Jn(e, [], n), function (e) {
try {
t(e);
} catch (e) {
R(e);
}
});
}
function R(e) {
console.error(e);
}
function ae(e, t, n) {
e = w(e);
if (n == null) {
n = {};
}
n.elt = e;
const r = Bt(t, n);
if (Q.logger && !Ut(t)) {
Q.logger(e, t, n);
}
if (n.error) {
R(n.error);
ae(e, "htmx:error", { errorInfo: n });
}
let o = e.dispatchEvent(r);
const i = Xt(t);
if (o && i !== t) {
const s = Bt(i, r.detail);
o = o && e.dispatchEvent(s);
}
jt(ce(e), function (e) {
o = o && e.onEvent(t, r) !== false && !r.defaultPrevented;
});
return o;
}
let Vt = location.pathname + location.search;
function _t(e) {
Vt = e;
if (B()) {
sessionStorage.setItem("htmx-current-path-for-history", e);
}
}
function zt() {
const e = te().querySelector("[hx-history-elt],[data-hx-history-elt]");
return e || te().body;
}
function $t(t, e) {
if (!B()) {
return;
}
const n = Kt(e);
const r = te().title;
const o = window.scrollY;
if (Q.config.historyCacheSize <= 0) {
sessionStorage.removeItem("htmx-history-cache");
return;
}
t = U(t);
const i = v(sessionStorage.getItem("htmx-history-cache")) || [];
for (let e = 0; e < i.length; e++) {
if (i[e].url === t) {
i.splice(e, 1);
break;
}
}
const s = { url: t, content: n, title: r, scroll: o };
ae(te().body, "htmx:historyItemCreated", { item: s, cache: i });
i.push(s);
while (i.length > Q.config.historyCacheSize) {
i.shift();
}
while (i.length > 0) {
try {
sessionStorage.setItem("htmx-history-cache", JSON.stringify(i));
break;
} catch (e) {
fe(te().body, "htmx:historyCacheError", { cause: e, cache: i });
i.shift();
}
}
}
function Jt(t) {
if (!B()) {
return null;
}
t = U(t);
const n = v(sessionStorage.getItem("htmx-history-cache")) || [];
for (let e = 0; e < n.length; e++) {
if (n[e].url === t) {
return n[e];
}
}
return null;
}
function Kt(e) {
const t = Q.config.requestClass;
const n = e.cloneNode(true);
ie(x(n, "." + t), function (e) {
G(e, t);
});
ie(x(n, "[data-disabled-by-htmx]"), function (e) {
e.removeAttribute("disabled");
});
return n.innerHTML;
}
function Gt() {
const e = zt();
let t = Vt;
if (B()) {
t = sessionStorage.getItem("htmx-current-path-for-history");
}
t = t || location.pathname + location.search;
const n = te().querySelector(
'[hx-history="false" i],[data-hx-history="false" i]',
);
if (!n) {
ae(te().body, "htmx:beforeHistorySave", { path: t, historyElt: e });
$t(t, e);
}
if (Q.config.historyEnabled)
history.replaceState({ htmx: true }, te().title, location.href);
}
function Wt(e) {
if (Q.config.getCacheBusterParam) {
e = e.replace(/org\.htmx\.cache-buster=[^&]*&?/, "");
if (Y(e, "&") || Y(e, "?")) {
e = e.slice(0, -1);
}
}
if (Q.config.historyEnabled) {
history.pushState({ htmx: true }, "", e);
}
_t(e);
}
function Zt(e) {
if (Q.config.historyEnabled) history.replaceState({ htmx: true }, "", e);
_t(e);
}
function Yt(e) {
ie(e, function (e) {
e.call(undefined);
});
}
function Qt(e) {
const t = new XMLHttpRequest();
const n = { swapStyle: "innerHTML", swapDelay: 0, settleDelay: 0 };
const r = { path: e, xhr: t, historyElt: zt(), swapSpec: n };
t.open("GET", e, true);
if (Q.config.historyRestoreAsHxRequest) {
t.setRequestHeader("HX-Request", "true");
}
t.setRequestHeader("HX-History-Restore-Request", "true");
t.setRequestHeader("HX-Current-URL", location.href);
t.onload = function () {
if (this.status >= 200 && this.status < 400) {
r.response = this.response;
ae(te().body, "htmx:historyCacheMissLoad", r);
$e(r.historyElt, r.response, n, {
contextElement: r.historyElt,
historyRequest: true,
});
_t(r.path);
ae(te().body, "htmx:historyRestore", {
path: e,
cacheMiss: true,
serverResponse: r.response,
});
} else {
fe(te().body, "htmx:historyCacheMissLoadError", r);
}
};
if (ae(te().body, "htmx:historyCacheMiss", r)) {
t.send();
}
}
function en(e) {
Gt();
e = e || location.pathname + location.search;
const t = Jt(e);
if (t) {
const n = {
swapStyle: "innerHTML",
swapDelay: 0,
settleDelay: 0,
scroll: t.scroll,
};
const r = { path: e, item: t, historyElt: zt(), swapSpec: n };
if (ae(te().body, "htmx:historyCacheHit", r)) {
$e(r.historyElt, t.content, n, {
contextElement: r.historyElt,
title: t.title,
});
_t(r.path);
ae(te().body, "htmx:historyRestore", r);
}
} else {
if (Q.config.refreshOnHistoryMiss) {
Q.location.reload(true);
} else {
Qt(e);
}
}
}
function tn(e) {
let t = we(e, "hx-indicator");
if (t == null) {
t = [e];
}
ie(t, function (e) {
const t = oe(e);
t.requestCount = (t.requestCount || 0) + 1;
e.classList.add.call(e.classList, Q.config.requestClass);
});
return t;
}
function nn(e) {
let t = we(e, "hx-disabled-elt");
if (t == null) {
t = [];
}
ie(t, function (e) {
const t = oe(e);
t.requestCount = (t.requestCount || 0) + 1;
e.setAttribute("disabled", "");
e.setAttribute("data-disabled-by-htmx", "");
});
return t;
}
function rn(e, t) {
ie(e.concat(t), function (e) {
const t = oe(e);
t.requestCount = (t.requestCount || 1) - 1;
});
ie(e, function (e) {
const t = oe(e);
if (t.requestCount === 0) {
e.classList.remove.call(e.classList, Q.config.requestClass);
}
});
ie(t, function (e) {
const t = oe(e);
if (t.requestCount === 0) {
e.removeAttribute("disabled");
e.removeAttribute("data-disabled-by-htmx");
}
});
}
function on(t, n) {
for (let e = 0; e < t.length; e++) {
const r = t[e];
if (r.isSameNode(n)) {
return true;
}
}
return false;
}
function sn(e) {
const t = e;
if (
t.name === "" ||
t.name == null ||
t.disabled ||
g(t, "fieldset[disabled]")
) {
return false;
}
if (
t.type === "button" ||
t.type === "submit" ||
t.tagName === "image" ||
t.tagName === "reset" ||
t.tagName === "file"
) {
return false;
}
if (t.type === "checkbox" || t.type === "radio") {
return t.checked;
}
return true;
}
function ln(t, e, n) {
if (t != null && e != null) {
if (Array.isArray(e)) {
e.forEach(function (e) {
n.append(t, e);
});
} else {
n.append(t, e);
}
}
}
function cn(t, n, r) {
if (t != null && n != null) {
let e = r.getAll(t);
if (Array.isArray(n)) {
e = e.filter((e) => n.indexOf(e) < 0);
} else {
e = e.filter((e) => e !== n);
}
r.delete(t);
ie(e, (e) => r.append(t, e));
}
}
function un(e) {
if (e instanceof HTMLSelectElement && e.multiple) {
return M(e.querySelectorAll("option:checked")).map(function (e) {
return e.value;
});
}
if (e instanceof HTMLInputElement && e.files) {
return M(e.files);
}
return e.value;
}
function fn(t, n, r, e, o) {
if (e == null || on(t, e)) {
return;
} else {
t.push(e);
}
if (sn(e)) {
const i = ee(e, "name");
ln(i, un(e), n);
if (o) {
an(e, r);
}
}
if (e instanceof HTMLFormElement) {
ie(e.elements, function (e) {
if (t.indexOf(e) >= 0) {
cn(e.name, un(e), n);
} else {
t.push(e);
}
if (o) {
an(e, r);
}
});
new FormData(e).forEach(function (e, t) {
if (e instanceof File && e.name === "") {
return;
}
ln(t, e, n);
});
}
}
function an(e, t) {
const n = e;
if (n.willValidate) {
ae(n, "htmx:validation:validate");
if (!n.checkValidity()) {
t.push({ elt: n, message: n.validationMessage, validity: n.validity });
ae(n, "htmx:validation:failed", {
message: n.validationMessage,
validity: n.validity,
});
}
}
}
function hn(n, e) {
for (const t of e.keys()) {
n.delete(t);
}
e.forEach(function (e, t) {
n.append(t, e);
});
return n;
}
function dn(e, t) {
const n = [];
const r = new FormData();
const o = new FormData();
const i = [];
const s = oe(e);
if (s.lastButtonClicked && !se(s.lastButtonClicked)) {
s.lastButtonClicked = null;
}
let l =
(e instanceof HTMLFormElement && e.noValidate !== true) ||
a(e, "hx-validate") === "true";
if (s.lastButtonClicked) {
l = l && s.lastButtonClicked.formNoValidate !== true;
}
if (t !== "get") {
fn(n, o, i, Lt(e), l);
}
fn(n, r, i, e, l);
if (
s.lastButtonClicked ||
e.tagName === "BUTTON" ||
(e.tagName === "INPUT" && ee(e, "type") === "submit")
) {
const u = s.lastButtonClicked || e;
const f = ee(u, "name");
ln(f, u.value, o);
}
const c = we(e, "hx-include");
ie(c, function (e) {
fn(n, r, i, ce(e), l);
if (!h(e, "form")) {
ie(p(e).querySelectorAll(ot), function (e) {
fn(n, r, i, e, l);
});
}
});
hn(r, o);
return { errors: i, formData: r, values: kn(r) };
}
function pn(e, t, n) {
if (e !== "") {
e += "&";
}
if (String(n) === "[object Object]") {
n = JSON.stringify(n);
}
const r = encodeURIComponent(n);
e += encodeURIComponent(t) + "=" + r;
return e;
}
function gn(e) {
e = Pn(e);
let n = "";
e.forEach(function (e, t) {
n = pn(n, t, e);
});
return n;
}
function mn(e, t, n) {
const r = {
"HX-Request": "true",
"HX-Trigger": ee(e, "id"),
"HX-Trigger-Name": ee(e, "name"),
"HX-Target": a(t, "id"),
"HX-Current-URL": location.href,
};
Cn(e, "hx-headers", false, r);
if (n !== undefined) {
r["HX-Prompt"] = n;
}
if (oe(e).boosted) {
r["HX-Boosted"] = "true";
}
return r;
}
function yn(n, e) {
const t = ne(e, "hx-params");
if (t) {
if (t === "none") {
return new FormData();
} else if (t === "*") {
return n;
} else if (t.indexOf("not ") === 0) {
ie(t.slice(4).split(","), function (e) {
e = e.trim();
n.delete(e);
});
return n;
} else {
const r = new FormData();
ie(t.split(","), function (t) {
t = t.trim();
if (n.has(t)) {
n.getAll(t).forEach(function (e) {
r.append(t, e);
});
}
});
return r;
}
} else {
return n;
}
}
function xn(e) {
return !!ee(e, "href") && ee(e, "href").indexOf("#") >= 0;
}
function bn(e, t) {
const n = t || ne(e, "hx-swap");
const r = {
swapStyle: oe(e).boosted ? "innerHTML" : Q.config.defaultSwapStyle,
swapDelay: Q.config.defaultSwapDelay,
settleDelay: Q.config.defaultSettleDelay,
};
if (Q.config.scrollIntoViewOnBoost && oe(e).boosted && !xn(e)) {
r.show = "top";
}
if (n) {
const s = X(n);
if (s.length > 0) {
for (let e = 0; e < s.length; e++) {
const l = s[e];
if (l.indexOf("swap:") === 0) {
r.swapDelay = d(l.slice(5));
} else if (l.indexOf("settle:") === 0) {
r.settleDelay = d(l.slice(7));
} else if (l.indexOf("transition:") === 0) {
r.transition = l.slice(11) === "true";
} else if (l.indexOf("ignoreTitle:") === 0) {
r.ignoreTitle = l.slice(12) === "true";
} else if (l.indexOf("scroll:") === 0) {
const c = l.slice(7);
var o = c.split(":");
const u = o.pop();
var i = o.length > 0 ? o.join(":") : null;
r.scroll = u;
r.scrollTarget = i;
} else if (l.indexOf("show:") === 0) {
const f = l.slice(5);
var o = f.split(":");
const a = o.pop();
var i = o.length > 0 ? o.join(":") : null;
r.show = a;
r.showTarget = i;
} else if (l.indexOf("focus-scroll:") === 0) {
const h = l.slice("focus-scroll:".length);
r.focusScroll = h == "true";
} else if (e == 0) {
r.swapStyle = l;
} else {
R("Unknown modifier in hx-swap: " + l);
}
}
}
}
return r;
}
function vn(e) {
return (
ne(e, "hx-encoding") === "multipart/form-data" ||
(h(e, "form") && ee(e, "enctype") === "multipart/form-data")
);
}
function wn(t, n, r) {
let o = null;
jt(n, function (e) {
if (o == null) {
o = e.encodeParameters(t, r, n);
}
});
if (o != null) {
return o;
} else {
if (vn(n)) {
return hn(new FormData(), Pn(r));
} else {
return gn(r);
}
}
}
function Sn(e) {
return { tasks: [], elts: [e] };
}
function En(e, t) {
const n = e[0];
const r = e[e.length - 1];
if (t.scroll) {
var o = null;
if (t.scrollTarget) {
o = ce(ue(n, t.scrollTarget));
}
if (t.scroll === "top" && (n || o)) {
o = o || n;
o.scrollTop = 0;
}
if (t.scroll === "bottom" && (r || o)) {
o = o || r;
o.scrollTop = o.scrollHeight;
}
if (typeof t.scroll === "number") {
b().setTimeout(function () {
window.scrollTo(0, t.scroll);
}, 0);
}
}
if (t.show) {
var o = null;
if (t.showTarget) {
let e = t.showTarget;
if (t.showTarget === "window") {
e = "body";
}
o = ce(ue(n, e));
}
if (t.show === "top" && (n || o)) {
o = o || n;
o.scrollIntoView({ block: "start", behavior: Q.config.scrollBehavior });
}
if (t.show === "bottom" && (r || o)) {
o = o || r;
o.scrollIntoView({ block: "end", behavior: Q.config.scrollBehavior });
}
}
}
function Cn(r, e, o, i, s) {
if (i == null) {
i = {};
}
if (r == null) {
return i;
}
const l = a(r, e);
if (l) {
let e = l.trim();
let t = o;
if (e === "unset") {
return null;
}
if (e.indexOf("javascript:") === 0) {
e = e.slice(11);
t = true;
} else if (e.indexOf("js:") === 0) {
e = e.slice(3);
t = true;
}
if (e.indexOf("{") !== 0) {
e = "{" + e + "}";
}
let n;
if (t) {
n = On(
r,
function () {
if (s) {
return Function("event", "return (" + e + ")").call(r, s);
} else {
return Function("return (" + e + ")").call(r);
}
},
{},
);
} else {
n = v(e);
}
for (const c in n) {
if (n.hasOwnProperty(c)) {
if (i[c] == null) {
i[c] = n[c];
}
}
}
}
return Cn(ce(u(r)), e, o, i, s);
}
function On(e, t, n) {
if (Q.config.allowEval) {
return t();
} else {
fe(e, "htmx:evalDisallowedError");
return n;
}
}
function Rn(e, t, n) {
return Cn(e, "hx-vars", true, n, t);
}
function Hn(e, t, n) {
return Cn(e, "hx-vals", false, n, t);
}
function Tn(e, t) {
return le(Rn(e, t), Hn(e, t));
}
function qn(t, n, r) {
if (r !== null) {
try {
t.setRequestHeader(n, r);
} catch (e) {
t.setRequestHeader(n, encodeURIComponent(r));
t.setRequestHeader(n + "-URI-AutoEncoded", "true");
}
}
}
function An(t) {
if (t.responseURL) {
try {
const e = new URL(t.responseURL);
return e.pathname + e.search;
} catch (e) {
fe(te().body, "htmx:badResponseUrl", { url: t.responseURL });
}
}
}
function H(e, t) {
return t.test(e.getAllResponseHeaders());
}
function Ln(t, n, r) {
t = t.toLowerCase();
if (r) {
if (r instanceof Element || typeof r === "string") {
return he(t, n, null, null, {
targetOverride: w(r) || ve,
returnPromise: true,
});
} else {
let e = w(r.target);
if ((r.target && !e) || (r.source && !e && !w(r.source))) {
e = ve;
}
return he(t, n, w(r.source), r.event, {
handler: r.handler,
headers: r.headers,
values: r.values,
targetOverride: e,
swapOverride: r.swap,
select: r.select,
returnPromise: true,
});
}
} else {
return he(t, n, null, null, { returnPromise: true });
}
}
function Nn(e) {
const t = [];
while (e) {
t.push(e);
e = e.parentElement;
}
return t;
}
function In(e, t, n) {
const r = new URL(
t,
location.protocol !== "about:" ? location.href : window.origin,
);
const o = location.protocol !== "about:" ? location.origin : window.origin;
const i = o === r.origin;
if (Q.config.selfRequestsOnly) {
if (!i) {
return false;
}
}
return ae(e, "htmx:validateUrl", le({ url: r, sameHost: i }, n));
}
function Pn(e) {
if (e instanceof FormData) return e;
const t = new FormData();
for (const n in e) {
if (e.hasOwnProperty(n)) {
if (e[n] && typeof e[n].forEach === "function") {
e[n].forEach(function (e) {
t.append(n, e);
});
} else if (typeof e[n] === "object" && !(e[n] instanceof Blob)) {
t.append(n, JSON.stringify(e[n]));
} else {
t.append(n, e[n]);
}
}
}
return t;
}
function Dn(r, o, e) {
return new Proxy(e, {
get: function (t, e) {
if (typeof e === "number") return t[e];
if (e === "length") return t.length;
if (e === "push") {
return function (e) {
t.push(e);
r.append(o, e);
};
}
if (typeof t[e] === "function") {
return function () {
t[e].apply(t, arguments);
r.delete(o);
t.forEach(function (e) {
r.append(o, e);
});
};
}
if (t[e] && t[e].length === 1) {
return t[e][0];
} else {
return t[e];
}
},
set: function (e, t, n) {
e[t] = n;
r.delete(o);
e.forEach(function (e) {
r.append(o, e);
});
return true;
},
});
}
function kn(o) {
return new Proxy(o, {
get: function (e, t) {
if (typeof t === "symbol") {
const r = Reflect.get(e, t);
if (typeof r === "function") {
return function () {
return r.apply(o, arguments);
};
} else {
return r;
}
}
if (t === "toJSON") {
return () => Object.fromEntries(o);
}
if (t in e) {
if (typeof e[t] === "function") {
return function () {
return o[t].apply(o, arguments);
};
}
}
const n = o.getAll(t);
if (n.length === 0) {
return undefined;
} else if (n.length === 1) {
return n[0];
} else {
return Dn(e, t, n);
}
},
set: function (t, n, e) {
if (typeof n !== "string") {
return false;
}
t.delete(n);
if (e && typeof e.forEach === "function") {
e.forEach(function (e) {
t.append(n, e);
});
} else if (typeof e === "object" && !(e instanceof Blob)) {
t.append(n, JSON.stringify(e));
} else {
t.append(n, e);
}
return true;
},
deleteProperty: function (e, t) {
if (typeof t === "string") {
e.delete(t);
}
return true;
},
ownKeys: function (e) {
return Reflect.ownKeys(Object.fromEntries(e));
},
getOwnPropertyDescriptor: function (e, t) {
return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e), t);
},
});
}
function he(t, n, r, o, i, k) {
let s = null;
let l = null;
i = i != null ? i : {};
if (i.returnPromise && typeof Promise !== "undefined") {
var e = new Promise(function (e, t) {
s = e;
l = t;
});
}
if (r == null) {
r = te().body;
}
const M = i.handler || jn;
const F = i.select || null;
if (!se(r)) {
re(s);
return e;
}
const c = i.targetOverride || ce(Ee(r));
if (c == null || c == ve) {
fe(r, "htmx:targetError", { target: ne(r, "hx-target") });
re(l);
return e;
}
let u = oe(r);
const f = u.lastButtonClicked;
if (f) {
const A = ee(f, "formaction");
if (A != null) {
n = A;
}
const L = ee(f, "formmethod");
if (L != null) {
if (de.includes(L.toLowerCase())) {
t = L;
} else {
re(s);
return e;
}
}
}
const a = ne(r, "hx-confirm");
if (k === undefined) {
const K = function (e) {
return he(t, n, r, o, i, !!e);
};
const G = {
target: c,
elt: r,
path: n,
verb: t,
triggeringEvent: o,
etc: i,
issueRequest: K,
question: a,
};
if (ae(r, "htmx:confirm", G) === false) {
re(s);
return e;
}
}
let h = r;
let d = ne(r, "hx-sync");
let p = null;
let X = false;
if (d) {
const N = d.split(":");
const I = N[0].trim();
if (I === "this") {
h = Se(r, "hx-sync");
} else {
h = ce(ue(r, I));
}
d = (N[1] || "drop").trim();
u = oe(h);
if (d === "drop" && u.xhr && u.abortable !== true) {
re(s);
return e;
} else if (d === "abort") {
if (u.xhr) {
re(s);
return e;
} else {
X = true;
}
} else if (d === "replace") {
ae(h, "htmx:abort");
} else if (d.indexOf("queue") === 0) {
const W = d.split(" ");
p = (W[1] || "last").trim();
}
}
if (u.xhr) {
if (u.abortable) {
ae(h, "htmx:abort");
} else {
if (p == null) {
if (o) {
const P = oe(o);
if (P && P.triggerSpec && P.triggerSpec.queue) {
p = P.triggerSpec.queue;
}
}
if (p == null) {
p = "last";
}
}
if (u.queuedRequests == null) {
u.queuedRequests = [];
}
if (p === "first" && u.queuedRequests.length === 0) {
u.queuedRequests.push(function () {
he(t, n, r, o, i);
});
} else if (p === "all") {
u.queuedRequests.push(function () {
he(t, n, r, o, i);
});
} else if (p === "last") {
u.queuedRequests = [];
u.queuedRequests.push(function () {
he(t, n, r, o, i);
});
}
re(s);
return e;
}
}
const g = new XMLHttpRequest();
u.xhr = g;
u.abortable = X;
const m = function () {
u.xhr = null;
u.abortable = false;
if (u.queuedRequests != null && u.queuedRequests.length > 0) {
const e = u.queuedRequests.shift();
e();
}
};
const B = ne(r, "hx-prompt");
if (B) {
var y = prompt(B);
if (y === null || !ae(r, "htmx:prompt", { prompt: y, target: c })) {
re(s);
m();
return e;
}
}
if (a && !k) {
if (!confirm(a)) {
re(s);
m();
return e;
}
}
let x = mn(r, c, y);
if (t !== "get" && !vn(r)) {
x["Content-Type"] = "application/x-www-form-urlencoded";
}
if (i.headers) {
x = le(x, i.headers);
}
const U = dn(r, t);
let b = U.errors;
const j = U.formData;
if (i.values) {
hn(j, Pn(i.values));
}
const V = Pn(Tn(r, o));
const v = hn(j, V);
let w = yn(v, r);
if (Q.config.getCacheBusterParam && t === "get") {
w.set("org.htmx.cache-buster", ee(c, "id") || "true");
}
if (n == null || n === "") {
n = location.href;
}
const S = Cn(r, "hx-request");
const _ = oe(r).boosted;
let E = Q.config.methodsThatUseUrlParams.indexOf(t) >= 0;
const C = {
boosted: _,
useUrlParams: E,
formData: w,
parameters: kn(w),
unfilteredFormData: v,
unfilteredParameters: kn(v),
headers: x,
elt: r,
target: c,
verb: t,
errors: b,
withCredentials:
i.credentials || S.credentials || Q.config.withCredentials,
timeout: i.timeout || S.timeout || Q.config.timeout,
path: n,
triggeringEvent: o,
};
if (!ae(r, "htmx:configRequest", C)) {
re(s);
m();
return e;
}
n = C.path;
t = C.verb;
x = C.headers;
w = Pn(C.parameters);
b = C.errors;
E = C.useUrlParams;
if (b && b.length > 0) {
ae(r, "htmx:validation:halted", C);
re(s);
m();
return e;
}
const z = n.split("#");
const $ = z[0];
const O = z[1];
let R = n;
if (E) {
R = $;
const Z = !w.keys().next().done;
if (Z) {
if (R.indexOf("?") < 0) {
R += "?";
} else {
R += "&";
}
R += gn(w);
if (O) {
R += "#" + O;
}
}
}
if (!In(r, R, C)) {
fe(r, "htmx:invalidPath", C);
re(l);
m();
return e;
}
g.open(t.toUpperCase(), R, true);
g.overrideMimeType("text/html");
g.withCredentials = C.withCredentials;
g.timeout = C.timeout;
if (S.noHeaders) {
} else {
for (const D in x) {
if (x.hasOwnProperty(D)) {
const Y = x[D];
qn(g, D, Y);
}
}
}
const H = {
xhr: g,
target: c,
requestConfig: C,
etc: i,
boosted: _,
select: F,
pathInfo: {
requestPath: n,
finalRequestPath: R,
responsePath: null,
anchor: O,
},
};
g.onload = function () {
try {
const t = Nn(r);
H.pathInfo.responsePath = An(g);
M(r, H);
if (H.keepIndicators !== true) {
rn(T, q);
}
ae(r, "htmx:afterRequest", H);
ae(r, "htmx:afterOnLoad", H);
if (!se(r)) {
let e = null;
while (t.length > 0 && e == null) {
const n = t.shift();
if (se(n)) {
e = n;
}
}
if (e) {
ae(e, "htmx:afterRequest", H);
ae(e, "htmx:afterOnLoad", H);
}
}
re(s);
} catch (e) {
fe(r, "htmx:onLoadError", le({ error: e }, H));
throw e;
} finally {
m();
}
};
g.onerror = function () {
rn(T, q);
fe(r, "htmx:afterRequest", H);
fe(r, "htmx:sendError", H);
re(l);
m();
};
g.onabort = function () {
rn(T, q);
fe(r, "htmx:afterRequest", H);
fe(r, "htmx:sendAbort", H);
re(l);
m();
};
g.ontimeout = function () {
rn(T, q);
fe(r, "htmx:afterRequest", H);
fe(r, "htmx:timeout", H);
re(l);
m();
};
if (!ae(r, "htmx:beforeRequest", H)) {
re(s);
m();
return e;
}
var T = tn(r);
var q = nn(r);
ie(["loadstart", "loadend", "progress", "abort"], function (t) {
ie([g, g.upload], function (e) {
e.addEventListener(t, function (e) {
ae(r, "htmx:xhr:" + t, {
lengthComputable: e.lengthComputable,
loaded: e.loaded,
total: e.total,
});
});
});
});
ae(r, "htmx:beforeSend", H);
const J = E ? null : wn(g, r, w);
g.send(J);
return e;
}
function Mn(e, t) {
const n = t.xhr;
let r = null;
let o = null;
if (H(n, /HX-Push:/i)) {
r = n.getResponseHeader("HX-Push");
o = "push";
} else if (H(n, /HX-Push-Url:/i)) {
r = n.getResponseHeader("HX-Push-Url");
o = "push";
} else if (H(n, /HX-Replace-Url:/i)) {
r = n.getResponseHeader("HX-Replace-Url");
o = "replace";
}
if (r) {
if (r === "false") {
return {};
} else {
return { type: o, path: r };
}
}
const i = t.pathInfo.finalRequestPath;
const s = t.pathInfo.responsePath;
const l = ne(e, "hx-push-url");
const c = ne(e, "hx-replace-url");
const u = oe(e).boosted;
let f = null;
let a = null;
if (l) {
f = "push";
a = l;
} else if (c) {
f = "replace";
a = c;
} else if (u) {
f = "push";
a = s || i;
}
if (a) {
if (a === "false") {
return {};
}
if (a === "true") {
a = s || i;
}
if (t.pathInfo.anchor && a.indexOf("#") === -1) {
a = a + "#" + t.pathInfo.anchor;
}
return { type: f, path: a };
} else {
return {};
}
}
function Fn(e, t) {
var n = new RegExp(e.code);
return n.test(t.toString(10));
}
function Xn(e) {
for (var t = 0; t < Q.config.responseHandling.length; t++) {
var n = Q.config.responseHandling[t];
if (Fn(n, e.status)) {
return n;
}
}
return { swap: false };
}
function Bn(e) {
if (e) {
const t = f("title");
if (t) {
t.textContent = e;
} else {
window.document.title = e;
}
}
}
function Un(e, t) {
if (t === "this") {
return e;
}
const n = ce(ue(e, t));
if (n == null) {
fe(e, "htmx:targetError", { target: t });
throw new Error(`Invalid re-target ${t}`);
}
return n;
}
function jn(t, e) {
const n = e.xhr;
let r = e.target;
const o = e.etc;
const i = e.select;
if (!ae(t, "htmx:beforeOnLoad", e)) return;
if (H(n, /HX-Trigger:/i)) {
Je(n, "HX-Trigger", t);
}
if (H(n, /HX-Location:/i)) {
Gt();
let e = n.getResponseHeader("HX-Location");
var s;
if (e.indexOf("{") === 0) {
s = v(e);
e = s.path;
delete s.path;
}
Ln("get", e, s).then(function () {
Wt(e);
});
return;
}
const l =
H(n, /HX-Refresh:/i) && n.getResponseHeader("HX-Refresh") === "true";
if (H(n, /HX-Redirect:/i)) {
e.keepIndicators = true;
Q.location.href = n.getResponseHeader("HX-Redirect");
l && Q.location.reload();
return;
}
if (l) {
e.keepIndicators = true;
Q.location.reload();
return;
}
const c = Mn(t, e);
const u = Xn(n);
const f = u.swap;
let a = !!u.error;
let h = Q.config.ignoreTitle || u.ignoreTitle;
let d = u.select;
if (u.target) {
e.target = Un(t, u.target);
}
var p = o.swapOverride;
if (p == null && u.swapOverride) {
p = u.swapOverride;
}
if (H(n, /HX-Retarget:/i)) {
e.target = Un(t, n.getResponseHeader("HX-Retarget"));
}
if (H(n, /HX-Reswap:/i)) {
p = n.getResponseHeader("HX-Reswap");
}
var g = n.response;
var m = le(
{
shouldSwap: f,
serverResponse: g,
isError: a,
ignoreTitle: h,
selectOverride: d,
swapOverride: p,
},
e,
);
if (u.event && !ae(r, u.event, m)) return;
if (!ae(r, "htmx:beforeSwap", m)) return;
r = m.target;
g = m.serverResponse;
a = m.isError;
h = m.ignoreTitle;
d = m.selectOverride;
p = m.swapOverride;
e.target = r;
e.failed = a;
e.successful = !a;
if (m.shouldSwap) {
if (n.status === 286) {
lt(t);
}
jt(t, function (e) {
g = e.transformResponse(g, n, t);
});
if (c.type) {
Gt();
}
var y = bn(t, p);
if (!y.hasOwnProperty("ignoreTitle")) {
y.ignoreTitle = h;
}
r.classList.add(Q.config.swappingClass);
if (i) {
d = i;
}
if (H(n, /HX-Reselect:/i)) {
d = n.getResponseHeader("HX-Reselect");
}
const x = ne(t, "hx-select-oob");
const b = ne(t, "hx-select");
$e(r, g, y, {
select: d === "unset" ? null : d || b,
selectOOB: x,
eventInfo: e,
anchor: e.pathInfo.anchor,
contextElement: t,
afterSwapCallback: function () {
if (H(n, /HX-Trigger-After-Swap:/i)) {
let e = t;
if (!se(t)) {
e = te().body;
}
Je(n, "HX-Trigger-After-Swap", e);
}
},
afterSettleCallback: function () {
if (H(n, /HX-Trigger-After-Settle:/i)) {
let e = t;
if (!se(t)) {
e = te().body;
}
Je(n, "HX-Trigger-After-Settle", e);
}
},
beforeSwapCallback: function () {
if (c.type) {
ae(te().body, "htmx:beforeHistoryUpdate", le({ history: c }, e));
if (c.type === "push") {
Wt(c.path);
ae(te().body, "htmx:pushedIntoHistory", { path: c.path });
} else {
Zt(c.path);
ae(te().body, "htmx:replacedInHistory", { path: c.path });
}
}
},
});
}
if (a) {
fe(
t,
"htmx:responseError",
le(
{
error:
"Response Status Error Code " +
n.status +
" from " +
e.pathInfo.requestPath,
},
e,
),
);
}
}
const Vn = {};
function _n() {
return {
init: function (e) {
return null;
},
getSelectors: function () {
return null;
},
onEvent: function (e, t) {
return true;
},
transformResponse: function (e, t, n) {
return e;
},
isInlineSwap: function (e) {
return false;
},
handleSwap: function (e, t, n, r) {
return false;
},
encodeParameters: function (e, t, n) {
return null;
},
};
}
function zn(e, t) {
if (t.init) {
t.init(n);
}
Vn[e] = le(_n(), t);
}
function $n(e) {
delete Vn[e];
}
function Jn(e, n, r) {
if (n == undefined) {
n = [];
}
if (e == undefined) {
return n;
}
if (r == undefined) {
r = [];
}
const t = a(e, "hx-ext");
if (t) {
ie(t.split(","), function (e) {
e = e.replace(/ /g, "");
if (e.slice(0, 7) == "ignore:") {
r.push(e.slice(7));
return;
}
if (r.indexOf(e) < 0) {
const t = Vn[e];
if (t && n.indexOf(t) < 0) {
n.push(t);
}
}
});
}
return Jn(ce(u(e)), n, r);
}
var Kn = false;
te().addEventListener("DOMContentLoaded", function () {
Kn = true;
});
function Gn(e) {
if (Kn || te().readyState === "complete") {
e();
} else {
te().addEventListener("DOMContentLoaded", e);
}
}
function Wn() {
if (Q.config.includeIndicatorStyles !== false) {
const e = Q.config.inlineStyleNonce
? ` nonce="${Q.config.inlineStyleNonce}"`
: "";
te().head.insertAdjacentHTML(
"beforeend",
"<style" +
e +
"> ." +
Q.config.indicatorClass +
"{opacity:0} ." +
Q.config.requestClass +
" ." +
Q.config.indicatorClass +
"{opacity:1; transition: opacity 200ms ease-in;} ." +
Q.config.requestClass +
"." +
Q.config.indicatorClass +
"{opacity:1; transition: opacity 200ms ease-in;} </style>",
);
}
}
function Zn() {
const e = te().querySelector('meta[name="htmx-config"]');
if (e) {
return v(e.content);
} else {
return null;
}
}
function Yn() {
const e = Zn();
if (e) {
Q.config = le(Q.config, e);
}
}
Gn(function () {
Yn();
Wn();
let e = te().body;
Ft(e);
const t = te().querySelectorAll(
"[hx-trigger='restored'],[data-hx-trigger='restored']",
);
e.addEventListener("htmx:abort", function (e) {
const t = e.target;
const n = oe(t);
if (n && n.xhr) {
n.xhr.abort();
}
});
const n = window.onpopstate ? window.onpopstate.bind(window) : null;
window.onpopstate = function (e) {
if (e.state && e.state.htmx) {
en();
ie(t, function (e) {
ae(e, "htmx:restored", { document: te(), triggerEvent: ae });
});
} else {
if (n) {
n(e);
}
}
};
b().setTimeout(function () {
ae(e, "htmx:load", {});
e = null;
}, 0);
});
return Q;
})();
================================================
FILE: cmd/template/advanced/files/htmx/imports/fiber.tmpl
================================================
"github.com/a-h/templ"
"{{.ProjectName}}/cmd/web"
"github.com/gofiber/fiber/v2/middleware/adaptor"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"net/http"
================================================
FILE: cmd/template/advanced/files/htmx/imports/gin.tmpl
================================================
"github.com/a-h/templ"
"{{.ProjectName}}/cmd/web"
"io/fs"
================================================
FILE: cmd/template/advanced/files/htmx/imports/standard_library.tmpl
================================================
"github.com/a-h/templ"
"{{.ProjectName}}/cmd/web"
================================================
FILE: cmd/template/advanced/files/htmx/routes/chi.tmpl
================================================
fileServer := http.FileServer(http.FS(web.Files))
r.Handle("/assets/*", fileServer)
r.Get("/web", templ.Handler(web.HelloForm()).ServeHTTP)
r.Post("/hello", web.HelloWebHandler)
================================================
FILE: cmd/template/advanced/files/htmx/routes/echo.tmpl
================================================
fileServer := http.FileServer(http.FS(web.Files))
e.GET("/assets/*", echo.WrapHandler(fileServer))
e.GET("/web", echo.WrapHandler(templ.Handler(web.HelloForm())))
e.POST("/hello", echo.WrapHandler(http.HandlerFunc(web.HelloWebHandler)))
================================================
FILE: cmd/template/advanced/files/htmx/routes/fiber.tmpl
================================================
s.App.Use("/assets", filesystem.New(filesystem.Config{
Root: http.FS(web.Files),
PathPrefix: "assets",
Browse: false,
}))
s.App.Get("/web", adaptor.HTTPHandler(templ.Handler(web.HelloForm())))
s.App.Post("/hello", func(c *fiber.Ctx) error {
return web.HelloWebHandler(c)
})
================================================
FILE: cmd/template/advanced/files/htmx/routes/gin.tmpl
================================================
staticFiles, _ := fs.Sub(web.Files, "assets")
r.StaticFS("/assets", http.FS(staticFiles))
r.GET("/web", func(c *gin.Context) {
templ.Handler(web.HelloForm()).ServeHTTP(c.Writer, c.Request)
})
r.POST("/hello", func(c *gin.Context) {
web.HelloWebHandler(c.Writer, c.Request)
})
================================================
FILE: cmd/template/advanced/files/htmx/routes/gorilla.tmpl
================================================
fileServer := http.FileServer(http.FS(web.Files))
r.PathPrefix("/assets/").Handler(fileServer)
r.HandleFunc("/web", func(w http.ResponseWriter, r *http.Request) {
templ.Handler(web.HelloForm()).ServeHTTP(w, r)
})
r.HandleFunc("/hello", web.HelloWebHandler)
================================================
FILE: cmd/template/advanced/files/htmx/routes/http_router.tmpl
================================================
fileServer := http.FileServer(http.FS(web.Files))
r.Handler(http.MethodGet, "/assets/*filepath", fileServer)
r.Handler(http.MethodGet, "/web", templ.Handler(web.HelloForm()))
r.HandlerFunc(http.MethodPost, "/hello", web.HelloWebHandler)
================================================
FILE: cmd/template/advanced/files/htmx/routes/standard_library.tmpl
================================================
fileServer := http.FileServer(http.FS(web.Files))
mux.Handle("/assets/", fileServer)
mux.Handle("/web", templ.Handler(web.HelloForm()))
mux.HandleFunc("/hello", web.HelloWebHandler)
================================================
FILE: cmd/template/advanced/files/htmx/tailwind/tailwind.config.js.tmpl
================================================
module.exports = {
content: ["./**/*.html", "./**/*.templ", "./**/*.go",],
theme: { extend: {}, },
plugins: [],
}
================================================
FILE: cmd/template/advanced/files/react/app.tsx.tmpl
================================================
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
const fetchData = () => {
fetch(`http://localhost:${import.meta.env.VITE_PORT}/`)
.then(response => response.text())
.then(data => setMessage(data))
.catch(error => console.error('Error fetching data:', error))
}
const [message, setMessage] = useState<string>('')
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<button onClick={fetchData}>
Click to fetch from Go server
</button>
{message && (
<div>
<h2>Server Response:</h2>
<p>{message}</p>
</div>
)}
</>
)
}
export default App
================================================
FILE: cmd/template/advanced/files/react/tailwind/app.tsx.tmpl
================================================
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
const [message, setMessage] = useState<string>('')
const fetchData = () => {
fetch(`http://localhost:${import.meta.env.VITE_PORT}/`)
.then(response => response.text())
.then(data => setMessage(data))
.catch(error => console.error('Error fetching data:', error))
}
return (
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md mx-auto space-y-8">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Welcome to Vite + React
</h1>
<p className="text-gray-600">
Get started by editing <code className="text-sm bg-gray-100 p-1 rounded">src/App.tsx</code>
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<div className="text-center space-y-4">
<button
onClick={() => setCount((count) => count + 1)}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-md transition-colors"
>
Count is {count}
</button>
<button
onClick={fetchData}
className="block w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded-md transition-colors"
>
Fetch from Server
</button>
{message && (
<div className="mt-4 p-4 bg-gray-50 rounded-md">
<p className="text-gray-700">Server Response:</p>
<p className="text-gray-900 font-medium">{message}</p>
</div>
)}
</div>
</div>
<div className="text-center text-gray-500 text-sm">
Built with Vite, React, and Tailwind CSS
</div>
</div>
</div>
)
}
export default App
================================================
FILE: cmd/template/advanced/files/react/tailwind/index.css.tmpl
================================================
@import "tailwindcss";
================================================
FILE: cmd/template/advanced/files/react/tailwind/vite.config.ts.tmpl
================================================
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(),tailwindcss()],
})
================================================
FILE: cmd/template/advanced/files/tailwind/input.css.tmpl
================================================
@import "tailwindcss"
================================================
FILE: cmd/template/advanced/files/tailwind/output.css.tmpl
================================================
================================================
FILE: cmd/template/advanced/files/websocket/imports/fiber.tmpl
================================================
"github.com/gofiber/contrib/websocket"
================================================
FILE: cmd/template/advanced/files/websocket/imports/standard_library.tmpl
================================================
"github.com/coder/websocket"
================================================
FILE: cmd/template/advanced/files/workflow/github/github_action_goreleaser.yml.tmpl
================================================
name: goreleaser
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22.x'
{{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}}
- name: Install templ
shell: bash
run: go install github.com/a-h/templ/cmd/templ@latest
- name: Run templ generate
shell: bash
run: templ generate -path .
{{end}}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: ${{"{{"}} env.GITHUB_REF_NAME {{"}}"}}
args: release --clean
workdir: ./
env:
GITHUB_TOKEN: ${{"{{"}} secrets.GITHUB_TOKEN {{"}}"}}
================================================
FILE: cmd/template/advanced/files/workflow/github/github_action_gotest.yml.tmpl
================================================
name: Go-test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.22.x'
{{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}}
- name: Install templ
shell: bash
run: go install github.com/a-h/templ/cmd/templ@latest
- name: Run templ generate
shell: bash
run: templ generate -path .
{{end}}
- name: Build
run: go build -v ./...
- name: Test with the Go CLI
run: go test ./...
================================================
FILE: cmd/template/advanced/files/workflow/github/github_action_releaser_config.yml.tmpl
================================================
version: 2
before:
hooks:
- go mod tidy
env:
- PACKAGE_PATH=github.com/<user>/<repo>/cmd
builds:
- binary: "{{"{{"}} .ProjectName {{"}}"}}"
main: ./cmd/api
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X {{"{{"}}.Env.PACKAGE_PATH{{"}}"}}={{"{{"}}.Version{{"}}"}}
release:
prerelease: auto
universal_binaries:
- replace: true
archives:
- name_template: >
{{"{{"}}- .ProjectName {{"}}"}}_{{"{{"}}- .Version {{"}}"}}_{{"{{"}}- title .Os {{"}}"}}_{{"{{"}}- if eq .Arch "amd64" {{"}}"}}x86_64{{"{{"}}- else if eq .Arch "386" {{"}}"}}i386{{"{{"}}- else {{"}}"}}{{"{{"}} .Arch {{"}}"}}{{"{{"}} end {{"}}"}}{{"{{"}}- if .Arm {{"}}"}}v{{"{{"}} .Arm {{"}}"}}{{"{{"}} end -{{"}}"}}
format_overrides:
- goos: windows
format: zip
builds_info:
group: root
owner: root
files:
- README.md
checksum:
name_template: 'checksums.txt'
================================================
FILE: cmd/template/advanced/gitHubAction.go
================================================
package advanced
import (
_ "embed"
)
//go:embed files/workflow/github/github_action_goreleaser.yml.tmpl
var gitHubActionBuildTemplate []byte
//go:embed files/workflow/github/github_action_gotest.yml.tmpl
var gitHubActionTestTemplate []byte
//go:embed files/workflow/github/github_action_releaser_config.yml.tmpl
var gitHubActionConfigTemplate []byte
func Releaser() []byte {
return gitHubActionBuildTemplate
}
func Test() []byte {
return gitHubActionTestTemplate
}
func ReleaserConfig() []byte {
return gitHubActionConfigTemplate
}
================================================
FILE: cmd/template/advanced/routes.go
================================================
package advanced
import (
_ "embed"
)
//go:embed files/htmx/hello.templ.tmpl
var helloTemplTemplate []byte
//go:embed files/htmx/base.templ.tmpl
var baseTemplTemplate []byte
//go:embed files/react/tailwind/index.css.tmpl
var inputCssTemplateReact []byte
//go:embed files/react/tailwind/vite.config.ts.tmpl
var viteTailwindConfigFile []byte
//go:embed files/react/tailwind/app.tsx.tmpl
var reactTailwindAppFile []byte
//go:embed files/react/app.tsx.tmpl
var reactAppFile []byte
//go:embed files/tailwind/input.css.tmpl
var inputCssTemplate []byte
//go:embed files/tailwind/output.css.tmpl
var outputCssTemplate []byte
//go:embed files/htmx/tailwind/tailwind.config.js.tmpl
var htmxTailwindConfigJsTemplate []byte
//go:embed files/htmx/htmx.min.js.tmpl
var htmxMinJsTemplate []byte
//go:embed files/htmx/efs.go.tmpl
var efsTemplate []byte
//go:embed files/htmx/hello.go.tmpl
var helloGoTemplate []byte
//go:embed files/htmx/hello_fiber.go.tmpl
var helloFiberGoTemplate []byte
//go:embed files/htmx/routes/http_router.tmpl
var httpRouterHtmxTemplRoutes []byte
//go:embed files/htmx/routes/standard_library.tmpl
var stdLibHtmxTemplRoutes []byte
//go:embed files/htmx/imports/standard_library.tmpl
var stdLibHtmxTemplImports []byte
//go:embed files/websocket/imports/standard_library.tmpl
var stdLibWebsocketImports []byte
//go:embed files/htmx/routes/chi.tmpl
var chiHtmxTemplRoutes []byte
//go:embed files/htmx/routes/gin.tmpl
var ginHtmxTemplRoutes []byte
//go:embed files/htmx/imports/gin.tmpl
var ginHtmxTemplImports []byte
//go:embed files/htmx/routes/gorilla.tmpl
var gorillaHtmxTemplRoutes []byte
//go:embed files/htmx/routes/echo.tmpl
var echoHtmxTemplRoutes []byte
//go:embed files/htmx/routes/fiber.tmpl
var fiberHtmxTemplRoutes []byte
//go:embed files/htmx/imports/fiber.tmpl
var fiberHtmxTemplImports []byte
//go:embed files/websocket/imports/fiber.tmpl
var fiberWebsocketTemplImports []byte
func EchoHtmxTemplRoutesTemplate() []byte {
return echoHtmxTemplRoutes
}
func GorillaHtmxTemplRoutesTemplate() []byte {
return gorillaHtmxTemplRoutes
}
func ChiHtmxTemplRoutesTemplate() []byte {
return chiHtmxTemplRoutes
}
func GinHtmxTemplRoutesTemplate() []byte {
return ginHtmxTemplRoutes
}
func HttpRouterHtmxTemplRoutesTemplate() []byte {
return httpRouterHtmxTemplRoutes
}
func StdLibHtmxTemplRoutesTemplate() []byte {
return stdLibHtmxTemplRoutes
}
func StdLibHtmxTemplImportsTemplate() []byte {
return stdLibHtmxTemplImports
}
func StdLibWebsocketTemplImportsTemplate() []byte {
return stdLibWebsocketImports
}
func HelloTemplTemplate() []byte {
return helloTemplTemplate
}
func BaseTemplTemplate() []byte {
return baseTemplTemplate
}
func ReactTailwindAppfile() []byte {
return reactTailwindAppFile
}
func ReactAppfile() []byte {
return reactAppFile
}
func InputCssTemplateReact() []byte {
return inputCssTemplateReact
}
func ViteTailwindConfigFile() []byte {
return viteTailwindConfigFile
}
func InputCssTemplate() []byte {
return inputCssTemplate
}
func OutputCssTemplate() []byte {
return outputCssTemplate
}
func HtmxTailwindConfigJsTemplate() []byte {
return htmxTailwindConfigJsTemplate
}
func HtmxJSTemplate() []byte {
return htmxMinJsTemplate
}
func EfsTemplate() []byte {
return efsTemplate
}
func HelloGoTemplate() []byte {
return helloGoTemplate
}
func HelloFiberGoTemplate() []byte {
return helloFiberGoTemplate
}
func FiberHtmxTemplRoutesTemplate() []byte {
return fiberHtmxTemplRoutes
}
func FiberHtmxTemplImportsTemplate() []byte {
return fiberHtmxTemplImports
}
func FiberWebsocketTemplImportsTemplate() []byte {
return fiberWebsocketTemplImports
}
func GinHtmxTemplImportsTemplate() []byte {
return ginHtmxTemplImports
}
================================================
FILE: cmd/template/dbdriver/files/env/mongo.tmpl
================================================
{{ if .AdvancedOptions.docker }}
BLUEPRINT_DB_HOST=mongo_bp
{{- else }}
BLUEPRINT_DB_HOST=localhost
{{- end }}
BLUEPRINT_DB_PORT=27017
BLUEPRINT_DB_USERNAME=melkey
BLUEPRINT_DB_ROOT_PASSWORD=password1234
================================================
FILE: cmd/template/dbdriver/files/env/mysql.tmpl
================================================
{{- if .AdvancedOptions.docker }}
BLUEPRINT_DB_HOST=mysql_bp
{{- else }}
BLUEPRINT_DB_HOST=localhost
{{- end }}
BLUEPRINT_DB_PORT=3306
BLUEPRINT_DB_DATABASE=blueprint
BLUEPRINT_DB_USERNAME=melkey
BLUEPRINT_DB_PASSWORD=password1234
BLUEPRINT_DB_ROOT_PASSWORD=password4321
================================================
FILE: cmd/template/dbdriver/files/env/postgres.tmpl
================================================
{{- if .AdvancedOptions.docker }}
BLUEPRINT_DB_HOST=psql_bp
{{- else }}
BLUEPRINT_DB_HOST=localhost
{{- end }}
BLUEPRINT_DB_PORT=5432
BLUEPRINT_DB_DATABASE=blueprint
BLUEPRINT_DB_USERNAME=melkey
BLUEPRINT_DB_PASSWORD=password1234
BLUEPRINT_DB_SCHEMA=public
================================================
FILE: cmd/template/dbdriver/files/env/redis.tmpl
================================================
{{- if .AdvancedOptions.docker }}
BLUEPRINT_DB_ADDRESS=redis_bp
{{- else }}
BLUEPRINT_DB_ADDRESS=localhost
{{- end }}
BLUEPRINT_DB_PORT=6379
BLUEPRINT_DB_PASSWORD=
BLUEPRINT_DB_DATABASE=0
================================================
FILE: cmd/template/dbdriver/files/env/scylla.tmpl
================================================
{{- if .AdvancedOptions.docker }}
# BLUEPRINT_DB_HOSTS=scylla_bp:9042 # ScyllaDB default port
BLUEPRINT_DB_HOSTS=scylla_bp:19042 # ScyllaDB Shard-Aware port
{{- else }}
# BLUEPRINT_DB_HOSTS=localhost:9042 # ScyllaDB default port
BLUEPRINT_DB_HOSTS=localhost:19042 # ScyllaDB Shard-Aware port
{{- end }}
BLUEPRINT_DB_CONSISTENCY="LOCAL_QUORUM"
# BLUEPRINT_DB_USERNAME=
# BLUEPRINT_DB_PASSWORD=
================================================
FILE: cmd/template/dbdriver/files/env/sqlite.tmpl
================================================
{{- if .AdvancedOptions.docker }}
BLUEPRINT_DB_URL=./db/test.db
{{- else }}
BLUEPRINT_DB_URL=./test.db
{{- end }}
================================================
FILE: cmd/template/dbdriver/files/service/mongo.tmpl
================================================
package database
import (
"context"
"fmt"
"log"
"os"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
_ "github.com/joho/godotenv/autoload"
)
type Service interface {
Health() map[string]string
}
type service struct {
db *mongo.Client
}
var (
host = os.Getenv("BLUEPRINT_DB_HOST")
port = os.Getenv("BLUEPRINT_DB_PORT")
//database = os.Getenv("BLUEPRINT_DB_DATABASE")
)
func New() Service {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s", host, port)))
if err != nil {
log.Fatal(err)
}
return &service{
db: client,
}
}
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := s.db.Ping(ctx, nil)
if err != nil {
log.Fatalf("db down: %v", err)
}
return map[string]string{
"message": "It's healthy",
}
}
================================================
FILE: cmd/template/dbdriver/files/service/mysql.tmpl
================================================
package database
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"strconv"
"time"
_ "github.com/go-sql-driver/mysql"
_ "github.com/joho/godotenv/autoload"
)
// Service represents a service that interacts with a database.
type Service interface {
// Health returns a map of health status information.
// The keys and values in the map are service-specific.
Health() map[string]string
// Close terminates the database connection.
// It returns an error if the connection cannot be closed.
Close() error
}
type service struct {
db *sql.DB
}
var (
dbname = os.Getenv("BLUEPRINT_DB_DATABASE")
password = os.Getenv("BLUEPRINT_DB_PASSWORD")
username = os.Getenv("BLUEPRINT_DB_USERNAME")
port = os.Getenv("BLUEPRINT_DB_PORT")
host = os.Getenv("BLUEPRINT_DB_HOST")
dbInstance *service
)
func New() Service {
// Reuse Connection
if dbInstance != nil {
return dbInstance
}
// Opening a driver typically will not attempt to connect to the database.
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname))
if err != nil {
// This will not be a connection error, but a DSN parse error or
// another initialization error.
log.Fatal(err)
}
db.SetConnMaxLifetime(0)
db.SetMaxIdleConns(50)
db.SetMaxOpenConns(50)
dbInstance = &service{
db: db,
}
return dbInstance
}
// Health checks the health of the database connection by pinging the database.
// It returns a map with keys indicating various health statistics.
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
stats := make(map[string]string)
// Ping the database
err := s.db.PingContext(ctx)
if err != nil {
stats["status"] = "down"
stats["error"] = fmt.Sprintf("db down: %v", err)
log.Fatalf("db down: %v", err) // Log the error and terminate the program
return stats
}
// Database is up, add more statistics
stats["status"] = "up"
stats["message"] = "It's healthy"
// Get database stats (like open connections, in use, idle, etc.)
dbStats := s.db.Stats()
stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
stats["in_use"] = strconv.Itoa(dbStats.InUse)
stats["idle"] = strconv.Itoa(dbStats.Idle)
stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10)
stats["wait_duration"] = dbStats.WaitDuration.String()
stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)
// Evaluate stats to provide a health message
if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example
stats["message"] = "The database is experiencing heavy load."
}
if dbStats.WaitCount > 1000 {
stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks."
}
if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {
stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings."
}
if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {
stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern."
}
return stats
}
// Close closes the database connection.
// It logs a message indicating the disconnection from the specific database.
// If the connection is successfully closed, it returns nil.
// If an error occurs while closing the connection, it returns the error.
func (s *service) Close() error {
log.Printf("Disconnected from database: %s", dbname)
return s.db.Close()
}
================================================
FILE: cmd/template/dbdriver/files/service/postgres.tmpl
================================================
package database
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"strconv"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
_ "github.com/joho/godotenv/autoload"
)
// Service represents a service that interacts with a database.
type Service interface {
// Health returns a map of health status information.
// The keys and values in the map are service-specific.
Health() map[string]string
// Close terminates the database connection.
// It returns an error if the connection cannot be closed.
Close() error
}
type service struct {
db *sql.DB
}
var (
database = os.Getenv("BLUEPRINT_DB_DATABASE")
password = os.Getenv("BLUEPRINT_DB_PASSWORD")
username = os.Getenv("BLUEPRINT_DB_USERNAME")
port = os.Getenv("BLUEPRINT_DB_PORT")
host = os.Getenv("BLUEPRINT_DB_HOST")
schema = os.Getenv("BLUEPRINT_DB_SCHEMA")
dbInstance *service
)
func New() Service {
// Reuse Connection
if dbInstance != nil {
return dbInstance
}
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&search_path=%s", username, password, host, port, database, schema)
db, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatal(err)
}
dbInstance = &service{
db: db,
}
return dbInstance
}
// Health checks the health of the database connection by pinging the database.
// It returns a map with keys indicating various health statistics.
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
stats := make(map[string]string)
// Ping the database
err := s.db.PingContext(ctx)
if err != nil {
stats["status"] = "down"
stats["error"] = fmt.Sprintf("db down: %v", err)
log.Fatalf("db down: %v", err) // Log the error and terminate the program
return stats
}
// Database is up, add more statistics
stats["status"] = "up"
stats["message"] = "It's healthy"
// Get database stats (like open connections, in use, idle, etc.)
dbStats := s.db.Stats()
stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
stats["in_use"] = strconv.Itoa(dbStats.InUse)
stats["idle"] = strconv.Itoa(dbStats.Idle)
stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10)
stats["wait_duration"] = dbStats.WaitDuration.String()
stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)
// Evaluate stats to provide a health message
if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example
stats["message"] = "The database is experiencing heavy load."
}
if dbStats.WaitCount > 1000 {
stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks."
}
if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {
stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings."
}
if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {
stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern."
}
return stats
}
// Close closes the database connection.
// It logs a message indicating the disconnection from the specific database.
// If the connection is successfully closed, it returns nil.
// If an error occurs while closing the connection, it returns the error.
func (s *service) Close() error {
log.Printf("Disconnected from database: %s", database)
return s.db.Close()
}
================================================
FILE: cmd/template/dbdriver/files/service/redis.tmpl
================================================
package database
import (
"context"
"fmt"
"log"
"math"
"os"
"strconv"
"strings"
"time"
_ "github.com/joho/godotenv/autoload"
"github.com/redis/go-redis/v9"
)
type Service interface {
Health() map[string]string
}
type service struct {
db *redis.Client
}
var (
address = os.Getenv("BLUEPRINT_DB_ADDRESS")
port = os.Getenv("BLUEPRINT_DB_PORT")
password = os.Getenv("BLUEPRINT_DB_PASSWORD")
database = os.Getenv("BLUEPRINT_DB_DATABASE")
)
func New() Service {
num, err := strconv.Atoi(database)
if err != nil {
log.Fatalf("database incorrect %v", err)
}
fullAddress := fmt.Sprintf("%s:%s", address, port)
rdb := redis.NewClient(&redis.Options{
Addr: fullAddress,
Password: password,
DB: num,
// Note: It's important to add this for a secure connection. Most cloud services that offer Redis should already have this configured in their services.
// For manual setup, please refer to the Redis documentation: https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/
// TLSConfig: &tls.Config{
// MinVersion: tls.VersionTLS12,
// },
})
s := &service{db: rdb}
return s
}
// Health returns the health status and statistics of the Redis server.
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // Default is now 5s
defer cancel()
stats := make(map[string]string)
// Check Redis health and populate the stats map
stats = s.checkRedisHealth(ctx, stats)
return stats
}
// checkRedisHealth checks the health of the Redis server and adds the relevant statistics to the stats map.
func (s *service) checkRedisHealth(ctx context.Context, stats map[string]string) map[string]string {
// Ping the Redis server to check its availability.
pong, err := s.db.Ping(ctx).Result()
// Note: By extracting and simplifying like this, `log.Fatalf("db down: %v", err)`
// can be changed into a standard error instead of a fatal error.
if err != nil {
log.Fatalf("db down: %v", err)
}
// Redis is up
stats["redis_status"] = "up"
stats["redis_message"] = "It's healthy"
stats["redis_ping_response"] = pong
// Retrieve Redis server information.
info, err := s.db.Info(ctx).Result()
if err != nil {
stats["redis_message"] = fmt.Sprintf("Failed to retrieve Redis info: %v", err)
return stats
}
// Parse the Redis info response.
redisInfo := parseRedisInfo(info)
// Get the pool stats of the Redis client.
poolStats := s.db.PoolStats()
// Prepare the stats map with Redis server information and pool statistics.
// Note: The "stats" map in the code uses string keys and values,
// which is suitable for structuring and serializing the data for the frontend (e.g., JSON, XML, HTMX).
// Using string types allows for easy conversion and compatibility with various data formats,
// making it convenient to create health stats for monitoring or other purposes.
// Also note that any raw "memory" (e.g., used_memory) value here is in bytes and can be converted to megabytes or gigabytes as a float64.
stats["redis_version"] = redisInfo["redis_version"]
stats["redis_mode"] = redisI
gitextract_afvolof3/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ ├── config.yml
│ │ └── feature-request.yml
│ ├── pull_request_template.md
│ ├── semantic.yml
│ └── workflows/
│ ├── ci.yml
│ ├── docs.yml
│ ├── generate-linter-advanced.yml
│ ├── generate-linter-core.yml
│ ├── npm-publish.yml
│ ├── release.yml
│ ├── testcontainers.yml
│ └── update-htmx-version.yml
├── .gitignore
├── .goreleaser.yml
├── .pre-commit-config.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│ ├── create.go
│ ├── flags/
│ │ ├── advancedFeatures.go
│ │ ├── database.go
│ │ ├── frameworks.go
│ │ └── git.go
│ ├── program/
│ │ └── program.go
│ ├── root.go
│ ├── steps/
│ │ └── steps.go
│ ├── template/
│ │ ├── advanced/
│ │ │ ├── docker.go
│ │ │ ├── files/
│ │ │ │ ├── docker/
│ │ │ │ │ ├── docker_compose.yml.tmpl
│ │ │ │ │ └── dockerfile.tmpl
│ │ │ │ ├── htmx/
│ │ │ │ │ ├── base.templ.tmpl
│ │ │ │ │ ├── efs.go.tmpl
│ │ │ │ │ ├── hello.go.tmpl
│ │ │ │ │ ├── hello.templ.tmpl
│ │ │ │ │ ├── hello_fiber.go.tmpl
│ │ │ │ │ ├── htmx.min.js.tmpl
│ │ │ │ │ ├── imports/
│ │ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ │ ├── gin.tmpl
│ │ │ │ │ │ └── standard_library.tmpl
│ │ │ │ │ ├── routes/
│ │ │ │ │ │ ├── chi.tmpl
│ │ │ │ │ │ ├── echo.tmpl
│ │ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ │ ├── gin.tmpl
│ │ │ │ │ │ ├── gorilla.tmpl
│ │ │ │ │ │ ├── http_router.tmpl
│ │ │ │ │ │ └── standard_library.tmpl
│ │ │ │ │ └── tailwind/
│ │ │ │ │ └── tailwind.config.js.tmpl
│ │ │ │ ├── react/
│ │ │ │ │ ├── app.tsx.tmpl
│ │ │ │ │ └── tailwind/
│ │ │ │ │ ├── app.tsx.tmpl
│ │ │ │ │ ├── index.css.tmpl
│ │ │ │ │ └── vite.config.ts.tmpl
│ │ │ │ ├── tailwind/
│ │ │ │ │ ├── input.css.tmpl
│ │ │ │ │ └── output.css.tmpl
│ │ │ │ ├── websocket/
│ │ │ │ │ └── imports/
│ │ │ │ │ ├── fiber.tmpl
│ │ │ │ │ └── standard_library.tmpl
│ │ │ │ └── workflow/
│ │ │ │ └── github/
│ │ │ │ ├── github_action_goreleaser.yml.tmpl
│ │ │ │ ├── github_action_gotest.yml.tmpl
│ │ │ │ └── github_action_releaser_config.yml.tmpl
│ │ │ ├── gitHubAction.go
│ │ │ └── routes.go
│ │ ├── dbdriver/
│ │ │ ├── files/
│ │ │ │ ├── env/
│ │ │ │ │ ├── mongo.tmpl
│ │ │ │ │ ├── mysql.tmpl
│ │ │ │ │ ├── postgres.tmpl
│ │ │ │ │ ├── redis.tmpl
│ │ │ │ │ ├── scylla.tmpl
│ │ │ │ │ └── sqlite.tmpl
│ │ │ │ ├── service/
│ │ │ │ │ ├── mongo.tmpl
│ │ │ │ │ ├── mysql.tmpl
│ │ │ │ │ ├── postgres.tmpl
│ │ │ │ │ ├── redis.tmpl
│ │ │ │ │ ├── scylla.tmpl
│ │ │ │ │ └── sqlite.tmpl
│ │ │ │ └── tests/
│ │ │ │ ├── mongo.tmpl
│ │ │ │ ├── mysql.tmpl
│ │ │ │ ├── postgres.tmpl
│ │ │ │ ├── redis.tmpl
│ │ │ │ └── scylla.tmpl
│ │ │ ├── mongo.go
│ │ │ ├── mysql.go
│ │ │ ├── postgres.go
│ │ │ ├── redis.go
│ │ │ ├── scylla.go
│ │ │ └── sqlite.go
│ │ ├── docker/
│ │ │ ├── files/
│ │ │ │ └── docker-compose/
│ │ │ │ ├── mongo.tmpl
│ │ │ │ ├── mysql.tmpl
│ │ │ │ ├── postgres.tmpl
│ │ │ │ ├── redis.tmpl
│ │ │ │ └── scylla.tmpl
│ │ │ ├── mongo.go
│ │ │ ├── mysql.go
│ │ │ ├── postgres.go
│ │ │ ├── redis.go
│ │ │ └── scylla.go
│ │ ├── framework/
│ │ │ ├── chiRoutes.go
│ │ │ ├── echoRoutes.go
│ │ │ ├── fiberServer.go
│ │ │ ├── files/
│ │ │ │ ├── README.md.tmpl
│ │ │ │ ├── air.toml.tmpl
│ │ │ │ ├── gitignore.tmpl
│ │ │ │ ├── globalenv.tmpl
│ │ │ │ ├── main/
│ │ │ │ │ ├── fiber_main.go.tmpl
│ │ │ │ │ └── main.go.tmpl
│ │ │ │ ├── makefile.tmpl
│ │ │ │ ├── routes/
│ │ │ │ │ ├── chi.go.tmpl
│ │ │ │ │ ├── echo.go.tmpl
│ │ │ │ │ ├── fiber.go.tmpl
│ │ │ │ │ ├── gin.go.tmpl
│ │ │ │ │ ├── gorilla.go.tmpl
│ │ │ │ │ ├── http_router.go.tmpl
│ │ │ │ │ └── standard_library.go.tmpl
│ │ │ │ ├── server/
│ │ │ │ │ ├── fiber.go.tmpl
│ │ │ │ │ └── standard_library.go.tmpl
│ │ │ │ └── tests/
│ │ │ │ ├── default-test.go.tmpl
│ │ │ │ ├── echo-test.go.tmpl
│ │ │ │ ├── fiber-test.go.tmpl
│ │ │ │ └── gin-test.go.tmpl
│ │ │ ├── ginRoutes.go
│ │ │ ├── gorillaRoutes.go
│ │ │ ├── httpRoutes.go
│ │ │ ├── main.go
│ │ │ └── routerRoutes.go
│ │ └── globalEnv.go
│ ├── ui/
│ │ ├── multiInput/
│ │ │ └── multiInput.go
│ │ ├── multiSelect/
│ │ │ └── multiSelect.go
│ │ ├── spinner/
│ │ │ └── spinner.go
│ │ └── textinput/
│ │ ├── textinput.go
│ │ └── textinput_test.go
│ ├── utils/
│ │ ├── utils.go
│ │ └── utils_test.go
│ └── version.go
├── contributors.yml
├── docs/
│ ├── Makefile
│ ├── custom_theme/
│ │ └── main.html
│ ├── docs/
│ │ ├── advanced-flag/
│ │ │ ├── advanced-flag.md
│ │ │ ├── docker.md
│ │ │ ├── goreleaser.md
│ │ │ ├── htmx-templ.md
│ │ │ ├── react-vite.md
│ │ │ ├── tailwind.md
│ │ │ └── websocket.md
│ │ ├── blueprint-core/
│ │ │ ├── db-drivers.md
│ │ │ └── frameworks.md
│ │ ├── blueprint-ui.md
│ │ ├── creating-project/
│ │ │ ├── air.md
│ │ │ ├── makefile.md
│ │ │ └── project-init.md
│ │ ├── endpoints-test/
│ │ │ ├── mongo.md
│ │ │ ├── redis.md
│ │ │ ├── scylladb.md
│ │ │ ├── server.md
│ │ │ ├── sql.md
│ │ │ ├── web.md
│ │ │ └── websocket.md
│ │ ├── index.md
│ │ └── installation.md
│ ├── mkdocs.yml
│ └── requirements.txt
├── go.mod
├── go.sum
├── main.go
└── scripts/
├── completions.sh
└── create-npm-packages.sh
SYMBOL INDEX (249 symbols across 40 files)
FILE: cmd/create.go
constant logo (line 23) | logo = `
function init (line 42) | func init() {
type Options (line 62) | type Options struct
function doesDirectoryExistAndIsNotEmpty (line 322) | func doesDirectoryExistAndIsNotEmpty(name string) bool {
FILE: cmd/flags/advancedFeatures.go
type AdvancedFeatures (line 8) | type AdvancedFeatures
method String (line 21) | func (f AdvancedFeatures) String() string {
method Type (line 25) | func (f *AdvancedFeatures) Type() string {
method Set (line 29) | func (f *AdvancedFeatures) Set(value string) error {
constant Htmx (line 11) | Htmx string = "htmx"
constant GoProjectWorkflow (line 12) | GoProjectWorkflow string = "githubaction"
constant Websocket (line 13) | Websocket string = "websocket"
constant Tailwind (line 14) | Tailwind string = "tailwind"
constant React (line 15) | React string = "react"
constant Docker (line 16) | Docker string = "docker"
FILE: cmd/flags/database.go
type Database (line 8) | type Database
method String (line 25) | func (f Database) String() string {
method Type (line 29) | func (f *Database) Type() string {
method Set (line 33) | func (f *Database) Set(value string) error {
constant MySql (line 14) | MySql Database = "mysql"
constant Postgres (line 15) | Postgres Database = "postgres"
constant Sqlite (line 16) | Sqlite Database = "sqlite"
constant Mongo (line 17) | Mongo Database = "mongo"
constant Redis (line 18) | Redis Database = "redis"
constant Scylla (line 19) | Scylla Database = "scylla"
constant None (line 20) | None Database = "none"
FILE: cmd/flags/frameworks.go
type Framework (line 8) | type Framework
method String (line 25) | func (f Framework) String() string {
method Type (line 29) | func (f *Framework) Type() string {
method Set (line 33) | func (f *Framework) Set(value string) error {
constant Chi (line 14) | Chi Framework = "chi"
constant Gin (line 15) | Gin Framework = "gin"
constant Fiber (line 16) | Fiber Framework = "fiber"
constant GorillaMux (line 17) | GorillaMux Framework = "gorilla/mux"
constant HttpRouter (line 18) | HttpRouter Framework = "httprouter"
constant StandardLibrary (line 19) | StandardLibrary Framework = "standard-library"
constant Echo (line 20) | Echo Framework = "echo"
FILE: cmd/flags/git.go
type Git (line 8) | type Git
method String (line 18) | func (f Git) String() string {
method Type (line 22) | func (f *Git) Type() string {
method Set (line 26) | func (f *Git) Set(value string) error {
constant Commit (line 11) | Commit = "commit"
constant Stage (line 12) | Stage = "stage"
constant Skip (line 13) | Skip = "skip"
FILE: cmd/program/program.go
type Project (line 28) | type Project struct
method CheckOS (line 125) | func (p *Project) CheckOS() {
method ExitCLI (line 141) | func (p *Project) ExitCLI(tprogram *tea.Program) {
method createFrameworkMap (line 153) | func (p *Project) createFrameworkMap() {
method createDBDriverMap (line 190) | func (p *Project) createDBDriverMap() {
method createDockerMap (line 218) | func (p *Project) createDockerMap() {
method CreateMainFile (line 245) | func (p *Project) CreateMainFile() error {
method CreatePath (line 733) | func (p *Project) CreatePath(pathToCreate string, projectPath string) ...
method CreateFileWithInjection (line 748) | func (p *Project) CreateFileWithInjection(pathToCreate string, project...
method CreateViteReactProject (line 811) | func (p *Project) CreateViteReactProject(projectPath string) error {
method CreateHtmxTemplates (line 935) | func (p *Project) CreateHtmxTemplates() {
method CreateWebsocketImports (line 965) | func (p *Project) CreateWebsocketImports(appDir string) {
type AdvancedTemplates (line 44) | type AdvancedTemplates struct
type Framework (line 51) | type Framework struct
type Driver (line 56) | type Driver struct
type Docker (line 61) | type Docker struct
type Templater (line 68) | type Templater interface
type DBDriverTemplater (line 78) | type DBDriverTemplater interface
type DockerTemplater (line 84) | type DockerTemplater interface
type WorkflowTemplater (line 88) | type WorkflowTemplater interface
constant root (line 115) | root = "/"
constant cmdApiPath (line 116) | cmdApiPath = "cmd/api"
constant cmdWebPath (line 117) | cmdWebPath = "cmd/web"
constant internalServerPath (line 118) | internalServerPath = "internal/server"
constant internalDatabasePath (line 119) | internalDatabasePath = "internal/database"
constant gitHubActionPath (line 120) | gitHubActionPath = ".github/workflows"
function checkNpmInstalled (line 994) | func checkNpmInstalled() error {
FILE: cmd/root.go
function Execute (line 19) | func Execute() {
function init (line 26) | func init() {
FILE: cmd/steps/steps.go
type StepSchema (line 9) | type StepSchema struct
type Steps (line 17) | type Steps struct
type Item (line 23) | type Item struct
function InitSteps (line 28) | func InitSteps(projectType flags.Framework, databaseType flags.Database)...
FILE: cmd/template/advanced/docker.go
function Dockerfile (line 13) | func Dockerfile() []byte {
function DockerCompose (line 17) | func DockerCompose() []byte {
FILE: cmd/template/advanced/gitHubAction.go
function Releaser (line 16) | func Releaser() []byte {
function Test (line 20) | func Test() []byte {
function ReleaserConfig (line 24) | func ReleaserConfig() []byte {
FILE: cmd/template/advanced/routes.go
function EchoHtmxTemplRoutesTemplate (line 82) | func EchoHtmxTemplRoutesTemplate() []byte {
function GorillaHtmxTemplRoutesTemplate (line 86) | func GorillaHtmxTemplRoutesTemplate() []byte {
function ChiHtmxTemplRoutesTemplate (line 90) | func ChiHtmxTemplRoutesTemplate() []byte {
function GinHtmxTemplRoutesTemplate (line 94) | func GinHtmxTemplRoutesTemplate() []byte {
function HttpRouterHtmxTemplRoutesTemplate (line 98) | func HttpRouterHtmxTemplRoutesTemplate() []byte {
function StdLibHtmxTemplRoutesTemplate (line 102) | func StdLibHtmxTemplRoutesTemplate() []byte {
function StdLibHtmxTemplImportsTemplate (line 106) | func StdLibHtmxTemplImportsTemplate() []byte {
function StdLibWebsocketTemplImportsTemplate (line 110) | func StdLibWebsocketTemplImportsTemplate() []byte {
function HelloTemplTemplate (line 114) | func HelloTemplTemplate() []byte {
function BaseTemplTemplate (line 118) | func BaseTemplTemplate() []byte {
function ReactTailwindAppfile (line 122) | func ReactTailwindAppfile() []byte {
function ReactAppfile (line 126) | func ReactAppfile() []byte {
function InputCssTemplateReact (line 130) | func InputCssTemplateReact() []byte {
function ViteTailwindConfigFile (line 134) | func ViteTailwindConfigFile() []byte {
function InputCssTemplate (line 138) | func InputCssTemplate() []byte {
function OutputCssTemplate (line 142) | func OutputCssTemplate() []byte {
function HtmxTailwindConfigJsTemplate (line 146) | func HtmxTailwindConfigJsTemplate() []byte {
function HtmxJSTemplate (line 150) | func HtmxJSTemplate() []byte {
function EfsTemplate (line 154) | func EfsTemplate() []byte {
function HelloGoTemplate (line 158) | func HelloGoTemplate() []byte {
function HelloFiberGoTemplate (line 162) | func HelloFiberGoTemplate() []byte {
function FiberHtmxTemplRoutesTemplate (line 166) | func FiberHtmxTemplRoutesTemplate() []byte {
function FiberHtmxTemplImportsTemplate (line 170) | func FiberHtmxTemplImportsTemplate() []byte {
function FiberWebsocketTemplImportsTemplate (line 174) | func FiberWebsocketTemplImportsTemplate() []byte {
function GinHtmxTemplImportsTemplate (line 178) | func GinHtmxTemplImportsTemplate() []byte {
FILE: cmd/template/dbdriver/mongo.go
type MongoTemplate (line 7) | type MongoTemplate struct
method Service (line 18) | func (m MongoTemplate) Service() []byte {
method Env (line 22) | func (m MongoTemplate) Env() []byte {
method Tests (line 26) | func (m MongoTemplate) Tests() []byte {
FILE: cmd/template/dbdriver/mysql.go
type MysqlTemplate (line 7) | type MysqlTemplate struct
method Service (line 18) | func (m MysqlTemplate) Service() []byte {
method Env (line 22) | func (m MysqlTemplate) Env() []byte {
method Tests (line 26) | func (m MysqlTemplate) Tests() []byte {
FILE: cmd/template/dbdriver/postgres.go
type PostgresTemplate (line 7) | type PostgresTemplate struct
method Service (line 18) | func (m PostgresTemplate) Service() []byte {
method Env (line 22) | func (m PostgresTemplate) Env() []byte {
method Tests (line 26) | func (m PostgresTemplate) Tests() []byte {
FILE: cmd/template/dbdriver/redis.go
type RedisTemplate (line 7) | type RedisTemplate struct
method Service (line 18) | func (r RedisTemplate) Service() []byte {
method Env (line 22) | func (r RedisTemplate) Env() []byte {
method Tests (line 26) | func (r RedisTemplate) Tests() []byte {
FILE: cmd/template/dbdriver/scylla.go
type ScyllaTemplate (line 7) | type ScyllaTemplate struct
method Service (line 18) | func (r ScyllaTemplate) Service() []byte {
method Env (line 22) | func (r ScyllaTemplate) Env() []byte {
method Tests (line 26) | func (r ScyllaTemplate) Tests() []byte {
FILE: cmd/template/dbdriver/sqlite.go
type SqliteTemplate (line 7) | type SqliteTemplate struct
method Service (line 15) | func (m SqliteTemplate) Service() []byte {
method Env (line 19) | func (m SqliteTemplate) Env() []byte {
method Tests (line 23) | func (m SqliteTemplate) Tests() []byte {
FILE: cmd/template/docker/mongo.go
type MongoDockerTemplate (line 7) | type MongoDockerTemplate struct
method Docker (line 12) | func (m MongoDockerTemplate) Docker() []byte {
FILE: cmd/template/docker/mysql.go
type MysqlDockerTemplate (line 7) | type MysqlDockerTemplate struct
method Docker (line 12) | func (m MysqlDockerTemplate) Docker() []byte {
FILE: cmd/template/docker/postgres.go
type PostgresDockerTemplate (line 7) | type PostgresDockerTemplate struct
method Docker (line 12) | func (m PostgresDockerTemplate) Docker() []byte {
FILE: cmd/template/docker/redis.go
type RedisDockerTemplate (line 7) | type RedisDockerTemplate struct
method Docker (line 12) | func (r RedisDockerTemplate) Docker() []byte {
FILE: cmd/template/docker/scylla.go
type ScyllaDockerTemplate (line 7) | type ScyllaDockerTemplate struct
method Docker (line 12) | func (r ScyllaDockerTemplate) Docker() []byte {
FILE: cmd/template/framework/chiRoutes.go
type ChiTemplates (line 17) | type ChiTemplates struct
method Main (line 19) | func (c ChiTemplates) Main() []byte {
method Server (line 23) | func (c ChiTemplates) Server() []byte {
method Routes (line 27) | func (c ChiTemplates) Routes() []byte {
method TestHandler (line 31) | func (c ChiTemplates) TestHandler() []byte {
method HtmxTemplImports (line 35) | func (c ChiTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 39) | func (c ChiTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 43) | func (c ChiTemplates) WebsocketImports() []byte {
FILE: cmd/template/framework/echoRoutes.go
type EchoTemplates (line 17) | type EchoTemplates struct
method Main (line 19) | func (e EchoTemplates) Main() []byte {
method Server (line 22) | func (e EchoTemplates) Server() []byte {
method Routes (line 26) | func (e EchoTemplates) Routes() []byte {
method TestHandler (line 30) | func (e EchoTemplates) TestHandler() []byte {
method HtmxTemplImports (line 34) | func (e EchoTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 38) | func (e EchoTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 42) | func (e EchoTemplates) WebsocketImports() []byte {
FILE: cmd/template/framework/fiberServer.go
type FiberTemplates (line 23) | type FiberTemplates struct
method Main (line 25) | func (f FiberTemplates) Main() []byte {
method Server (line 28) | func (f FiberTemplates) Server() []byte {
method Routes (line 32) | func (f FiberTemplates) Routes() []byte {
method TestHandler (line 36) | func (f FiberTemplates) TestHandler() []byte {
method HtmxTemplImports (line 40) | func (f FiberTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 44) | func (f FiberTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 48) | func (f FiberTemplates) WebsocketImports() []byte {
FILE: cmd/template/framework/ginRoutes.go
type GinTemplates (line 17) | type GinTemplates struct
method Main (line 19) | func (g GinTemplates) Main() []byte {
method Server (line 23) | func (g GinTemplates) Server() []byte {
method Routes (line 27) | func (g GinTemplates) Routes() []byte {
method TestHandler (line 31) | func (g GinTemplates) TestHandler() []byte {
method HtmxTemplImports (line 35) | func (g GinTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 39) | func (g GinTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 43) | func (g GinTemplates) WebsocketImports() []byte {
FILE: cmd/template/framework/gorillaRoutes.go
type GorillaTemplates (line 17) | type GorillaTemplates struct
method Main (line 19) | func (g GorillaTemplates) Main() []byte {
method Server (line 23) | func (g GorillaTemplates) Server() []byte {
method Routes (line 27) | func (g GorillaTemplates) Routes() []byte {
method TestHandler (line 31) | func (g GorillaTemplates) TestHandler() []byte {
method HtmxTemplImports (line 35) | func (g GorillaTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 39) | func (g GorillaTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 43) | func (g GorillaTemplates) WebsocketImports() []byte {
FILE: cmd/template/framework/httpRoutes.go
type StandardLibTemplate (line 20) | type StandardLibTemplate struct
method Main (line 22) | func (s StandardLibTemplate) Main() []byte {
method Server (line 26) | func (s StandardLibTemplate) Server() []byte {
method Routes (line 30) | func (s StandardLibTemplate) Routes() []byte {
method TestHandler (line 34) | func (s StandardLibTemplate) TestHandler() []byte {
method HtmxTemplImports (line 38) | func (s StandardLibTemplate) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 42) | func (s StandardLibTemplate) HtmxTemplRoutes() []byte {
method WebsocketImports (line 46) | func (s StandardLibTemplate) WebsocketImports() []byte {
FILE: cmd/template/framework/main.go
function MakeTemplate (line 26) | func MakeTemplate() []byte {
function GitIgnoreTemplate (line 30) | func GitIgnoreTemplate() []byte {
function AirTomlTemplate (line 34) | func AirTomlTemplate() []byte {
function ReadmeTemplate (line 40) | func ReadmeTemplate() []byte {
FILE: cmd/template/framework/routerRoutes.go
type RouterTemplates (line 17) | type RouterTemplates struct
method Main (line 19) | func (r RouterTemplates) Main() []byte {
method Server (line 22) | func (r RouterTemplates) Server() []byte {
method Routes (line 26) | func (r RouterTemplates) Routes() []byte {
method TestHandler (line 30) | func (r RouterTemplates) TestHandler() []byte {
method HtmxTemplImports (line 34) | func (r RouterTemplates) HtmxTemplImports() []byte {
method HtmxTemplRoutes (line 38) | func (r RouterTemplates) HtmxTemplRoutes() []byte {
method WebsocketImports (line 42) | func (r RouterTemplates) WebsocketImports() []byte {
FILE: cmd/template/globalEnv.go
function GlobalEnvTemplate (line 10) | func GlobalEnvTemplate() []byte {
FILE: cmd/ui/multiInput/multiInput.go
type Selection (line 25) | type Selection struct
method Update (line 30) | func (s *Selection) Update(value string) {
type model (line 37) | type model struct
method Init (line 46) | func (m model) Init() tea.Cmd {
method Update (line 65) | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 104) | func (m model) View() string {
function InitialModelMulti (line 52) | func InitialModelMulti(choices []steps.Item, selection *Selection, heade...
FILE: cmd/ui/multiSelect/multiSelect.go
type Selection (line 25) | type Selection struct
method Update (line 30) | func (s *Selection) Update(optionName string, value bool) {
type model (line 37) | type model struct
method Init (line 46) | func (m model) Init() tea.Cmd {
method Update (line 65) | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 99) | func (m model) View() string {
function InitialModelMultiSelect (line 52) | func InitialModelMultiSelect(options []steps.Item, selection *Selection,...
FILE: cmd/ui/spinner/spinner.go
type errMsg (line 11) | type errMsg
type model (line 13) | type model struct
method Init (line 26) | func (m model) Init() tea.Cmd {
method Update (line 30) | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 52) | func (m model) View() string {
function InitialModelNew (line 19) | func InitialModelNew() model {
FILE: cmd/ui/textinput/textinput.go
type errMsg (line 22) | type errMsg
type Output (line 26) | type Output struct
method update (line 31) | func (o *Output) update(val string) {
type model (line 38) | type model struct
method Init (line 93) | func (m model) Init() tea.Cmd {
method Update (line 99) | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 127) | func (m model) View() string {
method Err (line 134) | func (m model) Err() string {
function sanitizeInput (line 47) | func sanitizeInput(input string) error {
function InitialTextInputModel (line 57) | func InitialTextInputModel(output *Output, header string, program *progr...
function CreateErrorInputModel (line 75) | func CreateErrorInputModel(err error) model {
FILE: cmd/ui/textinput/textinput_test.go
function TestInputSanitization (line 5) | func TestInputSanitization(t *testing.T) {
FILE: cmd/utils/utils.go
constant ProgramName (line 17) | ProgramName = "go-blueprint"
function NonInteractiveCommand (line 21) | func NonInteractiveCommand(use string, flagSet *pflag.FlagSet) string {
function RegisterStaticCompletions (line 52) | func RegisterStaticCompletions(cmd *cobra.Command, flag string, options ...
function ExecuteCmd (line 63) | func ExecuteCmd(name string, args []string, dir string) error {
function InitGoMod (line 78) | func InitGoMod(projectName string, appDir string) error {
function GoGetPackage (line 90) | func GoGetPackage(appDir string, packages []string) error {
function GoFmt (line 104) | func GoFmt(appDir string) error {
function GoModReplace (line 116) | func GoModReplace(appDir string, replace string) error {
function GoTidy (line 127) | func GoTidy(appDir string) error {
function CheckGitConfig (line 135) | func CheckGitConfig(key string) (bool, error) {
function ValidateModuleName (line 154) | func ValidateModuleName(moduleName string) bool {
function GetRootDir (line 161) | func GetRootDir(moduleName string) string {
FILE: cmd/utils/utils_test.go
function TestValidateModuleName (line 5) | func TestValidateModuleName(t *testing.T) {
function TestGeRootDir (line 41) | func TestGeRootDir(t *testing.T) {
FILE: cmd/version.go
function getGoBlueprintVersion (line 23) | func getGoBlueprintVersion() string {
FILE: main.go
function main (line 5) | func main() {
Condensed preview — 163 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (395K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 67,
"preview": "# These are supported funding model platforms\n\ngithub: [melkeydev]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.yml",
"chars": 1457,
"preview": "name: Bug Report\ndescription: Found a bug? Please let us know!\ntitle: \"[Bug]\"\nlabels: [\"Bug\"]\nbody:\n - type: markdown\n "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 141,
"preview": "blank_issues_enabled: true\ncontact_links:\n - name: Melkeydev Discord\n url: https://discord.gg/HHZMSCu\n about: Cha"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.yml",
"chars": 758,
"preview": "name: Feature Request\ndescription: Suggest a new idea for go-blueprint.\ntitle: \"[Feature Request] \"\nlabels: [\"enhancemen"
},
{
"path": ".github/pull_request_template.md",
"chars": 390,
"preview": "By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.\n\n## Problem/"
},
{
"path": ".github/semantic.yml",
"chars": 15,
"preview": "titleOnly: true"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1163,
"preview": "name: continuous integration\n\non:\n push:\n paths:\n - '**.go'\n - go.sum\n - go.mod\n branches-ignore:\n"
},
{
"path": ".github/workflows/docs.yml",
"chars": 1169,
"preview": "name: Deploy Docs\n\non:\n push:\n branches:\n - main\n\njobs:\n build-deploy:\n name: Build and deploy docs\n\n ru"
},
{
"path": ".github/workflows/generate-linter-advanced.yml",
"chars": 1704,
"preview": "name: Linting Generated Blueprints Advanced\n\non:\n pull_request: {}\n workflow_dispatch: {}\n\njobs:\n framework_matrix:\n "
},
{
"path": ".github/workflows/generate-linter-core.yml",
"chars": 1359,
"preview": "name: Linting Generated Blueprints Core\n\non:\n pull_request: {}\n workflow_dispatch: {}\n\njobs:\n framework_matrix:\n s"
},
{
"path": ".github/workflows/npm-publish.yml",
"chars": 1612,
"preview": "name: npm-publish\n\non:\n workflow_call:\n inputs:\n tag:\n description: \"Release tag to publish (e.g., v1.0."
},
{
"path": ".github/workflows/release.yml",
"chars": 788,
"preview": "name: goreleaser\n\non:\n push:\n tags:\n - \"v*.*.*\"\n\npermissions:\n contents: write\n\njobs:\n goreleaser:\n runs-o"
},
{
"path": ".github/workflows/testcontainers.yml",
"chars": 965,
"preview": "name: Integrations Test for the Generated Blueprints\n\non:\n pull_request: {}\n workflow_dispatch: {}\n\njobs:\n itests_mat"
},
{
"path": ".github/workflows/update-htmx-version.yml",
"chars": 3020,
"preview": "name: Check for new htmx release\n\non:\n schedule:\n - cron: '0 0 * * Sun'\n\njobs:\n check_release:\n runs-on: ubuntu-"
},
{
"path": ".gitignore",
"chars": 18,
"preview": "go-blueprint\nsite\n"
},
{
"path": ".goreleaser.yml",
"chars": 863,
"preview": "before:\n hooks:\n - go mod tidy\n - ./scripts/completions.sh\nbuilds:\n - binary: go-blueprint\n main: ./\n goos"
},
{
"path": ".pre-commit-config.yaml",
"chars": 406,
"preview": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n - repo"
},
{
"path": "CONTRIBUTING.md",
"chars": 1356,
"preview": "# Contributing\n\nThanks for helping make go-blueprint better! \n- [Contributing](#contributing)\n - [Design Principles](#d"
},
{
"path": "LICENSE",
"chars": 1097,
"preview": "The MIT License (MIT)\r\n\r\nCopyright (c) 2023 Melkeydev\r\n\r\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 6710,
"preview": "\n\n<div style=\"text-align: center;\">\n <h1>\n Introducing the Ultimate Golang Blueprint Libra"
},
{
"path": "cmd/create.go",
"chars": 12585,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/cha"
},
{
"path": "cmd/flags/advancedFeatures.go",
"chars": 1014,
"preview": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype AdvancedFeatures []string\n\nconst (\n\tHtmx string = \"htmx\"\n"
},
{
"path": "cmd/flags/database.go",
"chars": 1072,
"preview": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Database string\n\n// These are all the current databases supported. If "
},
{
"path": "cmd/flags/frameworks.go",
"chars": 1173,
"preview": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Framework string\n\n// These are all the current frameworks supported. I"
},
{
"path": "cmd/flags/git.go",
"chars": 556,
"preview": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Git string\n\nconst (\n\tCommit = \"commit\"\n\tStage = \"stage\"\n\tSkip = \"sk"
},
{
"path": "cmd/program/program.go",
"chars": 29622,
"preview": "// Package program provides the\n// main functionality of Blueprint\npackage program\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os"
},
{
"path": "cmd/root.go",
"chars": 648,
"preview": "/*\nCopyright © 2023 Melkey melkeydev@gmail.com\n*/\npackage cmd\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar rootCmd "
},
{
"path": "cmd/steps/steps.go",
"chars": 4395,
"preview": "// Package steps provides utility for creating\n// each step of the CLI\npackage steps\n\nimport \"github.com/melkeydev/go-bl"
},
{
"path": "cmd/template/advanced/docker.go",
"chars": 313,
"preview": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/docker/dockerfile.tmpl\nvar dockerfileTemplate []byte\n\n//go:emb"
},
{
"path": "cmd/template/advanced/files/docker/docker_compose.yml.tmpl",
"chars": 696,
"preview": "services:\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target: prod\n restart: unless-stopped"
},
{
"path": "cmd/template/advanced/files/docker/dockerfile.tmpl",
"chars": 1361,
"preview": "FROM golang:1.24.4-alpine AS build\n{{- if or (.AdvancedOptions.tailwind) (eq .DBDriver \"sqlite\") }}\nRUN apk add --no-cac"
},
{
"path": "cmd/template/advanced/files/htmx/base.templ.tmpl",
"chars": 578,
"preview": "package web\n\ntempl Base() {\n\t<!DOCTYPE html>\n\t<html lang=\"en\" {{if .AdvancedOptions.tailwind}}class=\"h-screen\"{{end}}>\n\t"
},
{
"path": "cmd/template/advanced/files/htmx/efs.go.tmpl",
"chars": 68,
"preview": "package web\n\nimport \"embed\"\n\n//go:embed \"assets\"\nvar Files embed.FS\n"
},
{
"path": "cmd/template/advanced/files/htmx/hello.go.tmpl",
"chars": 437,
"preview": "package web\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc HelloWebHandler(w http.ResponseWriter, r *http.Request) {\n\terr := r.Par"
},
{
"path": "cmd/template/advanced/files/htmx/hello.templ.tmpl",
"chars": 631,
"preview": "package web\n\ntempl HelloForm() {\n\t@Base() {\n\t\t<form hx-post=\"/hello\" method=\"POST\" hx-target=\"#hello-container\">\n\t\t\t<inp"
},
{
"path": "cmd/template/advanced/files/htmx/hello_fiber.go.tmpl",
"chars": 1022,
"preview": "package web\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/gofiber/fiber/v2\"\n)\n\nfunc HelloWebHandler(c *fiber.Ctx) error"
},
{
"path": "cmd/template/advanced/files/htmx/htmx.min.js.tmpl",
"chars": 83055,
"preview": "var htmx = (function () {\n \"use strict\";\n const Q = {\n onLoad: null,\n process: null,\n on: null,\n off: null"
},
{
"path": "cmd/template/advanced/files/htmx/imports/fiber.tmpl",
"chars": 161,
"preview": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\"\n\"github.com/gofiber/fiber/v2/middleware/adaptor\"\n\"github.com/gofiber/f"
},
{
"path": "cmd/template/advanced/files/htmx/imports/gin.tmpl",
"chars": 58,
"preview": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\"\n\"io/fs\"\n"
},
{
"path": "cmd/template/advanced/files/htmx/imports/standard_library.tmpl",
"chars": 49,
"preview": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\""
},
{
"path": "cmd/template/advanced/files/htmx/routes/chi.tmpl",
"chars": 181,
"preview": "fileServer := http.FileServer(http.FS(web.Files))\n\tr.Handle(\"/assets/*\", fileServer)\n\tr.Get(\"/web\", templ.Handler(web.He"
},
{
"path": "cmd/template/advanced/files/htmx/routes/echo.tmpl",
"chars": 238,
"preview": "fileServer := http.FileServer(http.FS(web.Files))\ne.GET(\"/assets/*\", echo.WrapHandler(fileServer))\n\ne.GET(\"/web\", echo.W"
},
{
"path": "cmd/template/advanced/files/htmx/routes/fiber.tmpl",
"chars": 295,
"preview": "s.App.Use(\"/assets\", filesystem.New(filesystem.Config{\n\t\tRoot: http.FS(web.Files),\n\t\tPathPrefix: \"assets\",\n\t\tBrows"
},
{
"path": "cmd/template/advanced/files/htmx/routes/gin.tmpl",
"chars": 282,
"preview": "staticFiles, _ := fs.Sub(web.Files, \"assets\")\nr.StaticFS(\"/assets\", http.FS(staticFiles))\n\nr.GET(\"/web\", func(c *gin.Con"
},
{
"path": "cmd/template/advanced/files/htmx/routes/gorilla.tmpl",
"chars": 261,
"preview": "fileServer := http.FileServer(http.FS(web.Files))\nr.PathPrefix(\"/assets/\").Handler(fileServer)\n\nr.HandleFunc(\"/web\", fun"
},
{
"path": "cmd/template/advanced/files/htmx/routes/http_router.tmpl",
"chars": 248,
"preview": " fileServer := http.FileServer(http.FS(web.Files))\n r.Handler(http.MethodGet, \"/assets/*filepath\", fileServer)\n r.Han"
},
{
"path": "cmd/template/advanced/files/htmx/routes/standard_library.tmpl",
"chars": 187,
"preview": " fileServer := http.FileServer(http.FS(web.Files))\n\tmux.Handle(\"/assets/\", fileServer)\n\tmux.Handle(\"/web\", templ.Handle"
},
{
"path": "cmd/template/advanced/files/htmx/tailwind/tailwind.config.js.tmpl",
"chars": 117,
"preview": "module.exports = {\n\tcontent: [\"./**/*.html\", \"./**/*.templ\", \"./**/*.go\",],\n\ttheme: { extend: {}, },\n\tplugins: [],\n}\n"
},
{
"path": "cmd/template/advanced/files/react/app.tsx.tmpl",
"chars": 1406,
"preview": "import { useState } from 'react'\nimport reactLogo from './assets/react.svg'\nimport viteLogo from '/vite.svg'\nimport './A"
},
{
"path": "cmd/template/advanced/files/react/tailwind/app.tsx.tmpl",
"chars": 1969,
"preview": "import { useState } from 'react'\n\nfunction App() {\n const [count, setCount] = useState(0)\n const [message, setMessage]"
},
{
"path": "cmd/template/advanced/files/react/tailwind/index.css.tmpl",
"chars": 22,
"preview": "@import \"tailwindcss\";"
},
{
"path": "cmd/template/advanced/files/react/tailwind/vite.config.ts.tmpl",
"chars": 218,
"preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport tailwindcss from '@tailwindcss/vite'"
},
{
"path": "cmd/template/advanced/files/tailwind/input.css.tmpl",
"chars": 22,
"preview": "@import \"tailwindcss\"\n"
},
{
"path": "cmd/template/advanced/files/tailwind/output.css.tmpl",
"chars": 0,
"preview": ""
},
{
"path": "cmd/template/advanced/files/websocket/imports/fiber.tmpl",
"chars": 39,
"preview": "\"github.com/gofiber/contrib/websocket\"\n"
},
{
"path": "cmd/template/advanced/files/websocket/imports/standard_library.tmpl",
"chars": 29,
"preview": "\"github.com/coder/websocket\"\n"
},
{
"path": "cmd/template/advanced/files/workflow/github/github_action_goreleaser.yml.tmpl",
"chars": 871,
"preview": "name: goreleaser\n\non:\n push:\n tags:\n - \"v*.*.*\"\n\npermissions:\n contents: write\n\njobs:\n goreleaser:\n runs-o"
},
{
"path": "cmd/template/advanced/files/workflow/github/github_action_gotest.yml.tmpl",
"chars": 617,
"preview": "name: Go-test\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n steps:\n - uses: actions/chec"
},
{
"path": "cmd/template/advanced/files/workflow/github/github_action_releaser_config.yml.tmpl",
"chars": 956,
"preview": "version: 2\nbefore:\n hooks:\n - go mod tidy\n\nenv:\n - PACKAGE_PATH=github.com/<user>/<repo>/cmd\n\nbuilds:\n- binary: \"{{\"{"
},
{
"path": "cmd/template/advanced/gitHubAction.go",
"chars": 544,
"preview": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/workflow/github/github_action_goreleaser.yml.tmpl\nvar gitHubAc"
},
{
"path": "cmd/template/advanced/routes.go",
"chars": 3724,
"preview": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/htmx/hello.templ.tmpl\nvar helloTemplTemplate []byte\n\n//go:embe"
},
{
"path": "cmd/template/dbdriver/files/env/mongo.tmpl",
"chars": 204,
"preview": "{{ if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=mongo_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRINT"
},
{
"path": "cmd/template/dbdriver/files/env/mysql.tmpl",
"chars": 271,
"preview": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=mysql_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRIN"
},
{
"path": "cmd/template/dbdriver/files/env/postgres.tmpl",
"chars": 257,
"preview": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=psql_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRINT"
},
{
"path": "cmd/template/dbdriver/files/env/redis.tmpl",
"chars": 188,
"preview": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_ADDRESS=redis_bp\n{{- else }}\nBLUEPRINT_DB_ADDRESS=localhost\n{{- end }}\nBL"
},
{
"path": "cmd/template/dbdriver/files/env/scylla.tmpl",
"chars": 392,
"preview": "{{- if .AdvancedOptions.docker }}\n# BLUEPRINT_DB_HOSTS=scylla_bp:9042 # ScyllaDB default port\nBLUEPRINT_DB_HOSTS=scylla_"
},
{
"path": "cmd/template/dbdriver/files/env/sqlite.tmpl",
"chars": 114,
"preview": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_URL=./db/test.db\n{{- else }}\nBLUEPRINT_DB_URL=./test.db\n{{- end }}\n"
},
{
"path": "cmd/template/dbdriver/files/service/mongo.tmpl",
"chars": 936,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org"
},
{
"path": "cmd/template/dbdriver/files/service/mysql.tmpl",
"chars": 3678,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t_ \"github.com/go-sql-driv"
},
{
"path": "cmd/template/dbdriver/files/service/postgres.tmpl",
"chars": 3534,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t_ \"github.com/jackc/pgx/v"
},
{
"path": "cmd/template/dbdriver/files/service/redis.tmpl",
"chars": 7354,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t_ \"github.com/joho/god"
},
{
"path": "cmd/template/dbdriver/files/service/scylla.tmpl",
"chars": 4763,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gocql/gocql\"\n\t_ \"g"
},
{
"path": "cmd/template/dbdriver/files/service/sqlite.tmpl",
"chars": 3260,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t_ \"github.com/mattn/go-sq"
},
{
"path": "cmd/template/dbdriver/files/tests/mongo.tmpl",
"chars": 1292,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/tes"
},
{
"path": "cmd/template/dbdriver/files/tests/mysql.tmpl",
"chars": 1997,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github"
},
{
"path": "cmd/template/dbdriver/files/tests/postgres.tmpl",
"chars": 2069,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github"
},
{
"path": "cmd/template/dbdriver/files/tests/redis.tmpl",
"chars": 1514,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/tes"
},
{
"path": "cmd/template/dbdriver/files/tests/scylla.tmpl",
"chars": 2972,
"preview": "package database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/docker/go-connections/nat\"\n\t\"github.com/testcontainers/testcon"
},
{
"path": "cmd/template/dbdriver/mongo.go",
"chars": 492,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype MongoTemplate struct{}\n\n//go:embed files/service/mongo.tmpl\nvar mongoServi"
},
{
"path": "cmd/template/dbdriver/mysql.go",
"chars": 492,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype MysqlTemplate struct{}\n\n//go:embed files/service/mysql.tmpl\nvar mysqlServi"
},
{
"path": "cmd/template/dbdriver/postgres.go",
"chars": 531,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype PostgresTemplate struct{}\n\n//go:embed files/service/postgres.tmpl\nvar post"
},
{
"path": "cmd/template/dbdriver/redis.go",
"chars": 492,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype RedisTemplate struct{}\n\n//go:embed files/service/redis.tmpl\nvar redisServi"
},
{
"path": "cmd/template/dbdriver/scylla.go",
"chars": 505,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype ScyllaTemplate struct{}\n\n//go:embed files/service/scylla.tmpl\nvar scyllaSe"
},
{
"path": "cmd/template/dbdriver/sqlite.go",
"chars": 409,
"preview": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype SqliteTemplate struct{}\n\n//go:embed files/service/sqlite.tmpl\nvar sqliteSe"
},
{
"path": "cmd/template/docker/files/docker-compose/mongo.tmpl",
"chars": 1497,
"preview": "services:\n{{- if .AdvancedOptions.docker }}\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target"
},
{
"path": "cmd/template/docker/files/docker-compose/mysql.tmpl",
"chars": 1695,
"preview": "services:\n{{- if .AdvancedOptions.docker }}\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target"
},
{
"path": "cmd/template/docker/files/docker-compose/postgres.tmpl",
"chars": 1658,
"preview": "services:\n{{- if .AdvancedOptions.docker }}\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target"
},
{
"path": "cmd/template/docker/files/docker-compose/redis.tmpl",
"chars": 1201,
"preview": "services:\n{{- if .AdvancedOptions.docker }}\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target"
},
{
"path": "cmd/template/docker/files/docker-compose/scylla.tmpl",
"chars": 1684,
"preview": "services:\n{{- if .AdvancedOptions.docker }}\n app:\n build:\n context: .\n dockerfile: Dockerfile\n target"
},
{
"path": "cmd/template/docker/mongo.go",
"chars": 225,
"preview": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype MongoDockerTemplate struct{}\n\n//go:embed files/docker-compose/mongo.tmpl\nvar"
},
{
"path": "cmd/template/docker/mysql.go",
"chars": 225,
"preview": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype MysqlDockerTemplate struct{}\n\n//go:embed files/docker-compose/mysql.tmpl\nvar"
},
{
"path": "cmd/template/docker/postgres.go",
"chars": 240,
"preview": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype PostgresDockerTemplate struct{}\n\n//go:embed files/docker-compose/postgres.tm"
},
{
"path": "cmd/template/docker/redis.go",
"chars": 226,
"preview": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype RedisDockerTemplate struct{}\n\n//go:embed files/docker-compose/redis.tmpl\nvar"
},
{
"path": "cmd/template/docker/scylla.go",
"chars": 231,
"preview": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype ScyllaDockerTemplate struct{}\n\n//go:embed files/docker-compose/scylla.tmpl\nv"
},
{
"path": "cmd/template/framework/chiRoutes.go",
"chars": 966,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/echoRoutes.go",
"chars": 980,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/fiberServer.go",
"chars": 1137,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/files/README.md.tmpl",
"chars": 858,
"preview": "# Project {{.ProjectName}}\n\nOne Paragraph of project description goes here\n\n## Getting Started\n\nThese instructions will "
},
{
"path": "cmd/template/framework/files/air.toml.tmpl",
"chars": 986,
"preview": "root = \".\"\ntestdata_dir = \"testdata\"\ntmp_dir = \"tmp\"\n\n[build]\n args_bin = []\n bin = {{if .OSCheck.UnixBased }}\"./main\""
},
{
"path": "cmd/template/framework/files/gitignore.tmpl",
"chars": 525,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with \"go test -c\"\n*.test\n\n# Ou"
},
{
"path": "cmd/template/framework/files/globalenv.tmpl",
"chars": 24,
"preview": "PORT=8080\nAPP_ENV=local\n"
},
{
"path": "cmd/template/framework/files/main/fiber_main.go.tmpl",
"chars": 1568,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\t\"{{.ProjectName}}/inte"
},
{
"path": "cmd/template/framework/files/main/main.go.tmpl",
"chars": 1425,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"{{.ProjectName}}/internal"
},
{
"path": "cmd/template/framework/files/makefile.tmpl",
"chars": 5166,
"preview": "# Simple Makefile for a Go project\n\n# Build the application\nall: build test\n\n{{- if and (or .AdvancedOptions.htmx .Advan"
},
{
"path": "cmd/template/framework/files/routes/chi.go.tmpl",
"chars": 2074,
"preview": "package server\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"fmt\"\n\t\"time\"\n {{end}"
},
{
"path": "cmd/template/framework/files/routes/echo.go.tmpl",
"chars": 1970,
"preview": "package server\n\nimport (\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"log\"\n\t\"fmt\"\n\t\"time\"\n {{end}}\n\n\t\"github.com/l"
},
{
"path": "cmd/template/framework/files/routes/fiber.go.tmpl",
"chars": 1739,
"preview": "package server\n\nimport (\n {{if .AdvancedOptions.websocket}}\n\t\"context\"\n\t\"log\"\n\t\"fmt\"\n\t\"time\"\n {{end}}\n\t\"github.com/gof"
},
{
"path": "cmd/template/framework/files/routes/gin.go.tmpl",
"chars": 1828,
"preview": "package server\n\nimport (\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"log\"\n\t\"fmt\"\n\t\"time\"\n {{end}}\n\n\t\"github.com/g"
},
{
"path": "cmd/template/framework/files/routes/gorilla.go.tmpl",
"chars": 2546,
"preview": "package server\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"fmt\"\n\t\"time\"\n {{end}"
},
{
"path": "cmd/template/framework/files/routes/http_router.go.tmpl",
"chars": 2684,
"preview": "package server\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"fmt\"\n\t\"time\"\n {{end}"
},
{
"path": "cmd/template/framework/files/routes/standard_library.go.tmpl",
"chars": 2872,
"preview": "package server\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n {{if .AdvancedOptions.websocket}}\n\t\"fmt\"\n\t\"time\"\n {{end}"
},
{
"path": "cmd/template/framework/files/server/fiber.go.tmpl",
"chars": 501,
"preview": "package server\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n {{if ne .DBDriver \"none\"}}\n\t\"{{.ProjectName}}/internal/databas"
},
{
"path": "cmd/template/framework/files/server/standard_library.go.tmpl",
"chars": 730,
"preview": "package server\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n {{if ne ."
},
{
"path": "cmd/template/framework/files/tests/default-test.go.tmpl",
"chars": 806,
"preview": "package server\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\nfunc TestHandler(t *testing.T) {\n s := &S"
},
{
"path": "cmd/template/framework/files/tests/echo-test.go.tmpl",
"chars": 1011,
"preview": "package server\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n\t\"github.com/labstack/ec"
},
{
"path": "cmd/template/framework/files/tests/fiber-test.go.tmpl",
"chars": 1027,
"preview": "package server\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"github.com/gofiber/fiber/v2\"\n)\nfunc TestHandler(t *testing.T) {\n\t"
},
{
"path": "cmd/template/framework/files/tests/gin-test.go.tmpl",
"chars": 838,
"preview": "package server\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"github.com/gin-gonic/gin\"\n)\nfunc TestHelloWorldHan"
},
{
"path": "cmd/template/framework/ginRoutes.go",
"chars": 962,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/gorillaRoutes.go",
"chars": 1027,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/httpRoutes.go",
"chars": 1136,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/framework/main.go",
"chars": 849,
"preview": "// Package template provides utility functions that\n// help with the templating of created files.\npackage framework\n\nimp"
},
{
"path": "cmd/template/framework/routerRoutes.go",
"chars": 1049,
"preview": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/r"
},
{
"path": "cmd/template/globalEnv.go",
"chars": 175,
"preview": "package template\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed framework/files/globalenv.tmpl\nvar globalEnvTemplate []byte\n\nfunc Gl"
},
{
"path": "cmd/ui/multiInput/multiInput.go",
"chars": 3373,
"preview": "// Package multiInput provides functions that\n// help define and draw a multi-input step\npackage multiInput\n\nimport (\n\t\""
},
{
"path": "cmd/ui/multiSelect/multiSelect.go",
"chars": 3318,
"preview": "// Package multiSelect provides functions that\n// help define and draw a multi-select step\npackage multiSelect\n\nimport ("
},
{
"path": "cmd/ui/spinner/spinner.go",
"chars": 1043,
"preview": "package spinner\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/charmbracelet/bubbles/spinner\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n"
},
{
"path": "cmd/ui/textinput/textinput.go",
"chars": 3097,
"preview": "// Package textinput provides functions that\n// help define and draw a text-input step\npackage textinput\n\nimport (\n\t\"err"
},
{
"path": "cmd/ui/textinput/textinput_test.go",
"chars": 626,
"preview": "package textinput\n\nimport \"testing\"\n\nfunc TestInputSanitization(t *testing.T) {\n\tpassTestCases := []string{\n\t\t\"chi\",\n\t\t\""
},
{
"path": "cmd/utils/utils.go",
"chars": 4432,
"preview": "// Package utils provides extra utility\n// for the program\npackage utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os/exec\"\n\t\"r"
},
{
"path": "cmd/utils/utils_test.go",
"chars": 1214,
"preview": "package utils\n\nimport \"testing\"\n\nfunc TestValidateModuleName(t *testing.T) {\n\tpassTestCases := []string{\n\t\t\"github.com/u"
},
{
"path": "cmd/version.go",
"chars": 2316,
"preview": "/*\nGo blueprint version\n*/\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// GoBlue"
},
{
"path": "contributors.yml",
"chars": 860,
"preview": "- Melkeydev\n- Ujstor\n- tylermeekel\n- MitchellBerend\n- H0llyW00dzZ\n- SudoSurya\n- mimatache\n- SputNikPlop\n- limesten\n- its"
},
{
"path": "docs/Makefile",
"chars": 351,
"preview": ".PHONY: docs\n\ndefault: install\n\nall: install build\n\n\nh help:\n\t@grep '^[a-z]' Makefile\n\n\ninstall:\n\tpip install pip --upgr"
},
{
"path": "docs/custom_theme/main.html",
"chars": 194,
"preview": "{% extends \"base.html\" %}\n\n{% block libs %}\n {{ super() }}\n <script defer data-domain=\"docs.go-blueprint.dev\" src="
},
{
"path": "docs/docs/advanced-flag/advanced-flag.md",
"chars": 1553,
"preview": "# Advanced Flag in Blueprint\n\nThe `--advanced` flag in Blueprint serves as a switch to enable additional features during"
},
{
"path": "docs/docs/advanced-flag/docker.md",
"chars": 3332,
"preview": "The Docker advanced flag provides the app's Dockerfile configuration and creates or updates the docker-compose.yml file,"
},
{
"path": "docs/docs/advanced-flag/goreleaser.md",
"chars": 1735,
"preview": "Release process for Go projects, providing extensive customization options through its configuration file, `.goreleaser."
},
{
"path": "docs/docs/advanced-flag/htmx-templ.md",
"chars": 2143,
"preview": "The WEB directory contains the web-related components and assets for the project. It leverages [htmx](https://github.com"
},
{
"path": "docs/docs/advanced-flag/react-vite.md",
"chars": 6306,
"preview": "This template provides a minimal setup for getting React working with Vite for the frontend and go on the backend. It al"
},
{
"path": "docs/docs/advanced-flag/tailwind.md",
"chars": 2344,
"preview": "Tailwind is closely coupled with the advanced HTMX flag, and HTMX will be automatically used if you select Tailwind in y"
},
{
"path": "docs/docs/advanced-flag/websocket.md",
"chars": 1048,
"preview": "A `/websocket` endpoint is added in `routes.go` to facilitate websocket connections. Upon accessing this endpoint, the s"
},
{
"path": "docs/docs/blueprint-core/db-drivers.md",
"chars": 3652,
"preview": "To extend the project with database functionality, users can choose from a variety of Go database drivers. Each driver i"
},
{
"path": "docs/docs/blueprint-core/frameworks.md",
"chars": 1160,
"preview": "Created projects can utilize several Go web frameworks to handle HTTP routing and server functionality. The chosen frame"
},
{
"path": "docs/docs/blueprint-ui.md",
"chars": 636,
"preview": "The Blueprint UI is a crucial component of the Go Blueprint ecosystem, providing a user-friendly interface for creating "
},
{
"path": "docs/docs/creating-project/air.md",
"chars": 2411,
"preview": "## Air - Live Reloading Utility\n\n[Air](https://github.com/cosmtrek/air) is a live-reloading utility designed to enhance "
},
{
"path": "docs/docs/creating-project/makefile.md",
"chars": 2003,
"preview": "## Makefile Project Management\n\nMakefile is designed for building, running, and testing a Go project. It includes suppor"
},
{
"path": "docs/docs/creating-project/project-init.md",
"chars": 2565,
"preview": "## Creating a Project\n\nAfter installing the Go-Blueprint CLI tool, you can create a new project with the default setting"
},
{
"path": "docs/docs/endpoints-test/mongo.md",
"chars": 1283,
"preview": "To test the MongoDB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n``"
},
{
"path": "docs/docs/endpoints-test/redis.md",
"chars": 8379,
"preview": "To test the Redis Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```\n"
},
{
"path": "docs/docs/endpoints-test/scylladb.md",
"chars": 4870,
"preview": "To test the ScyllaDB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n`"
},
{
"path": "docs/docs/endpoints-test/server.md",
"chars": 1940,
"preview": "## Testing Endpoints with CURL and WebSocat\n\nTesting endpoints is an essential part of ensuring the correctness and func"
},
{
"path": "docs/docs/endpoints-test/sql.md",
"chars": 3440,
"preview": "To test the SQL DB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```"
},
{
"path": "docs/docs/endpoints-test/web.md",
"chars": 513,
"preview": "\nTo test the /web endpoint when HTMX and Temp are used, you can simply open it in a web browser. This endpoint serves an"
},
{
"path": "docs/docs/endpoints-test/websocket.md",
"chars": 1202,
"preview": "## Testing with WebSocat\n[WebSocat](https://github.com/vi/websocat) is a versatile tool for working with websockets from"
},
{
"path": "docs/docs/index.md",
"chars": 5143,
"preview": "---\nhide:\n - toc\n---\n\n## Go Blueprint - Ultimate Golang Blueprint Library\n\n\n\nPowerful CLI too"
},
{
"path": "docs/docs/installation.md",
"chars": 2439,
"preview": "---\nhide:\n - toc\n---\n\nGo-Blueprint provides a convenient CLI tool to effortlessly set up your Go projects. Follow the s"
},
{
"path": "docs/mkdocs.yml",
"chars": 2252,
"preview": "### Site metadata ###\n\nsite_name: Go-Blueprint Docs\nsite_description: Official documentation for Go-Blueprint project\nsi"
},
{
"path": "docs/requirements.txt",
"chars": 37,
"preview": "mkdocs==1.5.3\nmkdocs-material==9.5.15"
},
{
"path": "go.mod",
"chars": 1125,
"preview": "module github.com/melkeydev/go-blueprint\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/charmbracelet/bubbles v0.16.1\n\tgithub.com/cha"
},
{
"path": "go.sum",
"chars": 4623,
"preview": "github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go"
},
{
"path": "main.go",
"chars": 93,
"preview": "package main\n\nimport \"github.com/melkeydev/go-blueprint/cmd\"\n\nfunc main() {\n\tcmd.Execute()\n}\n"
},
{
"path": "scripts/completions.sh",
"chars": 153,
"preview": "#!/bin/sh\nset -e\nrm -rf completions\nmkdir completions\nfor sh in bash zsh fish; do\n go run main.go completion \"$sh\" >\"co"
},
{
"path": "scripts/create-npm-packages.sh",
"chars": 7308,
"preview": "#!/bin/bash\n\nset -euo pipefail\n\nVERSION=\"$1\"\nPACKAGE_NAME=\"@melkeydev/go-blueprint\"\nMAIN_PACKAGE_DIR=\"npm-package\"\nPLATF"
}
]
About this extraction
This page contains the full source code of the Melkeydev/go-blueprint GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 163 files (350.9 KB), approximately 103.3k tokens, and a symbol index with 249 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.