[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [melkeydev]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: Bug Report\ndescription: Found a bug? Please let us know!\ntitle: \"[Bug]\"\nlabels: [\"Bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Please provide as much information as possible. This will help us resolve the Bug quickly and accurately.\n  - type: markdown\n    attributes:\n      value: ---\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What is the problem?\n      description: |\n        Description of what needs to be fixed.\n      placeholder: Tell us what you see!\n    validations:\n      required: true\n  - type: input\n    id: os\n    attributes:\n      label: Operating System\n      description: What is the affected operating system?\n    validations:\n      required: true\n  - type: input\n    id: arch\n    attributes:\n      label: Architecture Version (x86, x64, arm, etc)\n      description: (x86, x64, arm, etc)\n    validations:\n      required: true\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: Steps to reproduce\n      description: |\n        This includes the steps for reproducing the problem, the expected result, and the actual result.\n      placeholder: |\n        1. ...\n        2. ...\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n      render: shell\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Melkeydev Discord\n    url: https://discord.gg/HHZMSCu\n    about: Chat with the community."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: Feature Request\ndescription: Suggest a new idea for go-blueprint.\ntitle: \"[Feature Request] \"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Please provide as much information as possible by filling out form below.\n  - type: markdown\n    attributes:\n      value: ---\n  - type: textarea\n    id: idea\n    attributes:\n      label: Tell us about your feature request\n      description: |\n        Describe what you would like go-blueprint to be able to do.\n    validations:\n      required: true\n  - type: checkboxes\n    id: disclaimer\n    attributes:\n      label: Disclaimer\n      description: I have verified that this has not been suggested before.\n      options:\n        - label: I agree\n          required: true\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.\n\n## Problem/Feature\n\nPlease include a description of the problem or feature this PR is addressing.\n\n## Description of Changes: \n\n- Item 1\n- Item 2\n\n## Checklist\n\n- [ ] I have self-reviewed the changes being requested\n- [ ] I have updated the documentation (check issue ticket #218)\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "titleOnly: true"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: continuous integration\n\non:\n  push:\n    paths:\n      - '**.go'\n      - go.sum\n      - go.mod\n    branches-ignore:\n      - main\n  pull_request:\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.23.x'\n\n      - name: Deps cache\n        id: cache-go-deps\n        uses: actions/cache@v4\n        env:\n          cache-name: go-deps-cache\n        with:\n          path: ~/godeps\n          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-build-${{ env.cache-name }}-\n\n      - if: ${{ steps.cache-go-deps.outputs.cache-hit != 'true' }}\n        name: List the state of go modules\n        continue-on-error: true\n        run: go mod graph\n\n      - name: Install dependencies\n        run: |\n          go mod tidy\n          go mod download\n          go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4\n\n      - name: Run golangci-lint\n        run: golangci-lint run\n\n      - name: Run tests\n        run: |\n          go test ./...\n\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Deploy Docs\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build-deploy:\n    name: Build and deploy docs\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ~/.cache/pip\n          key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}\n          restore-keys: |\n            ${{ runner.os }}-pip-\n            ${{ runner.os }}-\n\n      - name: Install dependencies\n        working-directory: docs\n        run: make install\n\n      - name: Build and deploy to GitHub Pages\n        working-directory: docs\n        run: make deploy\n\n      # config for custom domain on gh-pages\n      # - name: Create and push CNAME file to gh-pages\n      #   run: |\n      #     git config --local user.email \"actions@github.com\"\n      #     git config --local user.name \"GitHub Actions\"\n      #     git checkout gh-pages\n\n      #     echo \"<domain>\" > CNAME\n\n      #     git add CNAME\n      #     git commit -m \"Add CNAME file\"\n      #     git push origin gh-pages\n"
  },
  {
    "path": ".github/workflows/generate-linter-advanced.yml",
    "content": "name: Linting Generated Blueprints Advanced\n\non:\n  pull_request: {}\n  workflow_dispatch: {}\n\njobs:\n  framework_matrix:\n    strategy:\n      matrix:\n        framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]\n        driver: [postgres]\n        git: [commit]\n        advanced: [htmx, githubaction, websocket, tailwind, docker, react]\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.23.x'\n\n      - name: Install golangci-lint\n        run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4\n\n      - name: Commit report\n        run: |\n          git config --global user.name 'testname'\n          git config --global user.email 'testemail@users.noreply.github.com'\n\n      - name: Set framework variable\n        id: set-proejct-directory\n        run: echo \"PROJECT_DIRECTORY=${{ matrix.framework }}\" | sed 's/\\//-/g' >> $GITHUB_ENV\n\n      - name: build templates\n        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 }}\"\n\n      - if: ${{ matrix.advanced == 'htmx' || matrix.advanced == 'tailwind' }}\n        name: Install Templ & gen templates\n        run: | \n          go install github.com/a-h/templ/cmd/templ@latest\n          /home/runner/go/bin/templ generate -path ${{ env.PROJECT_DIRECTORY }}\n\n      - name: golangci-lint\n        run: | \n          cd ${{ env.PROJECT_DIRECTORY }}\n          golangci-lint run\n\n      - name: remove templates\n        run: rm -rf ${{ env.PROJECT_DIRECTORY }}\n"
  },
  {
    "path": ".github/workflows/generate-linter-core.yml",
    "content": "name: Linting Generated Blueprints Core\n\non:\n  pull_request: {}\n  workflow_dispatch: {}\n\njobs:\n  framework_matrix:\n    strategy:\n      matrix:\n        framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]\n        driver: [mysql, postgres, sqlite, mongo, redis, scylla, none]\n        git: [commit, stage, skip]\n\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.23.x'\n\n      - name: Install golangci-lint\n        run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4\n\n      - name: Commit report\n        run: |\n          git config --global user.name 'testname'\n          git config --global user.email 'testemail@users.noreply.github.com'\n\n      - name: Set framework variable\n        id: set-proejct-directory\n        run: echo \"PROJECT_DIRECTORY=${{ matrix.framework }}\" | sed 's/\\//-/g' >> $GITHUB_ENV\n\n      - name: build templates\n        run: script -q /dev/null -c \"go run main.go create -n ${{ env.PROJECT_DIRECTORY }} -f ${{ matrix.framework}} -d ${{ matrix.driver }} -g ${{ matrix.git}}\"\n\n      - name: golangci-lint\n        run: | \n          cd ${{ env.PROJECT_DIRECTORY }}\n          golangci-lint run\n\n      - name: remove templates\n        run: rm -rf ${{ env.PROJECT_DIRECTORY }}\n"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "name: npm-publish\n\non:\n  workflow_call:\n    inputs:\n      tag:\n        description: \"Release tag to publish (e.g., v1.0.0)\"\n        required: true\n        type: string\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"Release tag to publish (e.g., v1.0.0)\"\n        required: true\n        type: string\n\njobs:\n  npm-publish:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write\n    env:\n      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Download release assets\n        run: |\n          TAG=\"${{ inputs.tag }}\"\n          VERSION=${TAG#v}\n          mkdir -p dist\n          gh release download \"$TAG\" --dir dist\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Create npm packages\n        run: |\n          chmod +x ./scripts/create-npm-packages.sh\n          ./scripts/create-npm-packages.sh \"$VERSION\"\n\n      - name: Publish platform-specific packages to npm\n        run: |\n          for platform_dir in platform-packages/go-blueprint-*; do\n            if [ -d \"$platform_dir\" ]; then\n              cd \"$platform_dir\"\n              npm publish --provenance --access public\n              cd - > /dev/null\n            fi\n          done\n\n      - name: Publish main package to npm\n        run: |\n          cd npm-package\n          npm publish --provenance --access public\n\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: goreleaser\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\npermissions:\n  contents: write\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: \"1.21.1\"\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v5.0.0\n        with:\n          distribution: goreleaser\n          version: ${{ env.GITHUB_REF_NAME }}\n          args: release --clean\n          workdir: ./\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  npm-publish:\n    needs: goreleaser\n    permissions:\n      contents: read\n      id-token: write\n    uses: ./.github/workflows/npm-publish.yml\n    with:\n      tag: ${{ github.ref_name }}\n    secrets: inherit\n\n"
  },
  {
    "path": ".github/workflows/testcontainers.yml",
    "content": "name: Integrations Test for the Generated Blueprints\n\non:\n  pull_request: {}\n  workflow_dispatch: {}\n\njobs:\n  itests_matrix:\n    strategy:\n      matrix:\n        driver:\n          [mysql, postgres, mongo, redis, scylla]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.23.x'\n\n      - name: Commit report\n        run: |\n          git config --global user.name 'testname'\n          git config --global user.email 'testemail@users.noreply.github.com'\n\n      - name: build ${{ matrix.driver }} template\n        run: script -q /dev/null -c \"go run main.go create -n ${{ matrix.driver }} -g commit -f fiber -d ${{matrix.driver}}\"\n\n      - name: run ${{ matrix.driver }} integration tests\n        working-directory: ${{ matrix.driver }}\n        run: make itest\n\n      - name: remove ${{ matrix.driver }} template\n        run: rm -rf ${{ matrix.driver }}\n"
  },
  {
    "path": ".github/workflows/update-htmx-version.yml",
    "content": "name: Check for new htmx release\n\non:\n  schedule:\n    - cron: '0 0 * * Sun'\n\njobs:\n  check_release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Get version from file\n        id: get_version_file\n        run: |\n          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}')\n          echo \"version file: $VERSION_FILE\"\n          if [[ \"$VERSION_FILE\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n            echo \"version_file=$VERSION_FILE\" >> $GITHUB_OUTPUT\n          else\n            echo \"Invalid VERSION_FILE format: $VERSION_FILE\" >&2\n            exit 1\n          fi\n\n      - name: Get version from GitHub API\n        id: get_version_api\n        run: |\n          VERSION_API=$(curl -s https://api.github.com/repos/bigskysoftware/htmx/releases/latest | jq -r '.tag_name')\n          echo \"version api: $VERSION_API\"\n          if [[ \"$VERSION_API\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n            echo \"version_api=$VERSION_API\" >> $GITHUB_OUTPUT\n          else\n            echo \"Invalid VERSION_API format: $VERSION_API\" >&2\n            exit 1\n          fi\n\n      - name: Compare versions\n        id: compare_versions\n        run: |\n          if [ \"${{ steps.get_version_api.outputs.version_api }}\" != \"${{ steps.get_version_file.outputs.version_file }}\" ]; then\n            echo \"release_changed=true\" >> $GITHUB_OUTPUT\n            echo \"Release changed: true\"\n          else\n            echo \"release_changed=false\" >> $GITHUB_OUTPUT\n            echo \"Release changed: false\"\n          fi\n\n      - name: dump latest htmx version\n        if: steps.compare_versions.outputs.release_changed == 'true'\n        run: curl -L https://github.com/bigskysoftware/htmx/releases/latest/download/htmx.min.js -o cmd/template/advanced/files/htmx/htmx.min.js\n\n      - name: Prettify code\n        if: steps.compare_versions.outputs.release_changed == 'true'\n        run: |\n          npm install --save-dev --save-exact prettier\n          npx prettier --write cmd/template/advanced/files/htmx/htmx.min.js\n          rm -rf node_modules\n          rm package-lock.json\n          rm package.json\n\n      - name: Create tmpl after Prettify\n        if: steps.compare_versions.outputs.release_changed == 'true'\n        run: mv cmd/template/advanced/files/htmx/htmx.min.js cmd/template/advanced/files/htmx/htmx.min.js.tmpl\n\n      - name: Create Pull Request\n        if: steps.compare_versions.outputs.release_changed == 'true'\n        uses: peter-evans/create-pull-request@v6\n        with:\n          commit-message: update htmx version ${{ steps.get_version_api.outputs.version_api }}\n          title: Update htmx to version ${{ steps.get_version_api.outputs.version_api }} [Bot]\n          body: New htmx ${{ steps.get_version_api.outputs.version_api }} version is available. This is an automatic PR to update changes.\n          branch: htmx-version-update\n          base: main\n"
  },
  {
    "path": ".gitignore",
    "content": "go-blueprint\nsite\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "before:\n  hooks:\n    - go mod tidy\n    - ./scripts/completions.sh\nbuilds:\n  - binary: go-blueprint\n    main: ./\n    goos:\n      - darwin\n      - linux\n      - windows\n    goarch:\n      - amd64\n      - arm64\n    env:\n      - CGO_ENABLED=0\n    ldflags:\n      - -s -w -X github.com/melkeydev/go-blueprint/cmd.GoBlueprintVersion={{.Version}}\n\nrelease:\n  prerelease: auto\n\nuniversal_binaries:\n  - replace: true\narchives:\n  - name_template: >-\n      {{- .ProjectName }}_ {{- .Version }}_ {{- title .Os }}_ {{- if eq .Arch \"amd64\" }}x86_64 {{- else if eq .Arch \"386\" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end -}}\n    format_overrides:\n      - goos: windows\n        format: zip\n    builds_info:\n      group: root\n      owner: root\n    files:\n      - README.md\n      - LICENSE\n      - completions/*\n\nchecksum:\n  name_template: \"checksums.txt\"\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v3.2.0\n    hooks:\n        - id: trailing-whitespace\n        - id: end-of-file-fixer\n        - id: check-added-large-files\n  - repo: https://github.com/golangci/golangci-lint\n    rev: v1.55.2\n    hooks:\n      - id: golangci-lint\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for helping make go-blueprint better! \n- [Contributing](#contributing)\n  - [Design Principles](#design-principles)\n  - [Report an Issue](#report-an-issue)\n  - [Contributing Code with Pull Requests](#contributing-code-with-pull-requests)\n    - [Requirements](#requirements)\n  - [Licensing](#licensing)\n\n## Design Principles\n\nContributions to go-blueprint should align with the project’s design principles:\n\n * Maintain backwards compatibility whenever possible.\n\n## Report an Issue\n\nIf you have run into a bug or want to discuss a new feature, please [file an issue](https://github.com/Melkeydev/go-blueprint/issues).\n\n## Contributing Code with Pull Requests\n\ngo-blueprint uses [Github pull requests](https://github.com/Melkeydev/go-blueprint/pulls). Feel free to fork, hack away at your changes and submit.\n\n### Requirements\n\n *  All commands and functionality should be documented appropriately\n *  All new functionality/features should have appropriate unit testing \n\ngo-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.\n\n## Licensing\n\nSee 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.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\r\n\r\nCopyright (c) 2023 Melkeydev\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "![logo](./public/logo.png)\n\n<div style=\"text-align: center;\">\n  <h1>\n    Introducing the Ultimate Golang Blueprint Library\n  </h1>\n</div>\n\nGo Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly. It also\ngives the option to integrate with one of the more popular Go frameworks (and the list is growing with new features)!\n\n### Why Would I use this?\n\n- Easy to set up and install\n- Have the entire Go structure already established\n- Setting up a Go HTTP server (or Fasthttp with Fiber)\n- Integrate with a popular frameworks\n- Focus on the actual code of your application\n\n## Table of Contents\n\n- [Install](#install)\n- [Frameworks Supported](#frameworks-supported)\n- [Database Support](#database-support)\n- [Advanced Features](#advanced-features)\n- [Blueprint UI](#blueprint-ui)\n- [Usage Example](#usage-example)\n- [GitHub Stats](#github-stats)\n- [License](#license)\n\n<a id=\"install\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/install.gif?raw=true\" width=\"60px\" style=\"margin-right: 1px;\">\n  </picture>\n  Install\n</h2>\n\n### Go Install\n```bash\ngo install github.com/melkeydev/go-blueprint@latest\n```\n\nThis installs a go binary that will automatically bind to your $GOPATH\n\n> if you’re using Zsh, you’ll need to add it manually to `~/.zshrc`.\n\n```bash\nGOPATH=$HOME/go  PATH=$PATH:/usr/local/go/bin:$GOPATH/bin\n```\n\ndon't forget to update\n\n```bash\nsource ~/.zshrc\n```\n\n### NPM Install\n```bash\nnpm install -g @melkeydev/go-blueprint\n```\n\n### Homebrew Install\n```bash\nbrew install go-blueprint\n```\n\nThen in a new terminal run:\n\n```bash\ngo-blueprint create\n```\n\nYou can also use the provided flags to set up a project without interacting with the UI.\n\n```bash\ngo-blueprint create --name my-project --framework gin --driver postgres --git commit\n```\n\nSee `go-blueprint create -h` for all the options and shorthands.\n\n<a id=\"frameworks-supported\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/frameworks.gif?raw=true\" width=\"60px\" style=\"margin-right: 1px;\">\n  </picture>\n  Frameworks Supported\n</h2>\n\n- [Chi](https://github.com/go-chi/chi)\n- [Gin](https://github.com/gin-gonic/gin)\n- [Fiber](https://github.com/gofiber/fiber)\n- [HttpRouter](https://github.com/julienschmidt/httprouter)\n- [Gorilla/mux](https://github.com/gorilla/mux)\n- [Echo](https://github.com/labstack/echo)\n\n<a id=\"database-support\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/database.gif?raw=true\" width=\"45px\" style=\"margin-right: 15px;\">\n  </picture>\n  Database Support\n</h2>\n\nGo 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.\n\n### Supported Database Drivers\n\nChoose from a variety of supported database drivers:\n\n- [Mysql](https://github.com/go-sql-driver/mysql)\n- [Postgres](https://github.com/jackc/pgx/)\n- [Sqlite](https://github.com/mattn/go-sqlite3)\n- [Mongo](https://go.mongodb.org/mongo-driver)\n- [Redis](https://github.com/redis/go-redis)\n- [ScyllaDB GoCQL](https://github.com/scylladb/gocql)\n\n<a id=\"advanced-features\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/advanced.gif?raw=true\" width=\"70px\" style=\"margin-right: 1px;\">\n  </picture>\n  Advanced Features\n</h2>\n\nBlueprint 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.\n\nYou 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:\n\n- [HTMX](https://htmx.org/) support using [Templ](https://templ.guide/)\n- CI/CD workflow setup using [Github Actions](https://docs.github.com/en/actions)\n- [Websocket](https://pkg.go.dev/github.com/coder/websocket) sets up a websocket endpoint\n- [Tailwind](https://tailwindcss.com/) css framework\n- Docker configuration for go project\n- [React](https://react.dev/) frontend written in TypeScript, including an example fetch request to the backend\n\nNote: Selecting Tailwind option will automatically select HTMX unless React is explicitly selected\n\n<a id=\"blueprint-ui\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/ui.gif?raw=true\" width=\"100px\" style=\"margin-right: 1px;\">\n  </picture>\n  Blueprint UI\n</h2>\n\nBlueprint 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)\n\n<a id=\"usage-example\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/example.gif?raw=true\" width=\"60px\" style=\"margin-right: 1px;\">\n  </picture>\n  Usage Example\n</h2>\n\nHere's an example of setting up a project with a specific database driver:\n\n```bash\ngo-blueprint create --name my-project --framework gin --driver postgres --git commit\n```\n\n<p align=\"center\">\n  <img src=\"./public/blueprint_1.png\" alt=\"Starter Image\" width=\"800\"/>\n</p>\n\nAdvanced features are accessible with the --advanced flag\n\n```bash\ngo-blueprint create --advanced\n```\n\nAdvanced features can be enabled using the `--feature` flag along with the `--advanced` flag.\n\nHTMX:\n\n```bash\ngo-blueprint create --advanced --feature htmx\n```\n\nCI/CD workflow:\n\n```bash\ngo-blueprint create --advanced --feature githubaction\n```\n\nWebsocket:\n\n```bash\ngo-blueprint create --advanced --feature websocket\n```\n\nTailwind:\n\n```bash\ngo-blueprint create --advanced --feature tailwind\n```\n\nDocker:\n\n```bash\ngo-blueprint create --advanced --feature docker\n```\n\nReact:\n\n```bash\ngo-blueprint create --advanced --feature react\n```\n\nOr all features at once:\n\n```bash\ngo-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker --git commit --feature react\n```\n\n<p align=\"center\">\n  <img src=\"./public/blueprint_advanced.png\" alt=\"Advanced Options\" width=\"800\"/>\n</p>\n\n**Visit [documentation](https://docs.go-blueprint.dev) to learn more about blueprint and its features.**\n\n<a id=\"github-stats\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/stats.gif?raw=true\" width=\"45px\" style=\"margin-right: 10px;\">\n  </picture>\n  GitHub Stats\n</h2>\n\n<p align=\"center\">\n  <img alt=\"Alt\" src=\"https://repobeats.axiom.co/api/embed/7c4be18864d441f961be61186ce49b5471a9e7bf.svg\" title=\"Repobeats analytics image\"/>\n</p>\n\n<a id=\"license\"></a>\n\n<h2>\n  <picture>\n    <img src=\"./public/license.gif?raw=true\" width=\"50px\" style=\"margin-right: 1px;\">\n  </picture>\n  License\n</h2>\n\nLicensed under [MIT License](./LICENSE)\n"
  },
  {
    "path": "cmd/create.go",
    "content": "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/charmbracelet/lipgloss\"\n\t\"github.com/melkeydev/go-blueprint/cmd/flags\"\n\t\"github.com/melkeydev/go-blueprint/cmd/program\"\n\t\"github.com/melkeydev/go-blueprint/cmd/steps\"\n\t\"github.com/melkeydev/go-blueprint/cmd/ui/multiInput\"\n\t\"github.com/melkeydev/go-blueprint/cmd/ui/multiSelect\"\n\t\"github.com/melkeydev/go-blueprint/cmd/ui/spinner\"\n\t\"github.com/melkeydev/go-blueprint/cmd/ui/textinput\"\n\t\"github.com/melkeydev/go-blueprint/cmd/utils\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst logo = `\n\n ____  _                       _       _   \n|  _ \\| |                     (_)     | |  \n| |_) | |_   _  ___ _ __  _ __ _ _ __ | |_ \n|  _ <| | | | |/ _ \\ '_ \\| '__| | '_ \\| __|\n| |_) | | |_| |  __/ |_) | |  | | | | | |_ \n|____/|_|\\__,_|\\___| .__/|_|  |_|_| |_|\\__|\n\t\t\t\t   | |                     \n\t\t\t\t   |_|                     \n\n`\n\nvar (\n\tlogoStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#01FAC6\")).Bold(true)\n\ttipMsgStyle    = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"190\")).Italic(true)\n\tendingMsgStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"170\")).Bold(true)\n)\n\nfunc init() {\n\tvar flagFramework flags.Framework\n\tvar flagDBDriver flags.Database\n\tvar advancedFeatures flags.AdvancedFeatures\n\tvar flagGit flags.Git\n\trootCmd.AddCommand(createCmd)\n\n\tcreateCmd.Flags().StringP(\"name\", \"n\", \"\", \"Name of project to create\")\n\tcreateCmd.Flags().VarP(&flagFramework, \"framework\", \"f\", fmt.Sprintf(\"Framework to use. Allowed values: %s\", strings.Join(flags.AllowedProjectTypes, \", \")))\n\tcreateCmd.Flags().VarP(&flagDBDriver, \"driver\", \"d\", fmt.Sprintf(\"Database drivers to use. Allowed values: %s\", strings.Join(flags.AllowedDBDrivers, \", \")))\n\tcreateCmd.Flags().BoolP(\"advanced\", \"a\", false, \"Get prompts for advanced features\")\n\tcreateCmd.Flags().Var(&advancedFeatures, \"feature\", fmt.Sprintf(\"Advanced feature to use. Allowed values: %s\", strings.Join(flags.AllowedAdvancedFeatures, \", \")))\n\tcreateCmd.Flags().VarP(&flagGit, \"git\", \"g\", fmt.Sprintf(\"Git to use. Allowed values: %s\", strings.Join(flags.AllowedGitsOptions, \", \")))\n\n\tutils.RegisterStaticCompletions(createCmd, \"framework\", flags.AllowedProjectTypes)\n\tutils.RegisterStaticCompletions(createCmd, \"driver\", flags.AllowedDBDrivers)\n\tutils.RegisterStaticCompletions(createCmd, \"feature\", flags.AllowedAdvancedFeatures)\n\tutils.RegisterStaticCompletions(createCmd, \"git\", flags.AllowedGitsOptions)\n}\n\ntype Options struct {\n\tProjectName *textinput.Output\n\tProjectType *multiInput.Selection\n\tDBDriver    *multiInput.Selection\n\tAdvanced    *multiSelect.Selection\n\tWorkflow    *multiInput.Selection\n\tGit         *multiInput.Selection\n}\n\n// createCmd defines the \"create\" command for the CLI\nvar createCmd = &cobra.Command{\n\tUse:   \"create\",\n\tShort: \"Create a Go project and don't worry about the structure\",\n\tLong:  \"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\",\n\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tvar tprogram *tea.Program\n\t\tvar err error\n\n\t\tisInteractive := false\n\t\tflagName := cmd.Flag(\"name\").Value.String()\n\n\t\tif flagName != \"\" && !utils.ValidateModuleName(flagName) {\n\t\t\terr = fmt.Errorf(\"'%s' is not a valid module name. Please choose a different name\", flagName)\n\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t}\n\n\t\trootDirName := utils.GetRootDir(flagName)\n\t\tif rootDirName != \"\" && doesDirectoryExistAndIsNotEmpty(rootDirName) {\n\t\t\terr = fmt.Errorf(\"directory '%s' already exists and is not empty. Please choose a different name\", rootDirName)\n\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t}\n\n\t\t// VarP already validates the contents of the framework flag.\n\t\t// If this flag is filled, it is always valid\n\t\tflagFramework := flags.Framework(cmd.Flag(\"framework\").Value.String())\n\t\tflagDBDriver := flags.Database(cmd.Flag(\"driver\").Value.String())\n\t\tflagGit := flags.Git(cmd.Flag(\"git\").Value.String())\n\n\t\toptions := Options{\n\t\t\tProjectName: &textinput.Output{},\n\t\t\tProjectType: &multiInput.Selection{},\n\t\t\tDBDriver:    &multiInput.Selection{},\n\t\t\tAdvanced: &multiSelect.Selection{\n\t\t\t\tChoices: make(map[string]bool),\n\t\t\t},\n\t\t\tGit: &multiInput.Selection{},\n\t\t}\n\n\t\tproject := &program.Project{\n\t\t\tProjectName:     flagName,\n\t\t\tProjectType:     flagFramework,\n\t\t\tDBDriver:        flagDBDriver,\n\t\t\tFrameworkMap:    make(map[flags.Framework]program.Framework),\n\t\t\tDBDriverMap:     make(map[flags.Database]program.Driver),\n\t\t\tAdvancedOptions: make(map[string]bool),\n\t\t\tGitOptions:      flagGit,\n\t\t}\n\n\t\tsteps := steps.InitSteps(flagFramework, flagDBDriver)\n\t\tfmt.Printf(\"%s\\n\", logoStyle.Render(logo))\n\n\t\t// Advanced option steps:\n\t\tflagAdvanced, err := cmd.Flags().GetBool(\"advanced\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"failed to retrieve advanced flag\")\n\t\t}\n\n\t\tif flagAdvanced {\n\t\t\tfmt.Println(tipMsgStyle.Render(\"*** You are in advanced mode ***\\n\\n\"))\n\t\t}\n\n\t\tif project.ProjectName == \"\" {\n\t\t\tisInteractive = true\n\t\t\ttprogram := tea.NewProgram(textinput.InitialTextInputModel(options.ProjectName, \"What is the name of your project?\", project))\n\t\t\tif _, err := tprogram.Run(); err != nil {\n\t\t\t\tlog.Printf(\"Name of project contains an error: %v\", err)\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\n\t\t\tif options.ProjectName.Output != \"\" && !utils.ValidateModuleName(options.ProjectName.Output) {\n\t\t\t\terr = fmt.Errorf(\"'%s' is not a valid module name. Please choose a different name\", options.ProjectName.Output)\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\n\t\t\trootDirName = utils.GetRootDir(options.ProjectName.Output)\n\t\t\tif doesDirectoryExistAndIsNotEmpty(rootDirName) {\n\t\t\t\terr = fmt.Errorf(\"directory '%s' already exists and is not empty. Please choose a different name\", rootDirName)\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\t\t\tproject.ExitCLI(tprogram)\n\n\t\t\tproject.ProjectName = options.ProjectName.Output\n\t\t\terr := cmd.Flag(\"name\").Value.Set(project.ProjectName)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"failed to set the name flag value\", err)\n\t\t\t}\n\t\t}\n\n\t\tif project.ProjectType == \"\" {\n\t\t\tisInteractive = true\n\t\t\tstep := steps.Steps[\"framework\"]\n\t\t\ttprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.ProjectType, step.Headers, project))\n\t\t\tif _, err := tprogram.Run(); err != nil {\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\t\t\tproject.ExitCLI(tprogram)\n\n\t\t\tstep.Field = options.ProjectType.Choice\n\n\t\t\t// this type casting is always safe since the user interface can\n\t\t\t// only pass strings that can be cast to a flags.Framework instance\n\t\t\tproject.ProjectType = flags.Framework(strings.ToLower(options.ProjectType.Choice))\n\t\t\terr := cmd.Flag(\"framework\").Value.Set(project.ProjectType.String())\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"failed to set the framework flag value\", err)\n\t\t\t}\n\t\t}\n\n\t\tif project.DBDriver == \"\" {\n\t\t\tisInteractive = true\n\t\t\tstep := steps.Steps[\"driver\"]\n\t\t\ttprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.DBDriver, step.Headers, project))\n\t\t\tif _, err := tprogram.Run(); err != nil {\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\t\t\tproject.ExitCLI(tprogram)\n\n\t\t\t// this type casting is always safe since the user interface can\n\t\t\t// only pass strings that can be cast to a flags.Database instance\n\t\t\tproject.DBDriver = flags.Database(strings.ToLower(options.DBDriver.Choice))\n\t\t\terr := cmd.Flag(\"driver\").Value.Set(project.DBDriver.String())\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"failed to set the driver flag value\", err)\n\t\t\t}\n\t\t}\n\n\t\tif flagAdvanced {\n\n\t\t\tfeatureFlags := cmd.Flag(\"feature\").Value.String()\n\n\t\t\tif featureFlags != \"\" {\n\t\t\t\tfeaturesFlagValues := strings.Split(featureFlags, \",\")\n\t\t\t\tfor _, key := range featuresFlagValues {\n\t\t\t\t\tproject.AdvancedOptions[key] = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tisInteractive = true\n\t\t\t\tstep := steps.Steps[\"advanced\"]\n\t\t\t\ttprogram = tea.NewProgram((multiSelect.InitialModelMultiSelect(step.Options, options.Advanced, step.Headers, project)))\n\t\t\t\tif _, err := tprogram.Run(); err != nil {\n\t\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t\t}\n\t\t\t\tproject.ExitCLI(tprogram)\n\t\t\t\tfor key, opt := range options.Advanced.Choices {\n\t\t\t\t\tproject.AdvancedOptions[strings.ToLower(key)] = opt\n\t\t\t\t\terr := cmd.Flag(\"feature\").Value.Set(strings.ToLower(key))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Fatal(\"failed to set the feature flag value\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Fatal(\"failed to set the htmx option\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif project.GitOptions == \"\" {\n\t\t\tisInteractive = true\n\t\t\tstep := steps.Steps[\"git\"]\n\t\t\ttprogram = tea.NewProgram(multiInput.InitialModelMulti(step.Options, options.Git, step.Headers, project))\n\t\t\tif _, err := tprogram.Run(); err != nil {\n\t\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t\t}\n\t\t\tproject.ExitCLI(tprogram)\n\n\t\t\tproject.GitOptions = flags.Git(strings.ToLower(options.Git.Choice))\n\t\t\terr := cmd.Flag(\"git\").Value.Set(project.GitOptions.String())\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"failed to set the git flag value\", err)\n\t\t\t}\n\t\t}\n\n\t\tcurrentWorkingDir, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"could not get current working directory: %v\", err)\n\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t}\n\t\tproject.AbsolutePath = currentWorkingDir\n\n\t\tspinner := tea.NewProgram(spinner.InitialModelNew())\n\n\t\t// add synchronization to wait for spinner to finish\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif _, err := spinner.Run(); err != nil {\n\t\t\t\tcobra.CheckErr(err)\n\t\t\t}\n\t\t}()\n\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tfmt.Println(\"The program encountered an unexpected issue and had to exit. The error was:\", r)\n\t\t\t\tfmt.Println(\"If you continue to experience this issue, please post a message on our GitHub page or join our Discord server for support.\")\n\t\t\t\tif releaseErr := spinner.ReleaseTerminal(); releaseErr != nil {\n\t\t\t\t\tlog.Printf(\"Problem releasing terminal: %v\", releaseErr)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t// This calls the templates\n\t\terr = project.CreateMainFile()\n\t\tif err != nil {\n\t\t\tif releaseErr := spinner.ReleaseTerminal(); releaseErr != nil {\n\t\t\t\tlog.Printf(\"Problem releasing terminal: %v\", releaseErr)\n\t\t\t}\n\t\t\tlog.Printf(\"Problem creating files for project.\")\n\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err).Err())\n\t\t}\n\n\t\tfmt.Println(endingMsgStyle.Render(\"\\nNext steps:\"))\n\t\tfmt.Println(endingMsgStyle.Render(fmt.Sprintf(\"• cd into the newly created project with: `cd %s`\\n\", utils.GetRootDir(project.ProjectName))))\n\n\t\tif options.Advanced.Choices[\"React\"] {\n\t\t\toptions.Advanced.Choices[\"Htmx\"] = false\n\t\t\toptions.Advanced.Choices[\"Tailwind\"] = false\n\t\t\tfmt.Println(endingMsgStyle.Render(\"• cd into frontend\\n\"))\n\t\t\tfmt.Println(endingMsgStyle.Render(\"• npm install\\n\"))\n\t\t\tfmt.Println(endingMsgStyle.Render(\"• npm run dev\\n\"))\n\t\t}\n\n\t\tif options.Advanced.Choices[\"Tailwind\"] {\n\t\t\toptions.Advanced.Choices[\"Htmx\"] = true\n\t\t\tfmt.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\"))\n\t\t\tfmt.Println(endingMsgStyle.Render(\"• More info about the Tailwind CLI: https://tailwindcss.com/blog/standalone-cli\\n\"))\n\t\t}\n\n\t\tif options.Advanced.Choices[\"Htmx\"] {\n\t\t\toptions.Advanced.Choices[\"react\"] = false\n\t\t\tfmt.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\"))\n\t\t\tfmt.Println(endingMsgStyle.Render(\"• Generate templ function files by running `templ generate`\\n\"))\n\t\t}\n\n\t\tif isInteractive {\n\t\t\tnonInteractiveCommand := utils.NonInteractiveCommand(cmd.Use, cmd.Flags())\n\t\t\tfmt.Println(tipMsgStyle.Render(\"Tip: Repeat the equivalent Blueprint with the following non-interactive command:\"))\n\t\t\tfmt.Println(tipMsgStyle.Italic(false).Render(fmt.Sprintf(\"• %s\\n\", nonInteractiveCommand)))\n\t\t}\n\t\terr = spinner.ReleaseTerminal()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Could not release terminal: %v\", err)\n\t\t\tcobra.CheckErr(err)\n\t\t}\n\t},\n}\n\n// doesDirectoryExistAndIsNotEmpty checks if the directory exists and is not empty\nfunc doesDirectoryExistAndIsNotEmpty(name string) bool {\n\tif _, err := os.Stat(name); err == nil {\n\t\tdirEntries, err := os.ReadDir(name)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"could not read directory: %v\", err)\n\t\t\tcobra.CheckErr(textinput.CreateErrorInputModel(err))\n\t\t}\n\t\tif len(dirEntries) > 0 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/flags/advancedFeatures.go",
    "content": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype AdvancedFeatures []string\n\nconst (\n\tHtmx              string = \"htmx\"\n\tGoProjectWorkflow string = \"githubaction\"\n\tWebsocket         string = \"websocket\"\n\tTailwind          string = \"tailwind\"\n\tReact             string = \"react\"\n\tDocker            string = \"docker\"\n)\n\nvar AllowedAdvancedFeatures = []string{string(React), string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Docker)}\n\nfunc (f AdvancedFeatures) String() string {\n\treturn strings.Join(f, \",\")\n}\n\nfunc (f *AdvancedFeatures) Type() string {\n\treturn \"AdvancedFeatures\"\n}\n\nfunc (f *AdvancedFeatures) Set(value string) error {\n\t// Contains isn't available in 1.20 yet\n\t// if AdvancedFeatures.Contains(value) {\n\tfor _, advancedFeature := range AllowedAdvancedFeatures {\n\t\tif advancedFeature == value {\n\t\t\t*f = append(*f, advancedFeature)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"advanced Feature to use. Allowed values: %s\", strings.Join(AllowedAdvancedFeatures, \", \"))\n}\n"
  },
  {
    "path": "cmd/flags/database.go",
    "content": "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 you want to add one, you\n// can simply copy and paste a line here. Do not forget to also add it into the\n// AllowedDBDrivers slice too!\nconst (\n\tMySql    Database = \"mysql\"\n\tPostgres Database = \"postgres\"\n\tSqlite   Database = \"sqlite\"\n\tMongo    Database = \"mongo\"\n\tRedis    Database = \"redis\"\n\tScylla   Database = \"scylla\"\n\tNone     Database = \"none\"\n)\n\nvar AllowedDBDrivers = []string{string(MySql), string(Postgres), string(Sqlite), string(Mongo), string(Redis), string(Scylla), string(None)}\n\nfunc (f Database) String() string {\n\treturn string(f)\n}\n\nfunc (f *Database) Type() string {\n\treturn \"Database\"\n}\n\nfunc (f *Database) Set(value string) error {\n\t// Contains isn't available in 1.20 yet\n\t// if AllowedDBDrivers.Contains(value) {\n\tfor _, database := range AllowedDBDrivers {\n\t\tif database == value {\n\t\t\t*f = Database(value)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"Database to use. Allowed values: %s\", strings.Join(AllowedDBDrivers, \", \"))\n}\n"
  },
  {
    "path": "cmd/flags/frameworks.go",
    "content": "package flags\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Framework string\n\n// These are all the current frameworks supported. If you want to add one, you\n// can simply copy and paste a line here. Do not forget to also add it into the\n// AllowedProjectTypes slice too!\nconst (\n\tChi             Framework = \"chi\"\n\tGin             Framework = \"gin\"\n\tFiber           Framework = \"fiber\"\n\tGorillaMux      Framework = \"gorilla/mux\"\n\tHttpRouter      Framework = \"httprouter\"\n\tStandardLibrary Framework = \"standard-library\"\n\tEcho            Framework = \"echo\"\n)\n\nvar AllowedProjectTypes = []string{string(Chi), string(Gin), string(Fiber), string(GorillaMux), string(HttpRouter), string(StandardLibrary), string(Echo)}\n\nfunc (f Framework) String() string {\n\treturn string(f)\n}\n\nfunc (f *Framework) Type() string {\n\treturn \"Framework\"\n}\n\nfunc (f *Framework) Set(value string) error {\n\t// Contains isn't available in 1.20 yet\n\t// if AllowedProjectTypes.Contains(value) {\n\tfor _, project := range AllowedProjectTypes {\n\t\tif project == value {\n\t\t\t*f = Framework(value)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"Framework to use. Allowed values: %s\", strings.Join(AllowedProjectTypes, \", \"))\n}\n"
  },
  {
    "path": "cmd/flags/git.go",
    "content": "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   = \"skip\"\n)\n\nvar AllowedGitsOptions = []string{string(Commit), string(Stage), string(Skip)}\n\nfunc (f Git) String() string {\n\treturn string(f)\n}\n\nfunc (f *Git) Type() string {\n\treturn \"Git\"\n}\n\nfunc (f *Git) Set(value string) error {\n\tfor _, gitOption := range AllowedGitsOptions {\n\t\tif gitOption == value {\n\t\t\t*f = Git(value)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"Git to use. Allowed values: %s\", strings.Join(AllowedGitsOptions, \", \"))\n}\n"
  },
  {
    "path": "cmd/program/program.go",
    "content": "// 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\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"text/template\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/melkeydev/go-blueprint/cmd/flags\"\n\ttpl \"github.com/melkeydev/go-blueprint/cmd/template\"\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n\t\"github.com/melkeydev/go-blueprint/cmd/template/dbdriver\"\n\t\"github.com/melkeydev/go-blueprint/cmd/template/docker\"\n\t\"github.com/melkeydev/go-blueprint/cmd/template/framework\"\n\t\"github.com/melkeydev/go-blueprint/cmd/utils\"\n)\n\n// A Project contains the data for the project folder\n// being created, and methods that help with that process\ntype Project struct {\n\tProjectName       string\n\tExit              bool\n\tAbsolutePath      string\n\tProjectType       flags.Framework\n\tDBDriver          flags.Database\n\tDocker            flags.Database\n\tFrameworkMap      map[flags.Framework]Framework\n\tDBDriverMap       map[flags.Database]Driver\n\tDockerMap         map[flags.Database]Docker\n\tAdvancedOptions   map[string]bool\n\tAdvancedTemplates AdvancedTemplates\n\tGitOptions        flags.Git\n\tOSCheck           map[string]bool\n}\n\ntype AdvancedTemplates struct {\n\tTemplateRoutes  string\n\tTemplateImports string\n}\n\n// A Framework contains the name and templater for a\n// given Framework\ntype Framework struct {\n\tpackageName []string\n\ttemplater   Templater\n}\n\ntype Driver struct {\n\tpackageName []string\n\ttemplater   DBDriverTemplater\n}\n\ntype Docker struct {\n\tpackageName []string\n\ttemplater   DockerTemplater\n}\n\n// A Templater has the methods that help build the files\n// in the Project folder, and is specific to a Framework\ntype Templater interface {\n\tMain() []byte\n\tServer() []byte\n\tRoutes() []byte\n\tTestHandler() []byte\n\tHtmxTemplRoutes() []byte\n\tHtmxTemplImports() []byte\n\tWebsocketImports() []byte\n}\n\ntype DBDriverTemplater interface {\n\tService() []byte\n\tEnv() []byte\n\tTests() []byte\n}\n\ntype DockerTemplater interface {\n\tDocker() []byte\n}\n\ntype WorkflowTemplater interface {\n\tReleaser() []byte\n\tTest() []byte\n\tReleaserConfig() []byte\n}\n\nvar (\n\tchiPackage     = []string{\"github.com/go-chi/chi/v5\"}\n\tgorillaPackage = []string{\"github.com/gorilla/mux\"}\n\trouterPackage  = []string{\"github.com/julienschmidt/httprouter\"}\n\tginPackage     = []string{\"github.com/gin-gonic/gin\"}\n\tfiberPackage   = []string{\"github.com/gofiber/fiber/v2\"}\n\techoPackage    = []string{\"github.com/labstack/echo/v4\", \"github.com/labstack/echo/v4/middleware\"}\n\n\tmysqlDriver    = []string{\"github.com/go-sql-driver/mysql\"}\n\tpostgresDriver = []string{\"github.com/jackc/pgx/v5/stdlib\"}\n\tsqliteDriver   = []string{\"github.com/mattn/go-sqlite3\"}\n\tredisDriver    = []string{\"github.com/redis/go-redis/v9\"}\n\tmongoDriver    = []string{\"go.mongodb.org/mongo-driver\"}\n\tgocqlDriver    = []string{\"github.com/gocql/gocql\"}\n\tscyllaDriver   = \"github.com/scylladb/gocql@v1.14.4\" // Replacement for GoCQL\n\n\tgodotenvPackage = []string{\"github.com/joho/godotenv\"}\n\ttemplPackage    = []string{\"github.com/a-h/templ\"}\n)\n\nconst (\n\troot                 = \"/\"\n\tcmdApiPath           = \"cmd/api\"\n\tcmdWebPath           = \"cmd/web\"\n\tinternalServerPath   = \"internal/server\"\n\tinternalDatabasePath = \"internal/database\"\n\tgitHubActionPath     = \".github/workflows\"\n)\n\n// CheckOs checks Operation system and generates MakeFile and `go build` command\n// Based on Project.Unixbase\nfunc (p *Project) CheckOS() {\n\tp.OSCheck = make(map[string]bool)\n\n\tif runtime.GOOS != \"windows\" {\n\t\tp.OSCheck[\"UnixBased\"] = true\n\t}\n\tif runtime.GOOS == \"linux\" {\n\t\tp.OSCheck[\"linux\"] = true\n\t}\n\tif runtime.GOOS == \"darwin\" {\n\t\tp.OSCheck[\"darwin\"] = true\n\t}\n}\n\n// ExitCLI checks if the Project has been exited, and closes\n// out of the CLI if it has\nfunc (p *Project) ExitCLI(tprogram *tea.Program) {\n\tif p.Exit {\n\t\t// logo render here\n\t\tif err := tprogram.ReleaseTerminal(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tos.Exit(1)\n\t}\n}\n\n// createFrameWorkMap adds the current supported\n// Frameworks into a Project's FrameworkMap\nfunc (p *Project) createFrameworkMap() {\n\tp.FrameworkMap[flags.Chi] = Framework{\n\t\tpackageName: chiPackage,\n\t\ttemplater:   framework.ChiTemplates{},\n\t}\n\n\tp.FrameworkMap[flags.StandardLibrary] = Framework{\n\t\tpackageName: []string{},\n\t\ttemplater:   framework.StandardLibTemplate{},\n\t}\n\n\tp.FrameworkMap[flags.Gin] = Framework{\n\t\tpackageName: ginPackage,\n\t\ttemplater:   framework.GinTemplates{},\n\t}\n\n\tp.FrameworkMap[flags.Fiber] = Framework{\n\t\tpackageName: fiberPackage,\n\t\ttemplater:   framework.FiberTemplates{},\n\t}\n\n\tp.FrameworkMap[flags.GorillaMux] = Framework{\n\t\tpackageName: gorillaPackage,\n\t\ttemplater:   framework.GorillaTemplates{},\n\t}\n\n\tp.FrameworkMap[flags.HttpRouter] = Framework{\n\t\tpackageName: routerPackage,\n\t\ttemplater:   framework.RouterTemplates{},\n\t}\n\n\tp.FrameworkMap[flags.Echo] = Framework{\n\t\tpackageName: echoPackage,\n\t\ttemplater:   framework.EchoTemplates{},\n\t}\n}\n\nfunc (p *Project) createDBDriverMap() {\n\tp.DBDriverMap[flags.MySql] = Driver{\n\t\tpackageName: mysqlDriver,\n\t\ttemplater:   dbdriver.MysqlTemplate{},\n\t}\n\tp.DBDriverMap[flags.Postgres] = Driver{\n\t\tpackageName: postgresDriver,\n\t\ttemplater:   dbdriver.PostgresTemplate{},\n\t}\n\tp.DBDriverMap[flags.Sqlite] = Driver{\n\t\tpackageName: sqliteDriver,\n\t\ttemplater:   dbdriver.SqliteTemplate{},\n\t}\n\tp.DBDriverMap[flags.Mongo] = Driver{\n\t\tpackageName: mongoDriver,\n\t\ttemplater:   dbdriver.MongoTemplate{},\n\t}\n\tp.DBDriverMap[flags.Redis] = Driver{\n\t\tpackageName: redisDriver,\n\t\ttemplater:   dbdriver.RedisTemplate{},\n\t}\n\n\tp.DBDriverMap[flags.Scylla] = Driver{\n\t\tpackageName: gocqlDriver,\n\t\ttemplater:   dbdriver.ScyllaTemplate{},\n\t}\n}\n\nfunc (p *Project) createDockerMap() {\n\tp.DockerMap = make(map[flags.Database]Docker)\n\n\tp.DockerMap[flags.MySql] = Docker{\n\t\tpackageName: []string{},\n\t\ttemplater:   docker.MysqlDockerTemplate{},\n\t}\n\tp.DockerMap[flags.Postgres] = Docker{\n\t\tpackageName: []string{},\n\t\ttemplater:   docker.PostgresDockerTemplate{},\n\t}\n\tp.DockerMap[flags.Mongo] = Docker{\n\t\tpackageName: []string{},\n\t\ttemplater:   docker.MongoDockerTemplate{},\n\t}\n\tp.DockerMap[flags.Redis] = Docker{\n\t\tpackageName: []string{},\n\t\ttemplater:   docker.RedisDockerTemplate{},\n\t}\n\tp.DockerMap[flags.Scylla] = Docker{\n\t\tpackageName: []string{},\n\t\ttemplater:   docker.ScyllaDockerTemplate{},\n\t}\n}\n\n// CreateMainFile creates the project folders and files,\n// and writes to them depending on the selected options\nfunc (p *Project) CreateMainFile() error {\n\t// check if AbsolutePath exists\n\tif _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) {\n\t\t// create directory\n\t\tif err := os.Mkdir(p.AbsolutePath, 0o754); err != nil {\n\t\t\tlog.Printf(\"Could not create directory: %v\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Check if user.email is set.\n\tif p.GitOptions.String() != flags.Skip {\n\n\t\temailSet, err := utils.CheckGitConfig(\"user.email\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !emailSet {\n\t\t\tfmt.Println(\"user.email is not set in git config.\")\n\t\t\tfmt.Println(\"Please set up git config before trying again.\")\n\t\t\tpanic(\"\\nGIT CONFIG ISSUE: user.email is not set in git config.\\n\")\n\t\t}\n\t}\n\n\tp.ProjectName = strings.TrimSpace(p.ProjectName)\n\n\t// Create a new directory with the project name\n\tprojectPath := filepath.Join(p.AbsolutePath, utils.GetRootDir(p.ProjectName))\n\tif _, err := os.Stat(projectPath); os.IsNotExist(err) {\n\t\terr := os.MkdirAll(projectPath, 0o751)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error creating root project directory %v\\n\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Define Operating system\n\tp.CheckOS()\n\n\t// Create the map for our program\n\tp.createFrameworkMap()\n\n\t// Create go.mod\n\terr := utils.InitGoMod(p.ProjectName, projectPath)\n\tif err != nil {\n\t\tlog.Printf(\"Could not initialize go.mod in new project %v\\n\", err)\n\t\treturn err\n\t}\n\n\t// Install the correct package for the selected framework\n\tif p.ProjectType != flags.StandardLibrary {\n\t\terr = utils.GoGetPackage(projectPath, p.FrameworkMap[p.ProjectType].packageName)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Could not install go dependency for the chosen framework\")\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Install the correct package for the selected driver\n\tif p.DBDriver != \"none\" {\n\t\tp.createDBDriverMap()\n\t\terr = utils.GoGetPackage(projectPath, p.DBDriverMap[p.DBDriver].packageName)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Could not install go dependency for chosen driver\")\n\t\t\treturn err\n\t\t}\n\n\t\terr = p.CreatePath(internalDatabasePath, projectPath)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error creating path: %s\", internalDatabasePath)\n\t\t\treturn err\n\t\t}\n\n\t\terr = p.CreateFileWithInjection(internalDatabasePath, projectPath, \"database.go\", \"database\")\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error injecting database.go file: %v\", err)\n\t\t\treturn err\n\t\t}\n\n\t\tif p.DBDriver != \"sqlite\" {\n\t\t\terr = p.CreateFileWithInjection(internalDatabasePath, projectPath, \"database_test.go\", \"integration-tests\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error injecting database_test.go file: %v\", err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Create correct docker compose for the selected driver\n\tif p.DBDriver != \"none\" {\n\t\tif p.DBDriver != \"sqlite\" {\n\t\t\tp.createDockerMap()\n\t\t\tp.Docker = p.DBDriver\n\n\t\t\terr = p.CreateFileWithInjection(root, projectPath, \"docker-compose.yml\", \"db-docker\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error injecting docker-compose.yml file: %v\", err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tfmt.Println(\" SQLite doesn't support docker-compose.yml configuration\")\n\t\t}\n\t}\n\n\t// Install the godotenv package\n\terr = utils.GoGetPackage(projectPath, godotenvPackage)\n\tif err != nil {\n\t\tlog.Println(\"Could not install go dependency\")\n\n\t\treturn err\n\t}\n\n\tif p.DBDriver == flags.Scylla {\n\t\treplace := fmt.Sprintf(\"%s=%s\", gocqlDriver[0], scyllaDriver)\n\t\terr = utils.GoModReplace(projectPath, replace)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Could not replace go dependency %v\\n\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = p.CreatePath(cmdApiPath, projectPath)\n\tif err != nil {\n\t\tlog.Printf(\"Error creating path: %s\", projectPath)\n\t\treturn err\n\t}\n\n\terr = p.CreateFileWithInjection(cmdApiPath, projectPath, \"main.go\", \"main\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmakeFile, err := os.Create(filepath.Join(projectPath, \"Makefile\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer makeFile.Close()\n\n\t// inject makefile template\n\tmakeFileTemplate := template.Must(template.New(\"makefile\").Parse(string(framework.MakeTemplate())))\n\terr = makeFileTemplate.Execute(makeFile, p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treadmeFile, err := os.Create(filepath.Join(projectPath, \"README.md\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer readmeFile.Close()\n\n\t// inject readme template\n\treadmeFileTemplate := template.Must(template.New(\"readme\").Parse(string(framework.ReadmeTemplate())))\n\terr = readmeFileTemplate.Execute(readmeFile, p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = p.CreatePath(internalServerPath, projectPath)\n\tif err != nil {\n\t\tlog.Printf(\"Error creating path: %s\", internalServerPath)\n\t\treturn err\n\t}\n\n\tif p.AdvancedOptions[string(flags.React)] {\n\t\t// deselect htmx option automatically since react is selected\n\t\tp.AdvancedOptions[string(flags.Htmx)] = false\n\t\tif err := p.CreateViteReactProject(projectPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set up React project: %w\", err)\n\t\t}\n\n\t\t// if everything went smoothly, remove tailwing flag option\n\t\tp.AdvancedOptions[string(flags.Tailwind)] = false\n\t}\n\n\tif p.AdvancedOptions[string(flags.Tailwind)] {\n\t\t// select htmx option automatically since tailwind is selected\n\t\tp.AdvancedOptions[string(flags.Htmx)] = true\n\n\t\terr = os.MkdirAll(fmt.Sprintf(\"%s/%s/assets/css\", projectPath, cmdWebPath), 0o755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = os.MkdirAll(fmt.Sprintf(\"%s/%s/styles\", projectPath, cmdWebPath), 0o755)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create styles directory: %w\", err)\n\t\t}\n\n\t\tinputCssFile, err := os.Create(fmt.Sprintf(\"%s/%s/styles/input.css\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer inputCssFile.Close()\n\n\t\tinputCssTemplate := advanced.InputCssTemplate()\n\t\terr = os.WriteFile(fmt.Sprintf(\"%s/%s/styles/input.css\", projectPath, cmdWebPath), inputCssTemplate, 0o644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\toutputCssFile, err := os.Create(fmt.Sprintf(\"%s/%s/assets/css/output.css\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer outputCssFile.Close()\n\n\t\toutputCssTemplate := advanced.OutputCssTemplate()\n\t\terr = os.WriteFile(fmt.Sprintf(\"%s/%s/assets/css/output.css\", projectPath, cmdWebPath), outputCssTemplate, 0o644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif p.AdvancedOptions[string(flags.Htmx)] {\n\t\t// create folders and hello world file\n\t\terr = p.CreatePath(cmdWebPath, projectPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thelloTemplFile, err := os.Create(fmt.Sprintf(\"%s/%s/hello.templ\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer helloTemplFile.Close()\n\n\t\t// inject hello.templ template\n\t\thelloTemplTemplate := template.Must(template.New(\"hellotempl\").Parse((string(advanced.HelloTemplTemplate()))))\n\t\terr = helloTemplTemplate.Execute(helloTemplFile, p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tbaseTemplFile, err := os.Create(fmt.Sprintf(\"%s/%s/base.templ\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer baseTemplFile.Close()\n\n\t\tbaseTemplTemplate := template.Must(template.New(\"basetempl\").Parse((string(advanced.BaseTemplTemplate()))))\n\t\terr = baseTemplTemplate.Execute(baseTemplFile, p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = os.MkdirAll(fmt.Sprintf(\"%s/%s/assets/js\", projectPath, cmdWebPath), 0o755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\thtmxMinJsFile, err := os.Create(fmt.Sprintf(\"%s/%s/assets/js/htmx.min.js\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer htmxMinJsFile.Close()\n\n\t\thtmxMinJsTemplate := advanced.HtmxJSTemplate()\n\t\terr = os.WriteFile(fmt.Sprintf(\"%s/%s/assets/js/htmx.min.js\", projectPath, cmdWebPath), htmxMinJsTemplate, 0o644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\thtmxTailwindConfigJsFile, err := os.Create(fmt.Sprintf(\"%s/tailwind.config.js\", projectPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer htmxTailwindConfigJsFile.Close()\n\n\t\thtmxTailwindConfigJsTemplate := advanced.HtmxTailwindConfigJsTemplate()\n\t\terr = os.WriteFile(fmt.Sprintf(\"%s/tailwind.config.js\", projectPath), htmxTailwindConfigJsTemplate, 0o644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tefsFile, err := os.Create(fmt.Sprintf(\"%s/%s/efs.go\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer efsFile.Close()\n\n\t\tefsTemplate := template.Must(template.New(\"efs\").Parse((string(advanced.EfsTemplate()))))\n\t\terr = efsTemplate.Execute(efsFile, p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = utils.GoGetPackage(projectPath, templPackage)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Could not install go dependency\")\n\t\t\treturn err\n\t\t}\n\n\t\thelloGoFile, err := os.Create(fmt.Sprintf(\"%s/%s/hello.go\", projectPath, cmdWebPath))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer efsFile.Close()\n\n\t\tif p.ProjectType == \"fiber\" {\n\t\t\thelloGoTemplate := template.Must(template.New(\"efs\").Parse((string(advanced.HelloFiberGoTemplate()))))\n\t\t\terr = helloGoTemplate.Execute(helloGoFile, p)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = utils.GoGetPackage(projectPath, []string{\"github.com/gofiber/fiber/v2/middleware/adaptor\"})\n\t\t\tif err != nil {\n\t\t\t\tlog.Println(\"Could not install go dependency\")\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\thelloGoTemplate := template.Must(template.New(\"efs\").Parse((string(advanced.HelloGoTemplate()))))\n\t\t\terr = helloGoTemplate.Execute(helloGoFile, p)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tp.CreateHtmxTemplates()\n\t}\n\n\t// Create .github/workflows folder and inject release.yml and go-test.yml\n\tif p.AdvancedOptions[string(flags.GoProjectWorkflow)] {\n\t\terr = p.CreatePath(gitHubActionPath, projectPath)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error creating path: %s\", gitHubActionPath)\n\t\t\treturn err\n\t\t}\n\n\t\terr = p.CreateFileWithInjection(gitHubActionPath, projectPath, \"release.yml\", \"releaser\")\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error injecting release.yml file: %v\", err)\n\t\t\treturn err\n\t\t}\n\n\t\terr = p.CreateFileWithInjection(gitHubActionPath, projectPath, \"go-test.yml\", \"go-test\")\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error injecting go-test.yml file: %v\", err)\n\t\t\treturn err\n\t\t}\n\n\t\terr = p.CreateFileWithInjection(root, projectPath, \".goreleaser.yml\", \"releaser-config\")\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error injecting .goreleaser.yml file: %v\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// if the websocket option is checked, a websocket dependency needs to\n\t// be added to the routes depending on the framework choosen.\n\t// Only fiber uses a different websocket library, the other frameworks\n\t// all work with the same one\n\tif p.AdvancedOptions[string(flags.Websocket)] {\n\t\tp.CreateWebsocketImports(projectPath)\n\t}\n\n\tif p.AdvancedOptions[string(flags.Docker)] {\n\t\tDockerfile, err := os.Create(filepath.Join(projectPath, \"Dockerfile\"))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer Dockerfile.Close()\n\n\t\t// inject Docker template\n\t\tdockerfileTemplate := template.Must(template.New(\"Dockerfile\").Parse(string(advanced.Dockerfile())))\n\t\terr = dockerfileTemplate.Execute(Dockerfile, p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif p.DBDriver == \"none\" || p.DBDriver == \"sqlite\" {\n\n\t\t\tDockercompose, err := os.Create(filepath.Join(projectPath, \"docker-compose.yml\"))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer Dockercompose.Close()\n\n\t\t\t// inject DockerCompose template\n\t\t\tdockerComposeTemplate := template.Must(template.New(\"docker-compose.yml\").Parse(string(advanced.DockerCompose())))\n\t\t\terr = dockerComposeTemplate.Execute(Dockercompose, p)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\terr = p.CreateFileWithInjection(internalServerPath, projectPath, \"routes.go\", \"routes\")\n\tif err != nil {\n\t\tlog.Printf(\"Error injecting routes.go file: %v\", err)\n\t\treturn err\n\t}\n\n\terr = p.CreateFileWithInjection(internalServerPath, projectPath, \"routes_test.go\", \"tests\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = p.CreateFileWithInjection(internalServerPath, projectPath, \"server.go\", \"server\")\n\tif err != nil {\n\t\tlog.Printf(\"Error injecting server.go file: %v\", err)\n\t\treturn err\n\t}\n\n\terr = p.CreateFileWithInjection(root, projectPath, \".env\", \"env\")\n\tif err != nil {\n\t\tlog.Printf(\"Error injecting .env file: %v\", err)\n\t\treturn err\n\t}\n\n\t// Create gitignore\n\tgitignoreFile, err := os.Create(filepath.Join(projectPath, \".gitignore\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer gitignoreFile.Close()\n\n\t// inject gitignore template\n\tgitignoreTemplate := template.Must(template.New(\".gitignore\").Parse(string(framework.GitIgnoreTemplate())))\n\terr = gitignoreTemplate.Execute(gitignoreFile, p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Create .air.toml file\n\tairTomlFile, err := os.Create(filepath.Join(projectPath, \".air.toml\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer airTomlFile.Close()\n\n\t// inject air.toml template\n\tairTomlTemplate := template.Must(template.New(\"airtoml\").Parse(string(framework.AirTomlTemplate())))\n\terr = airTomlTemplate.Execute(airTomlFile, p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = utils.GoTidy(projectPath)\n\tif err != nil {\n\t\tlog.Printf(\"Could not go tidy in new project %v\\n\", err)\n\t\treturn err\n\t}\n\n\terr = utils.GoFmt(projectPath)\n\tif err != nil {\n\t\tlog.Printf(\"Could not gofmt in new project %v\\n\", err)\n\t\treturn err\n\t}\n\n\tif p.GitOptions != flags.Skip {\n\t\tnameSet, err := utils.CheckGitConfig(\"user.name\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !nameSet {\n\t\t\tfmt.Println(\"user.name is not set in git config.\")\n\t\t\tfmt.Println(\"Please set up git config before trying again.\")\n\t\t\tpanic(\"\\nGIT CONFIG ISSUE: user.name is not set in git config.\\n\")\n\t\t}\n\t\t// Initialize git repo\n\t\terr = utils.ExecuteCmd(\"git\", []string{\"init\"}, projectPath)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error initializing git repo: %v\", err)\n\t\t\treturn err\n\t\t}\n\n\t\t// Git add files\n\t\terr = utils.ExecuteCmd(\"git\", []string{\"add\", \".\"}, projectPath)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error adding files to git repo: %v\", err)\n\t\t\treturn err\n\t\t}\n\n\t\tif p.GitOptions == flags.Commit {\n\t\t\t// Git commit files\n\t\t\terr = utils.ExecuteCmd(\"git\", []string{\"commit\", \"-m\", \"Initial commit\"}, projectPath)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error committing files to git repo: %v\", err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreatePath creates the given directory in the projectPath\nfunc (p *Project) CreatePath(pathToCreate string, projectPath string) error {\n\tpath := filepath.Join(projectPath, pathToCreate)\n\tif _, err := os.Stat(path); os.IsNotExist(err) {\n\t\terr := os.MkdirAll(path, 0o751)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error creating directory %v\\n\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CreateFileWithInjection creates the given file at the\n// project path, and injects the appropriate template\nfunc (p *Project) CreateFileWithInjection(pathToCreate string, projectPath string, fileName string, methodName string) error {\n\tcreatedFile, err := os.Create(filepath.Join(projectPath, pathToCreate, fileName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer createdFile.Close()\n\n\tswitch methodName {\n\tcase \"main\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.Main())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"server\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.Server())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"routes\":\n\t\trouteFileBytes := p.FrameworkMap[p.ProjectType].templater.Routes()\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(routeFileBytes)))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"releaser\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(advanced.Releaser())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"go-test\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(advanced.Test())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"releaser-config\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(advanced.ReleaserConfig())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"database\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.DBDriverMap[p.DBDriver].templater.Service())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"db-docker\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.DockerMap[p.Docker].templater.Docker())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"integration-tests\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.DBDriverMap[p.DBDriver].templater.Tests())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"tests\":\n\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.TestHandler())))\n\t\terr = createdTemplate.Execute(createdFile, p)\n\tcase \"env\":\n\t\tif p.DBDriver != \"none\" {\n\n\t\t\tenvBytes := [][]byte{\n\t\t\t\ttpl.GlobalEnvTemplate(),\n\t\t\t\tp.DBDriverMap[p.DBDriver].templater.Env(),\n\t\t\t}\n\t\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(bytes.Join(envBytes, []byte(\"\\n\")))))\n\t\t\terr = createdTemplate.Execute(createdFile, p)\n\n\t\t} else {\n\t\t\tcreatedTemplate := template.Must(template.New(fileName).Parse(string(tpl.GlobalEnvTemplate())))\n\t\t\terr = createdTemplate.Execute(createdFile, p)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *Project) CreateViteReactProject(projectPath string) error {\n\tif err := checkNpmInstalled(); err != nil {\n\t\treturn err\n\t}\n\n\toriginalDir, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current directory: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err := os.Chdir(originalDir); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to change back to original directory: %v\\n\", err)\n\t\t}\n\t}()\n\n\t// change into the project directory to run vite command\n\terr = os.Chdir(projectPath)\n\tif err != nil {\n\t\tfmt.Println(\"failed to change into project directory: %w\", err)\n\t}\n\n\t// the interactive vite command will not work as we can't interact with it\n\tfmt.Println(\"Installing create-vite (using cache if available)...\")\n\tcmd := exec.Command(\"npm\", \"create\", \"vite@latest\", \"frontend\", \"--\",\n\t\t\"--template\", \"react-ts\",\n\t\t\"--prefer-offline\",\n\t\t\"--no-fund\")\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to use create-vite: %w\", err)\n\t}\n\n\tfrontendPath := filepath.Join(projectPath, \"frontend\")\n\tif err := os.MkdirAll(frontendPath, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create frontend directory: %w\", err)\n\t}\n\n\tif err := os.Chdir(frontendPath); err != nil {\n\t\treturn fmt.Errorf(\"failed to change to frontend directory: %w\", err)\n\t}\n\n\tsrcDir := filepath.Join(frontendPath, \"src\")\n\tif err := os.MkdirAll(srcDir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create src directory: %w\", err)\n\t}\n\n\tif err := os.WriteFile(filepath.Join(srcDir, \"App.tsx\"), advanced.ReactAppfile(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write App.tsx template: %w\", err)\n\t}\n\n\t// Create the global `.env` file from the template\n\terr = p.CreateFileWithInjection(\"\", projectPath, \".env\", \"env\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create global .env file: %w\", err)\n\t}\n\n\t// Read from the global `.env` file and create the frontend-specific `.env`\n\tglobalEnvPath := filepath.Join(projectPath, \".env\")\n\tvitePort := \"8080\" // Default fallback\n\n\t// Read the global .env file\n\tif data, err := os.ReadFile(globalEnvPath); err == nil {\n\t\tlines := strings.Split(string(data), \"\\n\")\n\t\tfor _, line := range lines {\n\t\t\tif strings.HasPrefix(line, \"PORT=\") {\n\t\t\t\tvitePort = strings.SplitN(line, \"=\", 2)[1] // Get the backend port value\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Use a template to generate the frontend .env file\n\tfrontendEnvContent := fmt.Sprintf(\"VITE_PORT=%s\\n\", vitePort)\n\tif err := os.WriteFile(filepath.Join(frontendPath, \".env\"), []byte(frontendEnvContent), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to create frontend .env file: %w\", err)\n\t}\n\n\t// Handle Tailwind configuration if selected\n\tif p.AdvancedOptions[string(flags.Tailwind)] {\n\t\tfmt.Println(\"Installing Tailwind dependencies (using cache if available)...\")\n\t\tcmd := exec.Command(\"npm\", \"install\",\n\t\t\t\"--prefer-offline\",\n\t\t\t\"--no-fund\",\n\t\t\t\"tailwindcss@^4\", \"@tailwindcss/vite\")\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Stderr = os.Stderr\n\t\tif err := cmd.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to install Tailwind: %w\", err)\n\t\t}\n\n\t\t// Create the vite + react + Tailwind v4 configuration\n\t\tif err := os.WriteFile(filepath.Join(frontendPath, \"vite.config.ts\"), advanced.ViteTailwindConfigFile(), 0644); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write vite.config.ts: %w\", err)\n\t\t}\n\n\t\tsrcDir := filepath.Join(frontendPath, \"src\")\n\t\tif err := os.MkdirAll(srcDir, 0755); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create src directory: %w\", err)\n\t\t}\n\n\t\terr = os.WriteFile(filepath.Join(srcDir, \"index.css\"), advanced.InputCssTemplateReact(), 0644)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to update index.css: %w\", err)\n\t\t}\n\n\t\tif err := os.WriteFile(filepath.Join(srcDir, \"App.tsx\"), advanced.ReactTailwindAppfile(), 0644); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write App.tsx template: %w\", err)\n\t\t}\n\n\t\tif err := os.Remove(filepath.Join(srcDir, \"App.css\")); err != nil {\n\t\t\t// Don't return error if file doesn't exist\n\t\t\tif !os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to remove App.css: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\t// set to false to not re-do in next step\n\t\tp.AdvancedOptions[string(flags.Tailwind)] = false\n\t}\n\n\treturn nil\n}\n\nfunc (p *Project) CreateHtmxTemplates() {\n\troutesPlaceHolder := \"\"\n\timportsPlaceHolder := \"\"\n\tif p.AdvancedOptions[string(flags.Htmx)] {\n\t\troutesPlaceHolder += string(p.FrameworkMap[p.ProjectType].templater.HtmxTemplRoutes())\n\t\timportsPlaceHolder += string(p.FrameworkMap[p.ProjectType].templater.HtmxTemplImports())\n\t}\n\n\trouteTmpl, err := template.New(\"routes\").Parse(routesPlaceHolder)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\timportTmpl, err := template.New(\"imports\").Parse(importsPlaceHolder)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tvar routeBuffer bytes.Buffer\n\tvar importBuffer bytes.Buffer\n\terr = routeTmpl.Execute(&routeBuffer, p)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\terr = importTmpl.Execute(&importBuffer, p)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tp.AdvancedTemplates.TemplateRoutes = routeBuffer.String()\n\tp.AdvancedTemplates.TemplateImports = importBuffer.String()\n}\n\nfunc (p *Project) CreateWebsocketImports(appDir string) {\n\twebsocketDependency := []string{\"github.com/coder/websocket\"}\n\tif p.ProjectType == flags.Fiber {\n\t\twebsocketDependency = []string{\"github.com/gofiber/contrib/websocket\"}\n\t}\n\n\t// Websockets require a different package depending on what framework is\n\t// choosen. The application calls go mod tidy at the end so we don't\n\t// have to here\n\terr := utils.GoGetPackage(appDir, websocketDependency)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\timportsPlaceHolder := string(p.FrameworkMap[p.ProjectType].templater.WebsocketImports())\n\n\timportTmpl, err := template.New(\"imports\").Parse(importsPlaceHolder)\n\tif err != nil {\n\t\tlog.Fatalf(\"CreateWebsocketImports failed to create template: %v\", err)\n\t}\n\tvar importBuffer bytes.Buffer\n\terr = importTmpl.Execute(&importBuffer, p)\n\tif err != nil {\n\t\tlog.Fatalf(\"CreateWebsocketImports failed write template: %v\", err)\n\t}\n\tnewImports := strings.Join([]string{string(p.AdvancedTemplates.TemplateImports), importBuffer.String()}, \"\\n\")\n\tp.AdvancedTemplates.TemplateImports = newImports\n}\n\nfunc checkNpmInstalled() error {\n\tcmd := exec.Command(\"npm\", \"--version\")\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"npm is not installed: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/root.go",
    "content": "/*\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 = &cobra.Command{\n\tUse:   \"go-blueprint\",\n\tShort: \"A program to spin up a quick Go project using a popular framework\",\n\tLong: `Go Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly. \nIt also gives the option to integrate with one of the more popular Go frameworks!`,\n}\n\nfunc Execute() {\n\terr := rootCmd.Execute()\n\tif err != nil {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc init() {\n\trootCmd.AddCommand(versionCmd)\n\trootCmd.Flags().BoolP(\"toggle\", \"t\", false, \"Help message for toggle\")\n}\n"
  },
  {
    "path": "cmd/steps/steps.go",
    "content": "// Package steps provides utility for creating\n// each step of the CLI\npackage steps\n\nimport \"github.com/melkeydev/go-blueprint/cmd/flags\"\n\n// A StepSchema contains the data that is used\n// for an individual step of the CLI\ntype StepSchema struct {\n\tStepName string // The name of a given step\n\tOptions  []Item // The slice of each option for a given step\n\tHeaders  string // The title displayed at the top of a given step\n\tField    string\n}\n\n// Steps contains a slice of steps\ntype Steps struct {\n\tSteps map[string]StepSchema\n}\n\n// An Item contains the data for each option\n// in a StepSchema.Options\ntype Item struct {\n\tFlag, Title, Desc string\n}\n\n// InitSteps initializes and returns the *Steps to be used in the CLI program\nfunc InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps {\n\tsteps := &Steps{\n\t\tmap[string]StepSchema{\n\t\t\t\"framework\": {\n\t\t\t\tStepName: \"Go Project Framework\",\n\t\t\t\tOptions: []Item{\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Standard-library\",\n\t\t\t\t\t\tDesc:  \"The built-in Go standard library HTTP package\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Chi\",\n\t\t\t\t\t\tDesc:  \"A lightweight, idiomatic and composable router for building Go HTTP services\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Gin\",\n\t\t\t\t\t\tDesc:  \"Features a martini-like API with performance that is up to 40 times faster thanks to httprouter\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Fiber\",\n\t\t\t\t\t\tDesc:  \"An Express inspired web framework built on top of Fasthttp\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Gorilla/Mux\",\n\t\t\t\t\t\tDesc:  \"Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"HttpRouter\",\n\t\t\t\t\t\tDesc:  \"HttpRouter is a lightweight high performance HTTP request router for Go\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Echo\",\n\t\t\t\t\t\tDesc:  \"High performance, extensible, minimalist Go web framework\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tHeaders: \"What framework do you want to use in your Go project?\",\n\t\t\t\tField:   projectType.String(),\n\t\t\t},\n\t\t\t\"driver\": {\n\t\t\t\tStepName: \"Go Project Database Driver\",\n\t\t\t\tOptions: []Item{\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Mysql\",\n\t\t\t\t\t\tDesc:  \"MySQL-Driver for Go's database/sql package\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Postgres\",\n\t\t\t\t\t\tDesc:  \"Go postgres driver for Go's database/sql package\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Sqlite\",\n\t\t\t\t\t\tDesc:  \"sqlite3 driver conforming to the built-in database/sql interface\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Mongo\",\n\t\t\t\t\t\tDesc:  \"The MongoDB supported driver for Go.\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Redis\",\n\t\t\t\t\t\tDesc:  \"Redis driver for Go.\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Scylla\",\n\t\t\t\t\t\tDesc:  \"ScyllaDB Enhanced driver from GoCQL.\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"None\",\n\t\t\t\t\t\tDesc:  \"Choose this option if you don't wish to install a specific database driver.\"},\n\t\t\t\t},\n\t\t\t\tHeaders: \"What database driver do you want to use in your Go project?\",\n\t\t\t\tField:   databaseType.String(),\n\t\t\t},\n\t\t\t\"advanced\": {\n\t\t\t\tStepName: \"Advanced Features\",\n\t\t\t\tHeaders:  \"Which advanced features do you want?\",\n\t\t\t\tOptions: []Item{\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"React\",\n\t\t\t\t\t\tTitle: \"React\",\n\t\t\t\t\t\tDesc:  \"Use Vite to spin up a React project in TypeScript. This disables selecting HTMX/Templ\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"Htmx\",\n\t\t\t\t\t\tTitle: \"HTMX/Templ\",\n\t\t\t\t\t\tDesc:  \"Add starter HTMX and Templ files. This disables selecting React\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"GitHubAction\",\n\t\t\t\t\t\tTitle: \"Go Project Workflow\",\n\t\t\t\t\t\tDesc:  \"Workflow templates for testing, cross-compiling and releasing Go projects\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"Websocket\",\n\t\t\t\t\t\tTitle: \"Websocket endpoint\",\n\t\t\t\t\t\tDesc:  \"Add a websocket endpoint\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"Tailwind\",\n\t\t\t\t\t\tTitle: \"TailwindCSS\",\n\t\t\t\t\t\tDesc:  \"A utility-first CSS framework (selecting this will automatically add HTMX unless React is specified)\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tFlag:  \"Docker\",\n\t\t\t\t\t\tTitle: \"Docker\",\n\t\t\t\t\t\tDesc:  \"Dockerfile and docker-compose generic configuration for go project\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"git\": {\n\t\t\t\tStepName: \"Git Repository\",\n\t\t\t\tHeaders:  \"Which git option would you like to select for your project?\",\n\t\t\t\tOptions: []Item{\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Commit\",\n\t\t\t\t\t\tDesc:  \"Initialize a new git repository and commit all the changes\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Stage\",\n\t\t\t\t\t\tDesc:  \"Initialize a new git repository but only stage the changes\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTitle: \"Skip\",\n\t\t\t\t\t\tDesc:  \"Proceed without initializing a git repository\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn steps\n}\n"
  },
  {
    "path": "cmd/template/advanced/docker.go",
    "content": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/docker/dockerfile.tmpl\nvar dockerfileTemplate []byte\n\n//go:embed files/docker/docker_compose.yml.tmpl\nvar dockerComposeTemplate []byte\n\nfunc Dockerfile() []byte {\n\treturn dockerfileTemplate\n}\n\nfunc DockerCompose() []byte {\n\treturn dockerComposeTemplate\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/docker/docker_compose.yml.tmpl",
    "content": "services:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n{{- if and (.AdvancedOptions.docker) (eq .DBDriver \"sqlite\") }}\n      BLUEPRINT_DB_URL: ${BLUEPRINT_DB_URL}\n    volumes:\n      - sqlite_bp:/app/db\n{{- end }}\n{{- if .AdvancedOptions.react }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    ports:\n      - 5173:5173\n    depends_on:\n      - app\n{{- end }}\n\n{{- if and (.AdvancedOptions.docker) (eq .DBDriver \"sqlite\") }}\nvolumes:\n  sqlite_bp:\n{{- end }}\n"
  },
  {
    "path": "cmd/template/advanced/files/docker/dockerfile.tmpl",
    "content": "FROM golang:1.24.4-alpine AS build\n{{- if or (.AdvancedOptions.tailwind) (eq .DBDriver \"sqlite\") }}\nRUN apk add --no-cache{{- if .AdvancedOptions.tailwind }} curl libstdc++ libgcc{{ end }}{{- if (eq .DBDriver \"sqlite\") }} alpine-sdk{{ end }}\n{{- end }}\n\nWORKDIR /app\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\n{{- if or .AdvancedOptions.htmx .AdvancedOptions.tailwind }}\nRUN go install github.com/a-h/templ/cmd/templ@latest && \\\n    templ generate{{- if .AdvancedOptions.tailwind}} && \\{{- end}}\n{{- end}}\n\n{{- if .AdvancedOptions.tailwind}}\n    curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64-musl -o tailwindcss && \\\n    chmod +x tailwindcss && \\\n    ./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css\n{{- end }}\n\nRUN {{ if (eq .DBDriver \"sqlite\") }}CGO_ENABLED=1 GOOS=linux {{ end }}go build -o main cmd/api/main.go\n\nFROM alpine:3.20.1 AS prod\nWORKDIR /app\nCOPY --from=build /app/main /app/main\nEXPOSE ${PORT}\nCMD [\"./main\"]\n\n{{ if .AdvancedOptions.react}}\nFROM node:20 AS frontend_builder\nWORKDIR /frontend\n\nCOPY frontend/package*.json ./\nRUN npm install\nCOPY frontend/. .\nRUN npm run build\n\nFROM node:23-slim AS frontend\nRUN npm install -g serve\nCOPY --from=frontend_builder /frontend/dist /app/dist\nEXPOSE 5173\nCMD [\"serve\", \"-s\", \"/app/dist\", \"-l\", \"5173\"]\n{{- end}}\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/base.templ.tmpl",
    "content": "package web\n\ntempl Base() {\n\t<!DOCTYPE html>\n\t<html lang=\"en\" {{if .AdvancedOptions.tailwind}}class=\"h-screen\"{{end}}>\n\t\t<head>\n\t\t\t<meta charset=\"utf-8\"/>\n\t\t\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n\t\t\t<title>Go Blueprint Hello</title>\n\t\t\t<link href=\"assets/css/output.css\" rel=\"stylesheet\"/>\n\t\t\t<script src=\"assets/js/htmx.min.js\"></script>\n\t\t</head>\n\t\t<body {{if .AdvancedOptions.tailwind}}class=\"bg-gray-100\"{{end}}>\n\t\t\t<main {{if .AdvancedOptions.tailwind}}class=\"max-w-sm mx-auto p-4\"{{end}}>\n\t\t\t\t{ children... }\n\t\t\t</main>\n\t\t</body>\n\t</html>\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/efs.go.tmpl",
    "content": "package web\n\nimport \"embed\"\n\n//go:embed \"assets\"\nvar Files embed.FS\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/hello.go.tmpl",
    "content": "package web\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc HelloWebHandler(w http.ResponseWriter, r *http.Request) {\n\terr := r.ParseForm()\n\tif err != nil {\n\t\thttp.Error(w, \"Bad Request\", http.StatusBadRequest)\n\t}\n\n\tname := r.FormValue(\"name\")\n\tcomponent := HelloPost(name)\n\terr = component.Render(r.Context(), w)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tlog.Fatalf(\"Error rendering in HelloWebHandler: %e\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/hello.templ.tmpl",
    "content": "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<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\"/>\n\t\t\t<button type=\"submit\" {{if .AdvancedOptions.tailwind}}class=\"bg-orange-500 hover:bg-orange-700 text-white py-2 px-4 rounded\"{{end}}>Submit</button>\n\t\t</form>\n\t\t<div id=\"hello-container\"></div>\n\t}\n}\n\ntempl HelloPost(name string) {\n\t<div {{if .AdvancedOptions.tailwind}}class=\"bg-green-100 p-4 shadow-md rounded-lg mt-6\"{{end}}>\n\t\t<p>Hello, { name }</p>\n\t</div>\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/hello_fiber.go.tmpl",
    "content": "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 {\n\t// Parse form data\n\tif err := c.BodyParser(c); err != nil {\n\t\tinnerErr := c.Status(fiber.StatusBadRequest).SendString(\"Bad Request\")\n\t\tif innerErr != nil {\n\t\t\tlog.Fatalf(\"Could not send error in HelloWebHandler: %e\", innerErr)\n\t\t}\n\t}\n\n\t// Get the name from the form data\n\tname := c.FormValue(\"name\")\n\n\t// Render the component\n\tcomponent := HelloPost(name)\n\tbuf := new(bytes.Buffer)\n\terr := component.Render(c.Context(), buf)\n\tif err != nil {\n\t\terrorString := fmt.Sprintf(\"Error rendering in HelloWebHandler: %e\", err)\n\t\tinnerErr := c.Status(fiber.StatusBadRequest).SendString(errorString)\n\t\tif innerErr != nil {\n\t\t\tlog.Fatalf(\"Could not send error in HelloWebHandler: %e\", innerErr)\n\t\t}\n\t\tlog.Fatalf(\"%s\", errorString)\n\t}\n\n\t// Send the response\n\terr = c.Status(fiber.StatusOK).SendString(buf.String())\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not send OK in HelloWebHandler: %e\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/htmx.min.js.tmpl",
    "content": "var htmx = (function () {\n  \"use strict\";\n  const Q = {\n    onLoad: null,\n    process: null,\n    on: null,\n    off: null,\n    trigger: null,\n    ajax: null,\n    find: null,\n    findAll: null,\n    closest: null,\n    values: function (e, t) {\n      const n = dn(e, t || \"post\");\n      return n.values;\n    },\n    remove: null,\n    addClass: null,\n    removeClass: null,\n    toggleClass: null,\n    takeClass: null,\n    swap: null,\n    defineExtension: null,\n    removeExtension: null,\n    logAll: null,\n    logNone: null,\n    logger: null,\n    config: {\n      historyEnabled: true,\n      historyCacheSize: 10,\n      refreshOnHistoryMiss: false,\n      defaultSwapStyle: \"innerHTML\",\n      defaultSwapDelay: 0,\n      defaultSettleDelay: 20,\n      includeIndicatorStyles: true,\n      indicatorClass: \"htmx-indicator\",\n      requestClass: \"htmx-request\",\n      addedClass: \"htmx-added\",\n      settlingClass: \"htmx-settling\",\n      swappingClass: \"htmx-swapping\",\n      allowEval: true,\n      allowScriptTags: true,\n      inlineScriptNonce: \"\",\n      inlineStyleNonce: \"\",\n      attributesToSettle: [\"class\", \"style\", \"width\", \"height\"],\n      withCredentials: false,\n      timeout: 0,\n      wsReconnectDelay: \"full-jitter\",\n      wsBinaryType: \"blob\",\n      disableSelector: \"[hx-disable], [data-hx-disable]\",\n      scrollBehavior: \"instant\",\n      defaultFocusScroll: false,\n      getCacheBusterParam: false,\n      globalViewTransitions: false,\n      methodsThatUseUrlParams: [\"get\", \"delete\"],\n      selfRequestsOnly: true,\n      ignoreTitle: false,\n      scrollIntoViewOnBoost: true,\n      triggerSpecsCache: null,\n      disableInheritance: false,\n      responseHandling: [\n        { code: \"204\", swap: false },\n        { code: \"[23]..\", swap: true },\n        { code: \"[45]..\", swap: false, error: true },\n      ],\n      allowNestedOobSwaps: true,\n      historyRestoreAsHxRequest: true,\n    },\n    parseInterval: null,\n    location: location,\n    _: null,\n    version: \"2.0.6\",\n  };\n  Q.onLoad = j;\n  Q.process = Ft;\n  Q.on = xe;\n  Q.off = be;\n  Q.trigger = ae;\n  Q.ajax = Ln;\n  Q.find = f;\n  Q.findAll = x;\n  Q.closest = g;\n  Q.remove = z;\n  Q.addClass = K;\n  Q.removeClass = G;\n  Q.toggleClass = W;\n  Q.takeClass = Z;\n  Q.swap = $e;\n  Q.defineExtension = zn;\n  Q.removeExtension = $n;\n  Q.logAll = V;\n  Q.logNone = _;\n  Q.parseInterval = d;\n  Q._ = e;\n  const n = {\n    addTriggerHandler: St,\n    bodyContains: se,\n    canAccessLocalStorage: B,\n    findThisElement: Se,\n    filterValues: yn,\n    swap: $e,\n    hasAttribute: s,\n    getAttributeValue: a,\n    getClosestAttributeValue: ne,\n    getClosestMatch: q,\n    getExpressionVars: Tn,\n    getHeaders: mn,\n    getInputValues: dn,\n    getInternalData: oe,\n    getSwapSpecification: bn,\n    getTriggerSpecs: st,\n    getTarget: Ee,\n    makeFragment: P,\n    mergeObjects: le,\n    makeSettleInfo: Sn,\n    oobSwap: He,\n    querySelectorExt: ue,\n    settleImmediately: Yt,\n    shouldCancel: ht,\n    triggerEvent: ae,\n    triggerErrorEvent: fe,\n    withExtensions: jt,\n  };\n  const de = [\"get\", \"post\", \"put\", \"delete\", \"patch\"];\n  const T = de\n    .map(function (e) {\n      return \"[hx-\" + e + \"], [data-hx-\" + e + \"]\";\n    })\n    .join(\", \");\n  function d(e) {\n    if (e == undefined) {\n      return undefined;\n    }\n    let t = NaN;\n    if (e.slice(-2) == \"ms\") {\n      t = parseFloat(e.slice(0, -2));\n    } else if (e.slice(-1) == \"s\") {\n      t = parseFloat(e.slice(0, -1)) * 1e3;\n    } else if (e.slice(-1) == \"m\") {\n      t = parseFloat(e.slice(0, -1)) * 1e3 * 60;\n    } else {\n      t = parseFloat(e);\n    }\n    return isNaN(t) ? undefined : t;\n  }\n  function ee(e, t) {\n    return e instanceof Element && e.getAttribute(t);\n  }\n  function s(e, t) {\n    return (\n      !!e.hasAttribute && (e.hasAttribute(t) || e.hasAttribute(\"data-\" + t))\n    );\n  }\n  function a(e, t) {\n    return ee(e, t) || ee(e, \"data-\" + t);\n  }\n  function u(e) {\n    const t = e.parentElement;\n    if (!t && e.parentNode instanceof ShadowRoot) return e.parentNode;\n    return t;\n  }\n  function te() {\n    return document;\n  }\n  function y(e, t) {\n    return e.getRootNode ? e.getRootNode({ composed: t }) : te();\n  }\n  function q(e, t) {\n    while (e && !t(e)) {\n      e = u(e);\n    }\n    return e || null;\n  }\n  function o(e, t, n) {\n    const r = a(t, n);\n    const o = a(t, \"hx-disinherit\");\n    var i = a(t, \"hx-inherit\");\n    if (e !== t) {\n      if (Q.config.disableInheritance) {\n        if (i && (i === \"*\" || i.split(\" \").indexOf(n) >= 0)) {\n          return r;\n        } else {\n          return null;\n        }\n      }\n      if (o && (o === \"*\" || o.split(\" \").indexOf(n) >= 0)) {\n        return \"unset\";\n      }\n    }\n    return r;\n  }\n  function ne(t, n) {\n    let r = null;\n    q(t, function (e) {\n      return !!(r = o(t, ce(e), n));\n    });\n    if (r !== \"unset\") {\n      return r;\n    }\n  }\n  function h(e, t) {\n    return e instanceof Element && e.matches(t);\n  }\n  function A(e) {\n    const t = /<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i;\n    const n = t.exec(e);\n    if (n) {\n      return n[1].toLowerCase();\n    } else {\n      return \"\";\n    }\n  }\n  function L(e) {\n    const t = new DOMParser();\n    return t.parseFromString(e, \"text/html\");\n  }\n  function N(e, t) {\n    while (t.childNodes.length > 0) {\n      e.append(t.childNodes[0]);\n    }\n  }\n  function r(e) {\n    const t = te().createElement(\"script\");\n    ie(e.attributes, function (e) {\n      t.setAttribute(e.name, e.value);\n    });\n    t.textContent = e.textContent;\n    t.async = false;\n    if (Q.config.inlineScriptNonce) {\n      t.nonce = Q.config.inlineScriptNonce;\n    }\n    return t;\n  }\n  function i(e) {\n    return (\n      e.matches(\"script\") &&\n      (e.type === \"text/javascript\" || e.type === \"module\" || e.type === \"\")\n    );\n  }\n  function I(e) {\n    Array.from(e.querySelectorAll(\"script\")).forEach((e) => {\n      if (i(e)) {\n        const t = r(e);\n        const n = e.parentNode;\n        try {\n          n.insertBefore(t, e);\n        } catch (e) {\n          R(e);\n        } finally {\n          e.remove();\n        }\n      }\n    });\n  }\n  function P(e) {\n    const t = e.replace(/<head(\\s[^>]*)?>[\\s\\S]*?<\\/head>/i, \"\");\n    const n = A(t);\n    let r;\n    if (n === \"html\") {\n      r = new DocumentFragment();\n      const i = L(e);\n      N(r, i.body);\n      r.title = i.title;\n    } else if (n === \"body\") {\n      r = new DocumentFragment();\n      const i = L(t);\n      N(r, i.body);\n      r.title = i.title;\n    } else {\n      const i = L(\n        '<body><template class=\"internal-htmx-wrapper\">' +\n          t +\n          \"</template></body>\",\n      );\n      r = i.querySelector(\"template\").content;\n      r.title = i.title;\n      var o = r.querySelector(\"title\");\n      if (o && o.parentNode === r) {\n        o.remove();\n        r.title = o.innerText;\n      }\n    }\n    if (r) {\n      if (Q.config.allowScriptTags) {\n        I(r);\n      } else {\n        r.querySelectorAll(\"script\").forEach((e) => e.remove());\n      }\n    }\n    return r;\n  }\n  function re(e) {\n    if (e) {\n      e();\n    }\n  }\n  function t(e, t) {\n    return Object.prototype.toString.call(e) === \"[object \" + t + \"]\";\n  }\n  function D(e) {\n    return typeof e === \"function\";\n  }\n  function k(e) {\n    return t(e, \"Object\");\n  }\n  function oe(e) {\n    const t = \"htmx-internal-data\";\n    let n = e[t];\n    if (!n) {\n      n = e[t] = {};\n    }\n    return n;\n  }\n  function M(t) {\n    const n = [];\n    if (t) {\n      for (let e = 0; e < t.length; e++) {\n        n.push(t[e]);\n      }\n    }\n    return n;\n  }\n  function ie(t, n) {\n    if (t) {\n      for (let e = 0; e < t.length; e++) {\n        n(t[e]);\n      }\n    }\n  }\n  function F(e) {\n    const t = e.getBoundingClientRect();\n    const n = t.top;\n    const r = t.bottom;\n    return n < window.innerHeight && r >= 0;\n  }\n  function se(e) {\n    return e.getRootNode({ composed: true }) === document;\n  }\n  function X(e) {\n    return e.trim().split(/\\s+/);\n  }\n  function le(e, t) {\n    for (const n in t) {\n      if (t.hasOwnProperty(n)) {\n        e[n] = t[n];\n      }\n    }\n    return e;\n  }\n  function v(e) {\n    try {\n      return JSON.parse(e);\n    } catch (e) {\n      R(e);\n      return null;\n    }\n  }\n  function B() {\n    const e = \"htmx:sessionStorageTest\";\n    try {\n      sessionStorage.setItem(e, e);\n      sessionStorage.removeItem(e);\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n  function U(e) {\n    const t = new URL(e, \"http://x\");\n    if (t) {\n      e = t.pathname + t.search;\n    }\n    if (e != \"/\") {\n      e = e.replace(/\\/+$/, \"\");\n    }\n    return e;\n  }\n  function e(e) {\n    return On(te().body, function () {\n      return eval(e);\n    });\n  }\n  function j(t) {\n    const e = Q.on(\"htmx:load\", function (e) {\n      t(e.detail.elt);\n    });\n    return e;\n  }\n  function V() {\n    Q.logger = function (e, t, n) {\n      if (console) {\n        console.log(t, e, n);\n      }\n    };\n  }\n  function _() {\n    Q.logger = null;\n  }\n  function f(e, t) {\n    if (typeof e !== \"string\") {\n      return e.querySelector(t);\n    } else {\n      return f(te(), e);\n    }\n  }\n  function x(e, t) {\n    if (typeof e !== \"string\") {\n      return e.querySelectorAll(t);\n    } else {\n      return x(te(), e);\n    }\n  }\n  function b() {\n    return window;\n  }\n  function z(e, t) {\n    e = w(e);\n    if (t) {\n      b().setTimeout(function () {\n        z(e);\n        e = null;\n      }, t);\n    } else {\n      u(e).removeChild(e);\n    }\n  }\n  function ce(e) {\n    return e instanceof Element ? e : null;\n  }\n  function $(e) {\n    return e instanceof HTMLElement ? e : null;\n  }\n  function J(e) {\n    return typeof e === \"string\" ? e : null;\n  }\n  function p(e) {\n    return e instanceof Element ||\n      e instanceof Document ||\n      e instanceof DocumentFragment\n      ? e\n      : null;\n  }\n  function K(e, t, n) {\n    e = ce(w(e));\n    if (!e) {\n      return;\n    }\n    if (n) {\n      b().setTimeout(function () {\n        K(e, t);\n        e = null;\n      }, n);\n    } else {\n      e.classList && e.classList.add(t);\n    }\n  }\n  function G(e, t, n) {\n    let r = ce(w(e));\n    if (!r) {\n      return;\n    }\n    if (n) {\n      b().setTimeout(function () {\n        G(r, t);\n        r = null;\n      }, n);\n    } else {\n      if (r.classList) {\n        r.classList.remove(t);\n        if (r.classList.length === 0) {\n          r.removeAttribute(\"class\");\n        }\n      }\n    }\n  }\n  function W(e, t) {\n    e = w(e);\n    e.classList.toggle(t);\n  }\n  function Z(e, t) {\n    e = w(e);\n    ie(e.parentElement.children, function (e) {\n      G(e, t);\n    });\n    K(ce(e), t);\n  }\n  function g(e, t) {\n    e = ce(w(e));\n    if (e) {\n      return e.closest(t);\n    }\n    return null;\n  }\n  function l(e, t) {\n    return e.substring(0, t.length) === t;\n  }\n  function Y(e, t) {\n    return e.substring(e.length - t.length) === t;\n  }\n  function pe(e) {\n    const t = e.trim();\n    if (l(t, \"<\") && Y(t, \"/>\")) {\n      return t.substring(1, t.length - 2);\n    } else {\n      return t;\n    }\n  }\n  function m(t, r, n) {\n    if (r.indexOf(\"global \") === 0) {\n      return m(t, r.slice(7), true);\n    }\n    t = w(t);\n    const o = [];\n    {\n      let t = 0;\n      let n = 0;\n      for (let e = 0; e < r.length; e++) {\n        const l = r[e];\n        if (l === \",\" && t === 0) {\n          o.push(r.substring(n, e));\n          n = e + 1;\n          continue;\n        }\n        if (l === \"<\") {\n          t++;\n        } else if (l === \"/\" && e < r.length - 1 && r[e + 1] === \">\") {\n          t--;\n        }\n      }\n      if (n < r.length) {\n        o.push(r.substring(n));\n      }\n    }\n    const i = [];\n    const s = [];\n    while (o.length > 0) {\n      const r = pe(o.shift());\n      let e;\n      if (r.indexOf(\"closest \") === 0) {\n        e = g(ce(t), pe(r.slice(8)));\n      } else if (r.indexOf(\"find \") === 0) {\n        e = f(p(t), pe(r.slice(5)));\n      } else if (r === \"next\" || r === \"nextElementSibling\") {\n        e = ce(t).nextElementSibling;\n      } else if (r.indexOf(\"next \") === 0) {\n        e = ge(t, pe(r.slice(5)), !!n);\n      } else if (r === \"previous\" || r === \"previousElementSibling\") {\n        e = ce(t).previousElementSibling;\n      } else if (r.indexOf(\"previous \") === 0) {\n        e = me(t, pe(r.slice(9)), !!n);\n      } else if (r === \"document\") {\n        e = document;\n      } else if (r === \"window\") {\n        e = window;\n      } else if (r === \"body\") {\n        e = document.body;\n      } else if (r === \"root\") {\n        e = y(t, !!n);\n      } else if (r === \"host\") {\n        e = t.getRootNode().host;\n      } else {\n        s.push(r);\n      }\n      if (e) {\n        i.push(e);\n      }\n    }\n    if (s.length > 0) {\n      const e = s.join(\",\");\n      const c = p(y(t, !!n));\n      i.push(...M(c.querySelectorAll(e)));\n    }\n    return i;\n  }\n  var ge = function (t, e, n) {\n    const r = p(y(t, n)).querySelectorAll(e);\n    for (let e = 0; e < r.length; e++) {\n      const o = r[e];\n      if (o.compareDocumentPosition(t) === Node.DOCUMENT_POSITION_PRECEDING) {\n        return o;\n      }\n    }\n  };\n  var me = function (t, e, n) {\n    const r = p(y(t, n)).querySelectorAll(e);\n    for (let e = r.length - 1; e >= 0; e--) {\n      const o = r[e];\n      if (o.compareDocumentPosition(t) === Node.DOCUMENT_POSITION_FOLLOWING) {\n        return o;\n      }\n    }\n  };\n  function ue(e, t) {\n    if (typeof e !== \"string\") {\n      return m(e, t)[0];\n    } else {\n      return m(te().body, e)[0];\n    }\n  }\n  function w(e, t) {\n    if (typeof e === \"string\") {\n      return f(p(t) || document, e);\n    } else {\n      return e;\n    }\n  }\n  function ye(e, t, n, r) {\n    if (D(t)) {\n      return { target: te().body, event: J(e), listener: t, options: n };\n    } else {\n      return { target: w(e), event: J(t), listener: n, options: r };\n    }\n  }\n  function xe(t, n, r, o) {\n    Gn(function () {\n      const e = ye(t, n, r, o);\n      e.target.addEventListener(e.event, e.listener, e.options);\n    });\n    const e = D(n);\n    return e ? n : r;\n  }\n  function be(t, n, r) {\n    Gn(function () {\n      const e = ye(t, n, r);\n      e.target.removeEventListener(e.event, e.listener);\n    });\n    return D(n) ? n : r;\n  }\n  const ve = te().createElement(\"output\");\n  function we(t, n) {\n    const e = ne(t, n);\n    if (e) {\n      if (e === \"this\") {\n        return [Se(t, n)];\n      } else {\n        const r = m(t, e);\n        const o = /(^|,)(\\s*)inherit(\\s*)($|,)/.test(e);\n        if (o) {\n          const i = ce(\n            q(t, function (e) {\n              return e !== t && s(ce(e), n);\n            }),\n          );\n          if (i) {\n            r.push(...we(i, n));\n          }\n        }\n        if (r.length === 0) {\n          R('The selector \"' + e + '\" on ' + n + \" returned no matches!\");\n          return [ve];\n        } else {\n          return r;\n        }\n      }\n    }\n  }\n  function Se(e, t) {\n    return ce(\n      q(e, function (e) {\n        return a(ce(e), t) != null;\n      }),\n    );\n  }\n  function Ee(e) {\n    const t = ne(e, \"hx-target\");\n    if (t) {\n      if (t === \"this\") {\n        return Se(e, \"hx-target\");\n      } else {\n        return ue(e, t);\n      }\n    } else {\n      const n = oe(e);\n      if (n.boosted) {\n        return te().body;\n      } else {\n        return e;\n      }\n    }\n  }\n  function Ce(e) {\n    return Q.config.attributesToSettle.includes(e);\n  }\n  function Oe(t, n) {\n    ie(t.attributes, function (e) {\n      if (!n.hasAttribute(e.name) && Ce(e.name)) {\n        t.removeAttribute(e.name);\n      }\n    });\n    ie(n.attributes, function (e) {\n      if (Ce(e.name)) {\n        t.setAttribute(e.name, e.value);\n      }\n    });\n  }\n  function Re(t, e) {\n    const n = Jn(e);\n    for (let e = 0; e < n.length; e++) {\n      const r = n[e];\n      try {\n        if (r.isInlineSwap(t)) {\n          return true;\n        }\n      } catch (e) {\n        R(e);\n      }\n    }\n    return t === \"outerHTML\";\n  }\n  function He(e, o, i, t) {\n    t = t || te();\n    let n = \"#\" + CSS.escape(ee(o, \"id\"));\n    let s = \"outerHTML\";\n    if (e === \"true\") {\n    } else if (e.indexOf(\":\") > 0) {\n      s = e.substring(0, e.indexOf(\":\"));\n      n = e.substring(e.indexOf(\":\") + 1);\n    } else {\n      s = e;\n    }\n    o.removeAttribute(\"hx-swap-oob\");\n    o.removeAttribute(\"data-hx-swap-oob\");\n    const r = m(t, n, false);\n    if (r.length) {\n      ie(r, function (e) {\n        let t;\n        const n = o.cloneNode(true);\n        t = te().createDocumentFragment();\n        t.appendChild(n);\n        if (!Re(s, e)) {\n          t = p(n);\n        }\n        const r = { shouldSwap: true, target: e, fragment: t };\n        if (!ae(e, \"htmx:oobBeforeSwap\", r)) return;\n        e = r.target;\n        if (r.shouldSwap) {\n          qe(t);\n          _e(s, e, e, t, i);\n          Te();\n        }\n        ie(i.elts, function (e) {\n          ae(e, \"htmx:oobAfterSwap\", r);\n        });\n      });\n      o.parentNode.removeChild(o);\n    } else {\n      o.parentNode.removeChild(o);\n      fe(te().body, \"htmx:oobErrorNoTarget\", { content: o });\n    }\n    return e;\n  }\n  function Te() {\n    const e = f(\"#--htmx-preserve-pantry--\");\n    if (e) {\n      for (const t of [...e.children]) {\n        const n = f(\"#\" + t.id);\n        n.parentNode.moveBefore(t, n);\n        n.remove();\n      }\n      e.remove();\n    }\n  }\n  function qe(e) {\n    ie(x(e, \"[hx-preserve], [data-hx-preserve]\"), function (e) {\n      const t = a(e, \"id\");\n      const n = te().getElementById(t);\n      if (n != null) {\n        if (e.moveBefore) {\n          let e = f(\"#--htmx-preserve-pantry--\");\n          if (e == null) {\n            te().body.insertAdjacentHTML(\n              \"afterend\",\n              \"<div id='--htmx-preserve-pantry--'></div>\",\n            );\n            e = f(\"#--htmx-preserve-pantry--\");\n          }\n          e.moveBefore(n, null);\n        } else {\n          e.parentNode.replaceChild(n, e);\n        }\n      }\n    });\n  }\n  function Ae(l, e, c) {\n    ie(e.querySelectorAll(\"[id]\"), function (t) {\n      const n = ee(t, \"id\");\n      if (n && n.length > 0) {\n        const r = n.replace(\"'\", \"\\\\'\");\n        const o = t.tagName.replace(\":\", \"\\\\:\");\n        const e = p(l);\n        const i = e && e.querySelector(o + \"[id='\" + r + \"']\");\n        if (i && i !== e) {\n          const s = t.cloneNode();\n          Oe(t, i);\n          c.tasks.push(function () {\n            Oe(t, s);\n          });\n        }\n      }\n    });\n  }\n  function Le(e) {\n    return function () {\n      G(e, Q.config.addedClass);\n      Ft(ce(e));\n      Ne(p(e));\n      ae(e, \"htmx:load\");\n    };\n  }\n  function Ne(e) {\n    const t = \"[autofocus]\";\n    const n = $(h(e, t) ? e : e.querySelector(t));\n    if (n != null) {\n      n.focus();\n    }\n  }\n  function c(e, t, n, r) {\n    Ae(e, n, r);\n    while (n.childNodes.length > 0) {\n      const o = n.firstChild;\n      K(ce(o), Q.config.addedClass);\n      e.insertBefore(o, t);\n      if (o.nodeType !== Node.TEXT_NODE && o.nodeType !== Node.COMMENT_NODE) {\n        r.tasks.push(Le(o));\n      }\n    }\n  }\n  function Ie(e, t) {\n    let n = 0;\n    while (n < e.length) {\n      t = ((t << 5) - t + e.charCodeAt(n++)) | 0;\n    }\n    return t;\n  }\n  function Pe(t) {\n    let n = 0;\n    for (let e = 0; e < t.attributes.length; e++) {\n      const r = t.attributes[e];\n      if (r.value) {\n        n = Ie(r.name, n);\n        n = Ie(r.value, n);\n      }\n    }\n    return n;\n  }\n  function De(t) {\n    const n = oe(t);\n    if (n.onHandlers) {\n      for (let e = 0; e < n.onHandlers.length; e++) {\n        const r = n.onHandlers[e];\n        be(t, r.event, r.listener);\n      }\n      delete n.onHandlers;\n    }\n  }\n  function ke(e) {\n    const t = oe(e);\n    if (t.timeout) {\n      clearTimeout(t.timeout);\n    }\n    if (t.listenerInfos) {\n      ie(t.listenerInfos, function (e) {\n        if (e.on) {\n          be(e.on, e.trigger, e.listener);\n        }\n      });\n    }\n    De(e);\n    ie(Object.keys(t), function (e) {\n      if (e !== \"firstInitCompleted\") delete t[e];\n    });\n  }\n  function S(e) {\n    ae(e, \"htmx:beforeCleanupElement\");\n    ke(e);\n    ie(e.children, function (e) {\n      S(e);\n    });\n  }\n  function Me(t, e, n) {\n    if (t.tagName === \"BODY\") {\n      return Ve(t, e, n);\n    }\n    let r;\n    const o = t.previousSibling;\n    const i = u(t);\n    if (!i) {\n      return;\n    }\n    c(i, t, e, n);\n    if (o == null) {\n      r = i.firstChild;\n    } else {\n      r = o.nextSibling;\n    }\n    n.elts = n.elts.filter(function (e) {\n      return e !== t;\n    });\n    while (r && r !== t) {\n      if (r instanceof Element) {\n        n.elts.push(r);\n      }\n      r = r.nextSibling;\n    }\n    S(t);\n    t.remove();\n  }\n  function Fe(e, t, n) {\n    return c(e, e.firstChild, t, n);\n  }\n  function Xe(e, t, n) {\n    return c(u(e), e, t, n);\n  }\n  function Be(e, t, n) {\n    return c(e, null, t, n);\n  }\n  function Ue(e, t, n) {\n    return c(u(e), e.nextSibling, t, n);\n  }\n  function je(e) {\n    S(e);\n    const t = u(e);\n    if (t) {\n      return t.removeChild(e);\n    }\n  }\n  function Ve(e, t, n) {\n    const r = e.firstChild;\n    c(e, r, t, n);\n    if (r) {\n      while (r.nextSibling) {\n        S(r.nextSibling);\n        e.removeChild(r.nextSibling);\n      }\n      S(r);\n      e.removeChild(r);\n    }\n  }\n  function _e(t, e, n, r, o) {\n    switch (t) {\n      case \"none\":\n        return;\n      case \"outerHTML\":\n        Me(n, r, o);\n        return;\n      case \"afterbegin\":\n        Fe(n, r, o);\n        return;\n      case \"beforebegin\":\n        Xe(n, r, o);\n        return;\n      case \"beforeend\":\n        Be(n, r, o);\n        return;\n      case \"afterend\":\n        Ue(n, r, o);\n        return;\n      case \"delete\":\n        je(n);\n        return;\n      default:\n        var i = Jn(e);\n        for (let e = 0; e < i.length; e++) {\n          const s = i[e];\n          try {\n            const l = s.handleSwap(t, n, r, o);\n            if (l) {\n              if (Array.isArray(l)) {\n                for (let e = 0; e < l.length; e++) {\n                  const c = l[e];\n                  if (\n                    c.nodeType !== Node.TEXT_NODE &&\n                    c.nodeType !== Node.COMMENT_NODE\n                  ) {\n                    o.tasks.push(Le(c));\n                  }\n                }\n              }\n              return;\n            }\n          } catch (e) {\n            R(e);\n          }\n        }\n        if (t === \"innerHTML\") {\n          Ve(n, r, o);\n        } else {\n          _e(Q.config.defaultSwapStyle, e, n, r, o);\n        }\n    }\n  }\n  function ze(e, n, r) {\n    var t = x(e, \"[hx-swap-oob], [data-hx-swap-oob]\");\n    ie(t, function (e) {\n      if (Q.config.allowNestedOobSwaps || e.parentElement === null) {\n        const t = a(e, \"hx-swap-oob\");\n        if (t != null) {\n          He(t, e, n, r);\n        }\n      } else {\n        e.removeAttribute(\"hx-swap-oob\");\n        e.removeAttribute(\"data-hx-swap-oob\");\n      }\n    });\n    return t.length > 0;\n  }\n  function $e(h, d, p, g) {\n    if (!g) {\n      g = {};\n    }\n    let m = null;\n    let n = null;\n    let e = function () {\n      re(g.beforeSwapCallback);\n      h = w(h);\n      const r = g.contextElement ? y(g.contextElement, false) : te();\n      const e = document.activeElement;\n      let t = {};\n      t = {\n        elt: e,\n        start: e ? e.selectionStart : null,\n        end: e ? e.selectionEnd : null,\n      };\n      const o = Sn(h);\n      if (p.swapStyle === \"textContent\") {\n        h.textContent = d;\n      } else {\n        let n = P(d);\n        o.title = g.title || n.title;\n        if (g.historyRequest) {\n          n = n.querySelector(\"[hx-history-elt],[data-hx-history-elt]\") || n;\n        }\n        if (g.selectOOB) {\n          const i = g.selectOOB.split(\",\");\n          for (let t = 0; t < i.length; t++) {\n            const s = i[t].split(\":\", 2);\n            let e = s[0].trim();\n            if (e.indexOf(\"#\") === 0) {\n              e = e.substring(1);\n            }\n            const l = s[1] || \"true\";\n            const c = n.querySelector(\"#\" + e);\n            if (c) {\n              He(l, c, o, r);\n            }\n          }\n        }\n        ze(n, o, r);\n        ie(x(n, \"template\"), function (e) {\n          if (e.content && ze(e.content, o, r)) {\n            e.remove();\n          }\n        });\n        if (g.select) {\n          const u = te().createDocumentFragment();\n          ie(n.querySelectorAll(g.select), function (e) {\n            u.appendChild(e);\n          });\n          n = u;\n        }\n        qe(n);\n        _e(p.swapStyle, g.contextElement, h, n, o);\n        Te();\n      }\n      if (t.elt && !se(t.elt) && ee(t.elt, \"id\")) {\n        const f = document.getElementById(ee(t.elt, \"id\"));\n        const a = {\n          preventScroll:\n            p.focusScroll !== undefined\n              ? !p.focusScroll\n              : !Q.config.defaultFocusScroll,\n        };\n        if (f) {\n          if (t.start && f.setSelectionRange) {\n            try {\n              f.setSelectionRange(t.start, t.end);\n            } catch (e) {}\n          }\n          f.focus(a);\n        }\n      }\n      h.classList.remove(Q.config.swappingClass);\n      ie(o.elts, function (e) {\n        if (e.classList) {\n          e.classList.add(Q.config.settlingClass);\n        }\n        ae(e, \"htmx:afterSwap\", g.eventInfo);\n      });\n      re(g.afterSwapCallback);\n      if (!p.ignoreTitle) {\n        Bn(o.title);\n      }\n      const n = function () {\n        ie(o.tasks, function (e) {\n          e.call();\n        });\n        ie(o.elts, function (e) {\n          if (e.classList) {\n            e.classList.remove(Q.config.settlingClass);\n          }\n          ae(e, \"htmx:afterSettle\", g.eventInfo);\n        });\n        if (g.anchor) {\n          const e = ce(w(\"#\" + g.anchor));\n          if (e) {\n            e.scrollIntoView({ block: \"start\", behavior: \"auto\" });\n          }\n        }\n        En(o.elts, p);\n        re(g.afterSettleCallback);\n        re(m);\n      };\n      if (p.settleDelay > 0) {\n        b().setTimeout(n, p.settleDelay);\n      } else {\n        n();\n      }\n    };\n    let t = Q.config.globalViewTransitions;\n    if (p.hasOwnProperty(\"transition\")) {\n      t = p.transition;\n    }\n    const r = g.contextElement || te();\n    if (\n      t &&\n      ae(r, \"htmx:beforeTransition\", g.eventInfo) &&\n      typeof Promise !== \"undefined\" &&\n      document.startViewTransition\n    ) {\n      const o = new Promise(function (e, t) {\n        m = e;\n        n = t;\n      });\n      const i = e;\n      e = function () {\n        document.startViewTransition(function () {\n          i();\n          return o;\n        });\n      };\n    }\n    try {\n      if (p?.swapDelay && p.swapDelay > 0) {\n        b().setTimeout(e, p.swapDelay);\n      } else {\n        e();\n      }\n    } catch (e) {\n      fe(r, \"htmx:swapError\", g.eventInfo);\n      re(n);\n      throw e;\n    }\n  }\n  function Je(e, t, n) {\n    const r = e.getResponseHeader(t);\n    if (r.indexOf(\"{\") === 0) {\n      const o = v(r);\n      for (const i in o) {\n        if (o.hasOwnProperty(i)) {\n          let e = o[i];\n          if (k(e)) {\n            n = e.target !== undefined ? e.target : n;\n          } else {\n            e = { value: e };\n          }\n          ae(n, i, e);\n        }\n      }\n    } else {\n      const s = r.split(\",\");\n      for (let e = 0; e < s.length; e++) {\n        ae(n, s[e].trim(), []);\n      }\n    }\n  }\n  const Ke = /\\s/;\n  const E = /[\\s,]/;\n  const Ge = /[_$a-zA-Z]/;\n  const We = /[_$a-zA-Z0-9]/;\n  const Ze = ['\"', \"'\", \"/\"];\n  const C = /[^\\s]/;\n  const Ye = /[{(]/;\n  const Qe = /[})]/;\n  function et(e) {\n    const t = [];\n    let n = 0;\n    while (n < e.length) {\n      if (Ge.exec(e.charAt(n))) {\n        var r = n;\n        while (We.exec(e.charAt(n + 1))) {\n          n++;\n        }\n        t.push(e.substring(r, n + 1));\n      } else if (Ze.indexOf(e.charAt(n)) !== -1) {\n        const o = e.charAt(n);\n        var r = n;\n        n++;\n        while (n < e.length && e.charAt(n) !== o) {\n          if (e.charAt(n) === \"\\\\\") {\n            n++;\n          }\n          n++;\n        }\n        t.push(e.substring(r, n + 1));\n      } else {\n        const i = e.charAt(n);\n        t.push(i);\n      }\n      n++;\n    }\n    return t;\n  }\n  function tt(e, t, n) {\n    return (\n      Ge.exec(e.charAt(0)) &&\n      e !== \"true\" &&\n      e !== \"false\" &&\n      e !== \"this\" &&\n      e !== n &&\n      t !== \".\"\n    );\n  }\n  function nt(r, o, i) {\n    if (o[0] === \"[\") {\n      o.shift();\n      let e = 1;\n      let t = \" return (function(\" + i + \"){ return (\";\n      let n = null;\n      while (o.length > 0) {\n        const s = o[0];\n        if (s === \"]\") {\n          e--;\n          if (e === 0) {\n            if (n === null) {\n              t = t + \"true\";\n            }\n            o.shift();\n            t += \")})\";\n            try {\n              const l = On(\n                r,\n                function () {\n                  return Function(t)();\n                },\n                function () {\n                  return true;\n                },\n              );\n              l.source = t;\n              return l;\n            } catch (e) {\n              fe(te().body, \"htmx:syntax:error\", { error: e, source: t });\n              return null;\n            }\n          }\n        } else if (s === \"[\") {\n          e++;\n        }\n        if (tt(s, n, i)) {\n          t +=\n            \"((\" +\n            i +\n            \".\" +\n            s +\n            \") ? (\" +\n            i +\n            \".\" +\n            s +\n            \") : (window.\" +\n            s +\n            \"))\";\n        } else {\n          t = t + s;\n        }\n        n = o.shift();\n      }\n    }\n  }\n  function O(e, t) {\n    let n = \"\";\n    while (e.length > 0 && !t.test(e[0])) {\n      n += e.shift();\n    }\n    return n;\n  }\n  function rt(e) {\n    let t;\n    if (e.length > 0 && Ye.test(e[0])) {\n      e.shift();\n      t = O(e, Qe).trim();\n      e.shift();\n    } else {\n      t = O(e, E);\n    }\n    return t;\n  }\n  const ot = \"input, textarea, select\";\n  function it(e, t, n) {\n    const r = [];\n    const o = et(t);\n    do {\n      O(o, C);\n      const l = o.length;\n      const c = O(o, /[,\\[\\s]/);\n      if (c !== \"\") {\n        if (c === \"every\") {\n          const u = { trigger: \"every\" };\n          O(o, C);\n          u.pollInterval = d(O(o, /[,\\[\\s]/));\n          O(o, C);\n          var i = nt(e, o, \"event\");\n          if (i) {\n            u.eventFilter = i;\n          }\n          r.push(u);\n        } else {\n          const f = { trigger: c };\n          var i = nt(e, o, \"event\");\n          if (i) {\n            f.eventFilter = i;\n          }\n          O(o, C);\n          while (o.length > 0 && o[0] !== \",\") {\n            const a = o.shift();\n            if (a === \"changed\") {\n              f.changed = true;\n            } else if (a === \"once\") {\n              f.once = true;\n            } else if (a === \"consume\") {\n              f.consume = true;\n            } else if (a === \"delay\" && o[0] === \":\") {\n              o.shift();\n              f.delay = d(O(o, E));\n            } else if (a === \"from\" && o[0] === \":\") {\n              o.shift();\n              if (Ye.test(o[0])) {\n                var s = rt(o);\n              } else {\n                var s = O(o, E);\n                if (\n                  s === \"closest\" ||\n                  s === \"find\" ||\n                  s === \"next\" ||\n                  s === \"previous\"\n                ) {\n                  o.shift();\n                  const h = rt(o);\n                  if (h.length > 0) {\n                    s += \" \" + h;\n                  }\n                }\n              }\n              f.from = s;\n            } else if (a === \"target\" && o[0] === \":\") {\n              o.shift();\n              f.target = rt(o);\n            } else if (a === \"throttle\" && o[0] === \":\") {\n              o.shift();\n              f.throttle = d(O(o, E));\n            } else if (a === \"queue\" && o[0] === \":\") {\n              o.shift();\n              f.queue = O(o, E);\n            } else if (a === \"root\" && o[0] === \":\") {\n              o.shift();\n              f[a] = rt(o);\n            } else if (a === \"threshold\" && o[0] === \":\") {\n              o.shift();\n              f[a] = O(o, E);\n            } else {\n              fe(e, \"htmx:syntax:error\", { token: o.shift() });\n            }\n            O(o, C);\n          }\n          r.push(f);\n        }\n      }\n      if (o.length === l) {\n        fe(e, \"htmx:syntax:error\", { token: o.shift() });\n      }\n      O(o, C);\n    } while (o[0] === \",\" && o.shift());\n    if (n) {\n      n[t] = r;\n    }\n    return r;\n  }\n  function st(e) {\n    const t = a(e, \"hx-trigger\");\n    let n = [];\n    if (t) {\n      const r = Q.config.triggerSpecsCache;\n      n = (r && r[t]) || it(e, t, r);\n    }\n    if (n.length > 0) {\n      return n;\n    } else if (h(e, \"form\")) {\n      return [{ trigger: \"submit\" }];\n    } else if (h(e, 'input[type=\"button\"], input[type=\"submit\"]')) {\n      return [{ trigger: \"click\" }];\n    } else if (h(e, ot)) {\n      return [{ trigger: \"change\" }];\n    } else {\n      return [{ trigger: \"click\" }];\n    }\n  }\n  function lt(e) {\n    oe(e).cancelled = true;\n  }\n  function ct(e, t, n) {\n    const r = oe(e);\n    r.timeout = b().setTimeout(function () {\n      if (se(e) && r.cancelled !== true) {\n        if (!pt(n, e, Bt(\"hx:poll:trigger\", { triggerSpec: n, target: e }))) {\n          t(e);\n        }\n        ct(e, t, n);\n      }\n    }, n.pollInterval);\n  }\n  function ut(e) {\n    return (\n      location.hostname === e.hostname &&\n      ee(e, \"href\") &&\n      ee(e, \"href\").indexOf(\"#\") !== 0\n    );\n  }\n  function ft(e) {\n    return g(e, Q.config.disableSelector);\n  }\n  function at(t, n, e) {\n    if (\n      (t instanceof HTMLAnchorElement &&\n        ut(t) &&\n        (t.target === \"\" || t.target === \"_self\")) ||\n      (t.tagName === \"FORM\" &&\n        String(ee(t, \"method\")).toLowerCase() !== \"dialog\")\n    ) {\n      n.boosted = true;\n      let r, o;\n      if (t.tagName === \"A\") {\n        r = \"get\";\n        o = ee(t, \"href\");\n      } else {\n        const i = ee(t, \"method\");\n        r = i ? i.toLowerCase() : \"get\";\n        o = ee(t, \"action\");\n        if (o == null || o === \"\") {\n          o = location.href;\n        }\n        if (r === \"get\" && o.includes(\"?\")) {\n          o = o.replace(/\\?[^#]+/, \"\");\n        }\n      }\n      e.forEach(function (e) {\n        gt(\n          t,\n          function (e, t) {\n            const n = ce(e);\n            if (ft(n)) {\n              S(n);\n              return;\n            }\n            he(r, o, n, t);\n          },\n          n,\n          e,\n          true,\n        );\n      });\n    }\n  }\n  function ht(e, t) {\n    if (e.type === \"submit\" || e.type === \"click\") {\n      t = ce(e.target) || t;\n      if (t.tagName === \"FORM\") {\n        return true;\n      }\n      if (t.form && t.type === \"submit\") {\n        return true;\n      }\n      t = t.closest(\"a\");\n      if (\n        t &&\n        t.href &&\n        (t.getAttribute(\"href\") === \"#\" ||\n          t.getAttribute(\"href\").indexOf(\"#\") !== 0)\n      ) {\n        return true;\n      }\n    }\n    return false;\n  }\n  function dt(e, t) {\n    return (\n      oe(e).boosted &&\n      e instanceof HTMLAnchorElement &&\n      t.type === \"click\" &&\n      (t.ctrlKey || t.metaKey)\n    );\n  }\n  function pt(e, t, n) {\n    const r = e.eventFilter;\n    if (r) {\n      try {\n        return r.call(t, n) !== true;\n      } catch (e) {\n        const o = r.source;\n        fe(te().body, \"htmx:eventFilter:error\", { error: e, source: o });\n        return true;\n      }\n    }\n    return false;\n  }\n  function gt(l, c, e, u, f) {\n    const a = oe(l);\n    let t;\n    if (u.from) {\n      t = m(l, u.from);\n    } else {\n      t = [l];\n    }\n    if (u.changed) {\n      if (!(\"lastValue\" in a)) {\n        a.lastValue = new WeakMap();\n      }\n      t.forEach(function (e) {\n        if (!a.lastValue.has(u)) {\n          a.lastValue.set(u, new WeakMap());\n        }\n        a.lastValue.get(u).set(e, e.value);\n      });\n    }\n    ie(t, function (i) {\n      const s = function (e) {\n        if (!se(l)) {\n          i.removeEventListener(u.trigger, s);\n          return;\n        }\n        if (dt(l, e)) {\n          return;\n        }\n        if (f || ht(e, l)) {\n          e.preventDefault();\n        }\n        if (pt(u, l, e)) {\n          return;\n        }\n        const t = oe(e);\n        t.triggerSpec = u;\n        if (t.handledFor == null) {\n          t.handledFor = [];\n        }\n        if (t.handledFor.indexOf(l) < 0) {\n          t.handledFor.push(l);\n          if (u.consume) {\n            e.stopPropagation();\n          }\n          if (u.target && e.target) {\n            if (!h(ce(e.target), u.target)) {\n              return;\n            }\n          }\n          if (u.once) {\n            if (a.triggeredOnce) {\n              return;\n            } else {\n              a.triggeredOnce = true;\n            }\n          }\n          if (u.changed) {\n            const n = e.target;\n            const r = n.value;\n            const o = a.lastValue.get(u);\n            if (o.has(n) && o.get(n) === r) {\n              return;\n            }\n            o.set(n, r);\n          }\n          if (a.delayed) {\n            clearTimeout(a.delayed);\n          }\n          if (a.throttle) {\n            return;\n          }\n          if (u.throttle > 0) {\n            if (!a.throttle) {\n              ae(l, \"htmx:trigger\");\n              c(l, e);\n              a.throttle = b().setTimeout(function () {\n                a.throttle = null;\n              }, u.throttle);\n            }\n          } else if (u.delay > 0) {\n            a.delayed = b().setTimeout(function () {\n              ae(l, \"htmx:trigger\");\n              c(l, e);\n            }, u.delay);\n          } else {\n            ae(l, \"htmx:trigger\");\n            c(l, e);\n          }\n        }\n      };\n      if (e.listenerInfos == null) {\n        e.listenerInfos = [];\n      }\n      e.listenerInfos.push({ trigger: u.trigger, listener: s, on: i });\n      i.addEventListener(u.trigger, s);\n    });\n  }\n  let mt = false;\n  let yt = null;\n  function xt() {\n    if (!yt) {\n      yt = function () {\n        mt = true;\n      };\n      window.addEventListener(\"scroll\", yt);\n      window.addEventListener(\"resize\", yt);\n      setInterval(function () {\n        if (mt) {\n          mt = false;\n          ie(\n            te().querySelectorAll(\n              \"[hx-trigger*='revealed'],[data-hx-trigger*='revealed']\",\n            ),\n            function (e) {\n              bt(e);\n            },\n          );\n        }\n      }, 200);\n    }\n  }\n  function bt(e) {\n    if (!s(e, \"data-hx-revealed\") && F(e)) {\n      e.setAttribute(\"data-hx-revealed\", \"true\");\n      const t = oe(e);\n      if (t.initHash) {\n        ae(e, \"revealed\");\n      } else {\n        e.addEventListener(\n          \"htmx:afterProcessNode\",\n          function () {\n            ae(e, \"revealed\");\n          },\n          { once: true },\n        );\n      }\n    }\n  }\n  function vt(e, t, n, r) {\n    const o = function () {\n      if (!n.loaded) {\n        n.loaded = true;\n        ae(e, \"htmx:trigger\");\n        t(e);\n      }\n    };\n    if (r > 0) {\n      b().setTimeout(o, r);\n    } else {\n      o();\n    }\n  }\n  function wt(t, n, e) {\n    let i = false;\n    ie(de, function (r) {\n      if (s(t, \"hx-\" + r)) {\n        const o = a(t, \"hx-\" + r);\n        i = true;\n        n.path = o;\n        n.verb = r;\n        e.forEach(function (e) {\n          St(t, e, n, function (e, t) {\n            const n = ce(e);\n            if (ft(n)) {\n              S(n);\n              return;\n            }\n            he(r, o, n, t);\n          });\n        });\n      }\n    });\n    return i;\n  }\n  function St(r, e, t, n) {\n    if (e.trigger === \"revealed\") {\n      xt();\n      gt(r, n, t, e);\n      bt(ce(r));\n    } else if (e.trigger === \"intersect\") {\n      const o = {};\n      if (e.root) {\n        o.root = ue(r, e.root);\n      }\n      if (e.threshold) {\n        o.threshold = parseFloat(e.threshold);\n      }\n      const i = new IntersectionObserver(function (t) {\n        for (let e = 0; e < t.length; e++) {\n          const n = t[e];\n          if (n.isIntersecting) {\n            ae(r, \"intersect\");\n            break;\n          }\n        }\n      }, o);\n      i.observe(ce(r));\n      gt(ce(r), n, t, e);\n    } else if (!t.firstInitCompleted && e.trigger === \"load\") {\n      if (!pt(e, r, Bt(\"load\", { elt: r }))) {\n        vt(ce(r), n, t, e.delay);\n      }\n    } else if (e.pollInterval > 0) {\n      t.polling = true;\n      ct(ce(r), n, e);\n    } else {\n      gt(r, n, t, e);\n    }\n  }\n  function Et(e) {\n    const t = ce(e);\n    if (!t) {\n      return false;\n    }\n    const n = t.attributes;\n    for (let e = 0; e < n.length; e++) {\n      const r = n[e].name;\n      if (\n        l(r, \"hx-on:\") ||\n        l(r, \"data-hx-on:\") ||\n        l(r, \"hx-on-\") ||\n        l(r, \"data-hx-on-\")\n      ) {\n        return true;\n      }\n    }\n    return false;\n  }\n  const Ct = new XPathEvaluator().createExpression(\n    './/*[@*[ starts-with(name(), \"hx-on:\") or starts-with(name(), \"data-hx-on:\") or' +\n      ' starts-with(name(), \"hx-on-\") or starts-with(name(), \"data-hx-on-\") ]]',\n  );\n  function Ot(e, t) {\n    if (Et(e)) {\n      t.push(ce(e));\n    }\n    const n = Ct.evaluate(e);\n    let r = null;\n    while ((r = n.iterateNext())) t.push(ce(r));\n  }\n  function Rt(e) {\n    const t = [];\n    if (e instanceof DocumentFragment) {\n      for (const n of e.childNodes) {\n        Ot(n, t);\n      }\n    } else {\n      Ot(e, t);\n    }\n    return t;\n  }\n  function Ht(e) {\n    if (e.querySelectorAll) {\n      const n =\n        \", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]\";\n      const r = [];\n      for (const i in Vn) {\n        const s = Vn[i];\n        if (s.getSelectors) {\n          var t = s.getSelectors();\n          if (t) {\n            r.push(t);\n          }\n        }\n      }\n      const o = e.querySelectorAll(\n        T +\n          n +\n          \", form, [type='submit'],\" +\n          \" [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]\" +\n          r\n            .flat()\n            .map((e) => \", \" + e)\n            .join(\"\"),\n      );\n      return o;\n    } else {\n      return [];\n    }\n  }\n  function Tt(e) {\n    const t = At(e.target);\n    const n = Nt(e);\n    if (n) {\n      n.lastButtonClicked = t;\n    }\n  }\n  function qt(e) {\n    const t = Nt(e);\n    if (t) {\n      t.lastButtonClicked = null;\n    }\n  }\n  function At(e) {\n    return g(ce(e), \"button, input[type='submit']\");\n  }\n  function Lt(e) {\n    return e.form || g(e, \"form\");\n  }\n  function Nt(e) {\n    const t = At(e.target);\n    if (!t) {\n      return;\n    }\n    const n = Lt(t);\n    return oe(n);\n  }\n  function It(e) {\n    e.addEventListener(\"click\", Tt);\n    e.addEventListener(\"focusin\", Tt);\n    e.addEventListener(\"focusout\", qt);\n  }\n  function Pt(t, e, n) {\n    const r = oe(t);\n    if (!Array.isArray(r.onHandlers)) {\n      r.onHandlers = [];\n    }\n    let o;\n    const i = function (e) {\n      On(t, function () {\n        if (ft(t)) {\n          return;\n        }\n        if (!o) {\n          o = new Function(\"event\", n);\n        }\n        o.call(t, e);\n      });\n    };\n    t.addEventListener(e, i);\n    r.onHandlers.push({ event: e, listener: i });\n  }\n  function Dt(t) {\n    De(t);\n    for (let e = 0; e < t.attributes.length; e++) {\n      const n = t.attributes[e].name;\n      const r = t.attributes[e].value;\n      if (l(n, \"hx-on\") || l(n, \"data-hx-on\")) {\n        const o = n.indexOf(\"-on\") + 3;\n        const i = n.slice(o, o + 1);\n        if (i === \"-\" || i === \":\") {\n          let e = n.slice(o + 1);\n          if (l(e, \":\")) {\n            e = \"htmx\" + e;\n          } else if (l(e, \"-\")) {\n            e = \"htmx:\" + e.slice(1);\n          } else if (l(e, \"htmx-\")) {\n            e = \"htmx:\" + e.slice(5);\n          }\n          Pt(t, e, r);\n        }\n      }\n    }\n  }\n  function kt(t) {\n    ae(t, \"htmx:beforeProcessNode\");\n    const n = oe(t);\n    const e = st(t);\n    const r = wt(t, n, e);\n    if (!r) {\n      if (ne(t, \"hx-boost\") === \"true\") {\n        at(t, n, e);\n      } else if (s(t, \"hx-trigger\")) {\n        e.forEach(function (e) {\n          St(t, e, n, function () {});\n        });\n      }\n    }\n    if (t.tagName === \"FORM\" || (ee(t, \"type\") === \"submit\" && s(t, \"form\"))) {\n      It(t);\n    }\n    n.firstInitCompleted = true;\n    ae(t, \"htmx:afterProcessNode\");\n  }\n  function Mt(e) {\n    if (!(e instanceof Element)) {\n      return false;\n    }\n    const t = oe(e);\n    const n = Pe(e);\n    if (t.initHash !== n) {\n      ke(e);\n      t.initHash = n;\n      return true;\n    }\n    return false;\n  }\n  function Ft(e) {\n    e = w(e);\n    if (ft(e)) {\n      S(e);\n      return;\n    }\n    const t = [];\n    if (Mt(e)) {\n      t.push(e);\n    }\n    ie(Ht(e), function (e) {\n      if (ft(e)) {\n        S(e);\n        return;\n      }\n      if (Mt(e)) {\n        t.push(e);\n      }\n    });\n    ie(Rt(e), Dt);\n    ie(t, kt);\n  }\n  function Xt(e) {\n    return e.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n  }\n  function Bt(e, t) {\n    return new CustomEvent(e, {\n      bubbles: true,\n      cancelable: true,\n      composed: true,\n      detail: t,\n    });\n  }\n  function fe(e, t, n) {\n    ae(e, t, le({ error: t }, n));\n  }\n  function Ut(e) {\n    return e === \"htmx:afterProcessNode\";\n  }\n  function jt(e, t, n) {\n    ie(Jn(e, [], n), function (e) {\n      try {\n        t(e);\n      } catch (e) {\n        R(e);\n      }\n    });\n  }\n  function R(e) {\n    console.error(e);\n  }\n  function ae(e, t, n) {\n    e = w(e);\n    if (n == null) {\n      n = {};\n    }\n    n.elt = e;\n    const r = Bt(t, n);\n    if (Q.logger && !Ut(t)) {\n      Q.logger(e, t, n);\n    }\n    if (n.error) {\n      R(n.error);\n      ae(e, \"htmx:error\", { errorInfo: n });\n    }\n    let o = e.dispatchEvent(r);\n    const i = Xt(t);\n    if (o && i !== t) {\n      const s = Bt(i, r.detail);\n      o = o && e.dispatchEvent(s);\n    }\n    jt(ce(e), function (e) {\n      o = o && e.onEvent(t, r) !== false && !r.defaultPrevented;\n    });\n    return o;\n  }\n  let Vt = location.pathname + location.search;\n  function _t(e) {\n    Vt = e;\n    if (B()) {\n      sessionStorage.setItem(\"htmx-current-path-for-history\", e);\n    }\n  }\n  function zt() {\n    const e = te().querySelector(\"[hx-history-elt],[data-hx-history-elt]\");\n    return e || te().body;\n  }\n  function $t(t, e) {\n    if (!B()) {\n      return;\n    }\n    const n = Kt(e);\n    const r = te().title;\n    const o = window.scrollY;\n    if (Q.config.historyCacheSize <= 0) {\n      sessionStorage.removeItem(\"htmx-history-cache\");\n      return;\n    }\n    t = U(t);\n    const i = v(sessionStorage.getItem(\"htmx-history-cache\")) || [];\n    for (let e = 0; e < i.length; e++) {\n      if (i[e].url === t) {\n        i.splice(e, 1);\n        break;\n      }\n    }\n    const s = { url: t, content: n, title: r, scroll: o };\n    ae(te().body, \"htmx:historyItemCreated\", { item: s, cache: i });\n    i.push(s);\n    while (i.length > Q.config.historyCacheSize) {\n      i.shift();\n    }\n    while (i.length > 0) {\n      try {\n        sessionStorage.setItem(\"htmx-history-cache\", JSON.stringify(i));\n        break;\n      } catch (e) {\n        fe(te().body, \"htmx:historyCacheError\", { cause: e, cache: i });\n        i.shift();\n      }\n    }\n  }\n  function Jt(t) {\n    if (!B()) {\n      return null;\n    }\n    t = U(t);\n    const n = v(sessionStorage.getItem(\"htmx-history-cache\")) || [];\n    for (let e = 0; e < n.length; e++) {\n      if (n[e].url === t) {\n        return n[e];\n      }\n    }\n    return null;\n  }\n  function Kt(e) {\n    const t = Q.config.requestClass;\n    const n = e.cloneNode(true);\n    ie(x(n, \".\" + t), function (e) {\n      G(e, t);\n    });\n    ie(x(n, \"[data-disabled-by-htmx]\"), function (e) {\n      e.removeAttribute(\"disabled\");\n    });\n    return n.innerHTML;\n  }\n  function Gt() {\n    const e = zt();\n    let t = Vt;\n    if (B()) {\n      t = sessionStorage.getItem(\"htmx-current-path-for-history\");\n    }\n    t = t || location.pathname + location.search;\n    const n = te().querySelector(\n      '[hx-history=\"false\" i],[data-hx-history=\"false\" i]',\n    );\n    if (!n) {\n      ae(te().body, \"htmx:beforeHistorySave\", { path: t, historyElt: e });\n      $t(t, e);\n    }\n    if (Q.config.historyEnabled)\n      history.replaceState({ htmx: true }, te().title, location.href);\n  }\n  function Wt(e) {\n    if (Q.config.getCacheBusterParam) {\n      e = e.replace(/org\\.htmx\\.cache-buster=[^&]*&?/, \"\");\n      if (Y(e, \"&\") || Y(e, \"?\")) {\n        e = e.slice(0, -1);\n      }\n    }\n    if (Q.config.historyEnabled) {\n      history.pushState({ htmx: true }, \"\", e);\n    }\n    _t(e);\n  }\n  function Zt(e) {\n    if (Q.config.historyEnabled) history.replaceState({ htmx: true }, \"\", e);\n    _t(e);\n  }\n  function Yt(e) {\n    ie(e, function (e) {\n      e.call(undefined);\n    });\n  }\n  function Qt(e) {\n    const t = new XMLHttpRequest();\n    const n = { swapStyle: \"innerHTML\", swapDelay: 0, settleDelay: 0 };\n    const r = { path: e, xhr: t, historyElt: zt(), swapSpec: n };\n    t.open(\"GET\", e, true);\n    if (Q.config.historyRestoreAsHxRequest) {\n      t.setRequestHeader(\"HX-Request\", \"true\");\n    }\n    t.setRequestHeader(\"HX-History-Restore-Request\", \"true\");\n    t.setRequestHeader(\"HX-Current-URL\", location.href);\n    t.onload = function () {\n      if (this.status >= 200 && this.status < 400) {\n        r.response = this.response;\n        ae(te().body, \"htmx:historyCacheMissLoad\", r);\n        $e(r.historyElt, r.response, n, {\n          contextElement: r.historyElt,\n          historyRequest: true,\n        });\n        _t(r.path);\n        ae(te().body, \"htmx:historyRestore\", {\n          path: e,\n          cacheMiss: true,\n          serverResponse: r.response,\n        });\n      } else {\n        fe(te().body, \"htmx:historyCacheMissLoadError\", r);\n      }\n    };\n    if (ae(te().body, \"htmx:historyCacheMiss\", r)) {\n      t.send();\n    }\n  }\n  function en(e) {\n    Gt();\n    e = e || location.pathname + location.search;\n    const t = Jt(e);\n    if (t) {\n      const n = {\n        swapStyle: \"innerHTML\",\n        swapDelay: 0,\n        settleDelay: 0,\n        scroll: t.scroll,\n      };\n      const r = { path: e, item: t, historyElt: zt(), swapSpec: n };\n      if (ae(te().body, \"htmx:historyCacheHit\", r)) {\n        $e(r.historyElt, t.content, n, {\n          contextElement: r.historyElt,\n          title: t.title,\n        });\n        _t(r.path);\n        ae(te().body, \"htmx:historyRestore\", r);\n      }\n    } else {\n      if (Q.config.refreshOnHistoryMiss) {\n        Q.location.reload(true);\n      } else {\n        Qt(e);\n      }\n    }\n  }\n  function tn(e) {\n    let t = we(e, \"hx-indicator\");\n    if (t == null) {\n      t = [e];\n    }\n    ie(t, function (e) {\n      const t = oe(e);\n      t.requestCount = (t.requestCount || 0) + 1;\n      e.classList.add.call(e.classList, Q.config.requestClass);\n    });\n    return t;\n  }\n  function nn(e) {\n    let t = we(e, \"hx-disabled-elt\");\n    if (t == null) {\n      t = [];\n    }\n    ie(t, function (e) {\n      const t = oe(e);\n      t.requestCount = (t.requestCount || 0) + 1;\n      e.setAttribute(\"disabled\", \"\");\n      e.setAttribute(\"data-disabled-by-htmx\", \"\");\n    });\n    return t;\n  }\n  function rn(e, t) {\n    ie(e.concat(t), function (e) {\n      const t = oe(e);\n      t.requestCount = (t.requestCount || 1) - 1;\n    });\n    ie(e, function (e) {\n      const t = oe(e);\n      if (t.requestCount === 0) {\n        e.classList.remove.call(e.classList, Q.config.requestClass);\n      }\n    });\n    ie(t, function (e) {\n      const t = oe(e);\n      if (t.requestCount === 0) {\n        e.removeAttribute(\"disabled\");\n        e.removeAttribute(\"data-disabled-by-htmx\");\n      }\n    });\n  }\n  function on(t, n) {\n    for (let e = 0; e < t.length; e++) {\n      const r = t[e];\n      if (r.isSameNode(n)) {\n        return true;\n      }\n    }\n    return false;\n  }\n  function sn(e) {\n    const t = e;\n    if (\n      t.name === \"\" ||\n      t.name == null ||\n      t.disabled ||\n      g(t, \"fieldset[disabled]\")\n    ) {\n      return false;\n    }\n    if (\n      t.type === \"button\" ||\n      t.type === \"submit\" ||\n      t.tagName === \"image\" ||\n      t.tagName === \"reset\" ||\n      t.tagName === \"file\"\n    ) {\n      return false;\n    }\n    if (t.type === \"checkbox\" || t.type === \"radio\") {\n      return t.checked;\n    }\n    return true;\n  }\n  function ln(t, e, n) {\n    if (t != null && e != null) {\n      if (Array.isArray(e)) {\n        e.forEach(function (e) {\n          n.append(t, e);\n        });\n      } else {\n        n.append(t, e);\n      }\n    }\n  }\n  function cn(t, n, r) {\n    if (t != null && n != null) {\n      let e = r.getAll(t);\n      if (Array.isArray(n)) {\n        e = e.filter((e) => n.indexOf(e) < 0);\n      } else {\n        e = e.filter((e) => e !== n);\n      }\n      r.delete(t);\n      ie(e, (e) => r.append(t, e));\n    }\n  }\n  function un(e) {\n    if (e instanceof HTMLSelectElement && e.multiple) {\n      return M(e.querySelectorAll(\"option:checked\")).map(function (e) {\n        return e.value;\n      });\n    }\n    if (e instanceof HTMLInputElement && e.files) {\n      return M(e.files);\n    }\n    return e.value;\n  }\n  function fn(t, n, r, e, o) {\n    if (e == null || on(t, e)) {\n      return;\n    } else {\n      t.push(e);\n    }\n    if (sn(e)) {\n      const i = ee(e, \"name\");\n      ln(i, un(e), n);\n      if (o) {\n        an(e, r);\n      }\n    }\n    if (e instanceof HTMLFormElement) {\n      ie(e.elements, function (e) {\n        if (t.indexOf(e) >= 0) {\n          cn(e.name, un(e), n);\n        } else {\n          t.push(e);\n        }\n        if (o) {\n          an(e, r);\n        }\n      });\n      new FormData(e).forEach(function (e, t) {\n        if (e instanceof File && e.name === \"\") {\n          return;\n        }\n        ln(t, e, n);\n      });\n    }\n  }\n  function an(e, t) {\n    const n = e;\n    if (n.willValidate) {\n      ae(n, \"htmx:validation:validate\");\n      if (!n.checkValidity()) {\n        t.push({ elt: n, message: n.validationMessage, validity: n.validity });\n        ae(n, \"htmx:validation:failed\", {\n          message: n.validationMessage,\n          validity: n.validity,\n        });\n      }\n    }\n  }\n  function hn(n, e) {\n    for (const t of e.keys()) {\n      n.delete(t);\n    }\n    e.forEach(function (e, t) {\n      n.append(t, e);\n    });\n    return n;\n  }\n  function dn(e, t) {\n    const n = [];\n    const r = new FormData();\n    const o = new FormData();\n    const i = [];\n    const s = oe(e);\n    if (s.lastButtonClicked && !se(s.lastButtonClicked)) {\n      s.lastButtonClicked = null;\n    }\n    let l =\n      (e instanceof HTMLFormElement && e.noValidate !== true) ||\n      a(e, \"hx-validate\") === \"true\";\n    if (s.lastButtonClicked) {\n      l = l && s.lastButtonClicked.formNoValidate !== true;\n    }\n    if (t !== \"get\") {\n      fn(n, o, i, Lt(e), l);\n    }\n    fn(n, r, i, e, l);\n    if (\n      s.lastButtonClicked ||\n      e.tagName === \"BUTTON\" ||\n      (e.tagName === \"INPUT\" && ee(e, \"type\") === \"submit\")\n    ) {\n      const u = s.lastButtonClicked || e;\n      const f = ee(u, \"name\");\n      ln(f, u.value, o);\n    }\n    const c = we(e, \"hx-include\");\n    ie(c, function (e) {\n      fn(n, r, i, ce(e), l);\n      if (!h(e, \"form\")) {\n        ie(p(e).querySelectorAll(ot), function (e) {\n          fn(n, r, i, e, l);\n        });\n      }\n    });\n    hn(r, o);\n    return { errors: i, formData: r, values: kn(r) };\n  }\n  function pn(e, t, n) {\n    if (e !== \"\") {\n      e += \"&\";\n    }\n    if (String(n) === \"[object Object]\") {\n      n = JSON.stringify(n);\n    }\n    const r = encodeURIComponent(n);\n    e += encodeURIComponent(t) + \"=\" + r;\n    return e;\n  }\n  function gn(e) {\n    e = Pn(e);\n    let n = \"\";\n    e.forEach(function (e, t) {\n      n = pn(n, t, e);\n    });\n    return n;\n  }\n  function mn(e, t, n) {\n    const r = {\n      \"HX-Request\": \"true\",\n      \"HX-Trigger\": ee(e, \"id\"),\n      \"HX-Trigger-Name\": ee(e, \"name\"),\n      \"HX-Target\": a(t, \"id\"),\n      \"HX-Current-URL\": location.href,\n    };\n    Cn(e, \"hx-headers\", false, r);\n    if (n !== undefined) {\n      r[\"HX-Prompt\"] = n;\n    }\n    if (oe(e).boosted) {\n      r[\"HX-Boosted\"] = \"true\";\n    }\n    return r;\n  }\n  function yn(n, e) {\n    const t = ne(e, \"hx-params\");\n    if (t) {\n      if (t === \"none\") {\n        return new FormData();\n      } else if (t === \"*\") {\n        return n;\n      } else if (t.indexOf(\"not \") === 0) {\n        ie(t.slice(4).split(\",\"), function (e) {\n          e = e.trim();\n          n.delete(e);\n        });\n        return n;\n      } else {\n        const r = new FormData();\n        ie(t.split(\",\"), function (t) {\n          t = t.trim();\n          if (n.has(t)) {\n            n.getAll(t).forEach(function (e) {\n              r.append(t, e);\n            });\n          }\n        });\n        return r;\n      }\n    } else {\n      return n;\n    }\n  }\n  function xn(e) {\n    return !!ee(e, \"href\") && ee(e, \"href\").indexOf(\"#\") >= 0;\n  }\n  function bn(e, t) {\n    const n = t || ne(e, \"hx-swap\");\n    const r = {\n      swapStyle: oe(e).boosted ? \"innerHTML\" : Q.config.defaultSwapStyle,\n      swapDelay: Q.config.defaultSwapDelay,\n      settleDelay: Q.config.defaultSettleDelay,\n    };\n    if (Q.config.scrollIntoViewOnBoost && oe(e).boosted && !xn(e)) {\n      r.show = \"top\";\n    }\n    if (n) {\n      const s = X(n);\n      if (s.length > 0) {\n        for (let e = 0; e < s.length; e++) {\n          const l = s[e];\n          if (l.indexOf(\"swap:\") === 0) {\n            r.swapDelay = d(l.slice(5));\n          } else if (l.indexOf(\"settle:\") === 0) {\n            r.settleDelay = d(l.slice(7));\n          } else if (l.indexOf(\"transition:\") === 0) {\n            r.transition = l.slice(11) === \"true\";\n          } else if (l.indexOf(\"ignoreTitle:\") === 0) {\n            r.ignoreTitle = l.slice(12) === \"true\";\n          } else if (l.indexOf(\"scroll:\") === 0) {\n            const c = l.slice(7);\n            var o = c.split(\":\");\n            const u = o.pop();\n            var i = o.length > 0 ? o.join(\":\") : null;\n            r.scroll = u;\n            r.scrollTarget = i;\n          } else if (l.indexOf(\"show:\") === 0) {\n            const f = l.slice(5);\n            var o = f.split(\":\");\n            const a = o.pop();\n            var i = o.length > 0 ? o.join(\":\") : null;\n            r.show = a;\n            r.showTarget = i;\n          } else if (l.indexOf(\"focus-scroll:\") === 0) {\n            const h = l.slice(\"focus-scroll:\".length);\n            r.focusScroll = h == \"true\";\n          } else if (e == 0) {\n            r.swapStyle = l;\n          } else {\n            R(\"Unknown modifier in hx-swap: \" + l);\n          }\n        }\n      }\n    }\n    return r;\n  }\n  function vn(e) {\n    return (\n      ne(e, \"hx-encoding\") === \"multipart/form-data\" ||\n      (h(e, \"form\") && ee(e, \"enctype\") === \"multipart/form-data\")\n    );\n  }\n  function wn(t, n, r) {\n    let o = null;\n    jt(n, function (e) {\n      if (o == null) {\n        o = e.encodeParameters(t, r, n);\n      }\n    });\n    if (o != null) {\n      return o;\n    } else {\n      if (vn(n)) {\n        return hn(new FormData(), Pn(r));\n      } else {\n        return gn(r);\n      }\n    }\n  }\n  function Sn(e) {\n    return { tasks: [], elts: [e] };\n  }\n  function En(e, t) {\n    const n = e[0];\n    const r = e[e.length - 1];\n    if (t.scroll) {\n      var o = null;\n      if (t.scrollTarget) {\n        o = ce(ue(n, t.scrollTarget));\n      }\n      if (t.scroll === \"top\" && (n || o)) {\n        o = o || n;\n        o.scrollTop = 0;\n      }\n      if (t.scroll === \"bottom\" && (r || o)) {\n        o = o || r;\n        o.scrollTop = o.scrollHeight;\n      }\n      if (typeof t.scroll === \"number\") {\n        b().setTimeout(function () {\n          window.scrollTo(0, t.scroll);\n        }, 0);\n      }\n    }\n    if (t.show) {\n      var o = null;\n      if (t.showTarget) {\n        let e = t.showTarget;\n        if (t.showTarget === \"window\") {\n          e = \"body\";\n        }\n        o = ce(ue(n, e));\n      }\n      if (t.show === \"top\" && (n || o)) {\n        o = o || n;\n        o.scrollIntoView({ block: \"start\", behavior: Q.config.scrollBehavior });\n      }\n      if (t.show === \"bottom\" && (r || o)) {\n        o = o || r;\n        o.scrollIntoView({ block: \"end\", behavior: Q.config.scrollBehavior });\n      }\n    }\n  }\n  function Cn(r, e, o, i, s) {\n    if (i == null) {\n      i = {};\n    }\n    if (r == null) {\n      return i;\n    }\n    const l = a(r, e);\n    if (l) {\n      let e = l.trim();\n      let t = o;\n      if (e === \"unset\") {\n        return null;\n      }\n      if (e.indexOf(\"javascript:\") === 0) {\n        e = e.slice(11);\n        t = true;\n      } else if (e.indexOf(\"js:\") === 0) {\n        e = e.slice(3);\n        t = true;\n      }\n      if (e.indexOf(\"{\") !== 0) {\n        e = \"{\" + e + \"}\";\n      }\n      let n;\n      if (t) {\n        n = On(\n          r,\n          function () {\n            if (s) {\n              return Function(\"event\", \"return (\" + e + \")\").call(r, s);\n            } else {\n              return Function(\"return (\" + e + \")\").call(r);\n            }\n          },\n          {},\n        );\n      } else {\n        n = v(e);\n      }\n      for (const c in n) {\n        if (n.hasOwnProperty(c)) {\n          if (i[c] == null) {\n            i[c] = n[c];\n          }\n        }\n      }\n    }\n    return Cn(ce(u(r)), e, o, i, s);\n  }\n  function On(e, t, n) {\n    if (Q.config.allowEval) {\n      return t();\n    } else {\n      fe(e, \"htmx:evalDisallowedError\");\n      return n;\n    }\n  }\n  function Rn(e, t, n) {\n    return Cn(e, \"hx-vars\", true, n, t);\n  }\n  function Hn(e, t, n) {\n    return Cn(e, \"hx-vals\", false, n, t);\n  }\n  function Tn(e, t) {\n    return le(Rn(e, t), Hn(e, t));\n  }\n  function qn(t, n, r) {\n    if (r !== null) {\n      try {\n        t.setRequestHeader(n, r);\n      } catch (e) {\n        t.setRequestHeader(n, encodeURIComponent(r));\n        t.setRequestHeader(n + \"-URI-AutoEncoded\", \"true\");\n      }\n    }\n  }\n  function An(t) {\n    if (t.responseURL) {\n      try {\n        const e = new URL(t.responseURL);\n        return e.pathname + e.search;\n      } catch (e) {\n        fe(te().body, \"htmx:badResponseUrl\", { url: t.responseURL });\n      }\n    }\n  }\n  function H(e, t) {\n    return t.test(e.getAllResponseHeaders());\n  }\n  function Ln(t, n, r) {\n    t = t.toLowerCase();\n    if (r) {\n      if (r instanceof Element || typeof r === \"string\") {\n        return he(t, n, null, null, {\n          targetOverride: w(r) || ve,\n          returnPromise: true,\n        });\n      } else {\n        let e = w(r.target);\n        if ((r.target && !e) || (r.source && !e && !w(r.source))) {\n          e = ve;\n        }\n        return he(t, n, w(r.source), r.event, {\n          handler: r.handler,\n          headers: r.headers,\n          values: r.values,\n          targetOverride: e,\n          swapOverride: r.swap,\n          select: r.select,\n          returnPromise: true,\n        });\n      }\n    } else {\n      return he(t, n, null, null, { returnPromise: true });\n    }\n  }\n  function Nn(e) {\n    const t = [];\n    while (e) {\n      t.push(e);\n      e = e.parentElement;\n    }\n    return t;\n  }\n  function In(e, t, n) {\n    const r = new URL(\n      t,\n      location.protocol !== \"about:\" ? location.href : window.origin,\n    );\n    const o = location.protocol !== \"about:\" ? location.origin : window.origin;\n    const i = o === r.origin;\n    if (Q.config.selfRequestsOnly) {\n      if (!i) {\n        return false;\n      }\n    }\n    return ae(e, \"htmx:validateUrl\", le({ url: r, sameHost: i }, n));\n  }\n  function Pn(e) {\n    if (e instanceof FormData) return e;\n    const t = new FormData();\n    for (const n in e) {\n      if (e.hasOwnProperty(n)) {\n        if (e[n] && typeof e[n].forEach === \"function\") {\n          e[n].forEach(function (e) {\n            t.append(n, e);\n          });\n        } else if (typeof e[n] === \"object\" && !(e[n] instanceof Blob)) {\n          t.append(n, JSON.stringify(e[n]));\n        } else {\n          t.append(n, e[n]);\n        }\n      }\n    }\n    return t;\n  }\n  function Dn(r, o, e) {\n    return new Proxy(e, {\n      get: function (t, e) {\n        if (typeof e === \"number\") return t[e];\n        if (e === \"length\") return t.length;\n        if (e === \"push\") {\n          return function (e) {\n            t.push(e);\n            r.append(o, e);\n          };\n        }\n        if (typeof t[e] === \"function\") {\n          return function () {\n            t[e].apply(t, arguments);\n            r.delete(o);\n            t.forEach(function (e) {\n              r.append(o, e);\n            });\n          };\n        }\n        if (t[e] && t[e].length === 1) {\n          return t[e][0];\n        } else {\n          return t[e];\n        }\n      },\n      set: function (e, t, n) {\n        e[t] = n;\n        r.delete(o);\n        e.forEach(function (e) {\n          r.append(o, e);\n        });\n        return true;\n      },\n    });\n  }\n  function kn(o) {\n    return new Proxy(o, {\n      get: function (e, t) {\n        if (typeof t === \"symbol\") {\n          const r = Reflect.get(e, t);\n          if (typeof r === \"function\") {\n            return function () {\n              return r.apply(o, arguments);\n            };\n          } else {\n            return r;\n          }\n        }\n        if (t === \"toJSON\") {\n          return () => Object.fromEntries(o);\n        }\n        if (t in e) {\n          if (typeof e[t] === \"function\") {\n            return function () {\n              return o[t].apply(o, arguments);\n            };\n          }\n        }\n        const n = o.getAll(t);\n        if (n.length === 0) {\n          return undefined;\n        } else if (n.length === 1) {\n          return n[0];\n        } else {\n          return Dn(e, t, n);\n        }\n      },\n      set: function (t, n, e) {\n        if (typeof n !== \"string\") {\n          return false;\n        }\n        t.delete(n);\n        if (e && typeof e.forEach === \"function\") {\n          e.forEach(function (e) {\n            t.append(n, e);\n          });\n        } else if (typeof e === \"object\" && !(e instanceof Blob)) {\n          t.append(n, JSON.stringify(e));\n        } else {\n          t.append(n, e);\n        }\n        return true;\n      },\n      deleteProperty: function (e, t) {\n        if (typeof t === \"string\") {\n          e.delete(t);\n        }\n        return true;\n      },\n      ownKeys: function (e) {\n        return Reflect.ownKeys(Object.fromEntries(e));\n      },\n      getOwnPropertyDescriptor: function (e, t) {\n        return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e), t);\n      },\n    });\n  }\n  function he(t, n, r, o, i, k) {\n    let s = null;\n    let l = null;\n    i = i != null ? i : {};\n    if (i.returnPromise && typeof Promise !== \"undefined\") {\n      var e = new Promise(function (e, t) {\n        s = e;\n        l = t;\n      });\n    }\n    if (r == null) {\n      r = te().body;\n    }\n    const M = i.handler || jn;\n    const F = i.select || null;\n    if (!se(r)) {\n      re(s);\n      return e;\n    }\n    const c = i.targetOverride || ce(Ee(r));\n    if (c == null || c == ve) {\n      fe(r, \"htmx:targetError\", { target: ne(r, \"hx-target\") });\n      re(l);\n      return e;\n    }\n    let u = oe(r);\n    const f = u.lastButtonClicked;\n    if (f) {\n      const A = ee(f, \"formaction\");\n      if (A != null) {\n        n = A;\n      }\n      const L = ee(f, \"formmethod\");\n      if (L != null) {\n        if (de.includes(L.toLowerCase())) {\n          t = L;\n        } else {\n          re(s);\n          return e;\n        }\n      }\n    }\n    const a = ne(r, \"hx-confirm\");\n    if (k === undefined) {\n      const K = function (e) {\n        return he(t, n, r, o, i, !!e);\n      };\n      const G = {\n        target: c,\n        elt: r,\n        path: n,\n        verb: t,\n        triggeringEvent: o,\n        etc: i,\n        issueRequest: K,\n        question: a,\n      };\n      if (ae(r, \"htmx:confirm\", G) === false) {\n        re(s);\n        return e;\n      }\n    }\n    let h = r;\n    let d = ne(r, \"hx-sync\");\n    let p = null;\n    let X = false;\n    if (d) {\n      const N = d.split(\":\");\n      const I = N[0].trim();\n      if (I === \"this\") {\n        h = Se(r, \"hx-sync\");\n      } else {\n        h = ce(ue(r, I));\n      }\n      d = (N[1] || \"drop\").trim();\n      u = oe(h);\n      if (d === \"drop\" && u.xhr && u.abortable !== true) {\n        re(s);\n        return e;\n      } else if (d === \"abort\") {\n        if (u.xhr) {\n          re(s);\n          return e;\n        } else {\n          X = true;\n        }\n      } else if (d === \"replace\") {\n        ae(h, \"htmx:abort\");\n      } else if (d.indexOf(\"queue\") === 0) {\n        const W = d.split(\" \");\n        p = (W[1] || \"last\").trim();\n      }\n    }\n    if (u.xhr) {\n      if (u.abortable) {\n        ae(h, \"htmx:abort\");\n      } else {\n        if (p == null) {\n          if (o) {\n            const P = oe(o);\n            if (P && P.triggerSpec && P.triggerSpec.queue) {\n              p = P.triggerSpec.queue;\n            }\n          }\n          if (p == null) {\n            p = \"last\";\n          }\n        }\n        if (u.queuedRequests == null) {\n          u.queuedRequests = [];\n        }\n        if (p === \"first\" && u.queuedRequests.length === 0) {\n          u.queuedRequests.push(function () {\n            he(t, n, r, o, i);\n          });\n        } else if (p === \"all\") {\n          u.queuedRequests.push(function () {\n            he(t, n, r, o, i);\n          });\n        } else if (p === \"last\") {\n          u.queuedRequests = [];\n          u.queuedRequests.push(function () {\n            he(t, n, r, o, i);\n          });\n        }\n        re(s);\n        return e;\n      }\n    }\n    const g = new XMLHttpRequest();\n    u.xhr = g;\n    u.abortable = X;\n    const m = function () {\n      u.xhr = null;\n      u.abortable = false;\n      if (u.queuedRequests != null && u.queuedRequests.length > 0) {\n        const e = u.queuedRequests.shift();\n        e();\n      }\n    };\n    const B = ne(r, \"hx-prompt\");\n    if (B) {\n      var y = prompt(B);\n      if (y === null || !ae(r, \"htmx:prompt\", { prompt: y, target: c })) {\n        re(s);\n        m();\n        return e;\n      }\n    }\n    if (a && !k) {\n      if (!confirm(a)) {\n        re(s);\n        m();\n        return e;\n      }\n    }\n    let x = mn(r, c, y);\n    if (t !== \"get\" && !vn(r)) {\n      x[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n    }\n    if (i.headers) {\n      x = le(x, i.headers);\n    }\n    const U = dn(r, t);\n    let b = U.errors;\n    const j = U.formData;\n    if (i.values) {\n      hn(j, Pn(i.values));\n    }\n    const V = Pn(Tn(r, o));\n    const v = hn(j, V);\n    let w = yn(v, r);\n    if (Q.config.getCacheBusterParam && t === \"get\") {\n      w.set(\"org.htmx.cache-buster\", ee(c, \"id\") || \"true\");\n    }\n    if (n == null || n === \"\") {\n      n = location.href;\n    }\n    const S = Cn(r, \"hx-request\");\n    const _ = oe(r).boosted;\n    let E = Q.config.methodsThatUseUrlParams.indexOf(t) >= 0;\n    const C = {\n      boosted: _,\n      useUrlParams: E,\n      formData: w,\n      parameters: kn(w),\n      unfilteredFormData: v,\n      unfilteredParameters: kn(v),\n      headers: x,\n      elt: r,\n      target: c,\n      verb: t,\n      errors: b,\n      withCredentials:\n        i.credentials || S.credentials || Q.config.withCredentials,\n      timeout: i.timeout || S.timeout || Q.config.timeout,\n      path: n,\n      triggeringEvent: o,\n    };\n    if (!ae(r, \"htmx:configRequest\", C)) {\n      re(s);\n      m();\n      return e;\n    }\n    n = C.path;\n    t = C.verb;\n    x = C.headers;\n    w = Pn(C.parameters);\n    b = C.errors;\n    E = C.useUrlParams;\n    if (b && b.length > 0) {\n      ae(r, \"htmx:validation:halted\", C);\n      re(s);\n      m();\n      return e;\n    }\n    const z = n.split(\"#\");\n    const $ = z[0];\n    const O = z[1];\n    let R = n;\n    if (E) {\n      R = $;\n      const Z = !w.keys().next().done;\n      if (Z) {\n        if (R.indexOf(\"?\") < 0) {\n          R += \"?\";\n        } else {\n          R += \"&\";\n        }\n        R += gn(w);\n        if (O) {\n          R += \"#\" + O;\n        }\n      }\n    }\n    if (!In(r, R, C)) {\n      fe(r, \"htmx:invalidPath\", C);\n      re(l);\n      m();\n      return e;\n    }\n    g.open(t.toUpperCase(), R, true);\n    g.overrideMimeType(\"text/html\");\n    g.withCredentials = C.withCredentials;\n    g.timeout = C.timeout;\n    if (S.noHeaders) {\n    } else {\n      for (const D in x) {\n        if (x.hasOwnProperty(D)) {\n          const Y = x[D];\n          qn(g, D, Y);\n        }\n      }\n    }\n    const H = {\n      xhr: g,\n      target: c,\n      requestConfig: C,\n      etc: i,\n      boosted: _,\n      select: F,\n      pathInfo: {\n        requestPath: n,\n        finalRequestPath: R,\n        responsePath: null,\n        anchor: O,\n      },\n    };\n    g.onload = function () {\n      try {\n        const t = Nn(r);\n        H.pathInfo.responsePath = An(g);\n        M(r, H);\n        if (H.keepIndicators !== true) {\n          rn(T, q);\n        }\n        ae(r, \"htmx:afterRequest\", H);\n        ae(r, \"htmx:afterOnLoad\", H);\n        if (!se(r)) {\n          let e = null;\n          while (t.length > 0 && e == null) {\n            const n = t.shift();\n            if (se(n)) {\n              e = n;\n            }\n          }\n          if (e) {\n            ae(e, \"htmx:afterRequest\", H);\n            ae(e, \"htmx:afterOnLoad\", H);\n          }\n        }\n        re(s);\n      } catch (e) {\n        fe(r, \"htmx:onLoadError\", le({ error: e }, H));\n        throw e;\n      } finally {\n        m();\n      }\n    };\n    g.onerror = function () {\n      rn(T, q);\n      fe(r, \"htmx:afterRequest\", H);\n      fe(r, \"htmx:sendError\", H);\n      re(l);\n      m();\n    };\n    g.onabort = function () {\n      rn(T, q);\n      fe(r, \"htmx:afterRequest\", H);\n      fe(r, \"htmx:sendAbort\", H);\n      re(l);\n      m();\n    };\n    g.ontimeout = function () {\n      rn(T, q);\n      fe(r, \"htmx:afterRequest\", H);\n      fe(r, \"htmx:timeout\", H);\n      re(l);\n      m();\n    };\n    if (!ae(r, \"htmx:beforeRequest\", H)) {\n      re(s);\n      m();\n      return e;\n    }\n    var T = tn(r);\n    var q = nn(r);\n    ie([\"loadstart\", \"loadend\", \"progress\", \"abort\"], function (t) {\n      ie([g, g.upload], function (e) {\n        e.addEventListener(t, function (e) {\n          ae(r, \"htmx:xhr:\" + t, {\n            lengthComputable: e.lengthComputable,\n            loaded: e.loaded,\n            total: e.total,\n          });\n        });\n      });\n    });\n    ae(r, \"htmx:beforeSend\", H);\n    const J = E ? null : wn(g, r, w);\n    g.send(J);\n    return e;\n  }\n  function Mn(e, t) {\n    const n = t.xhr;\n    let r = null;\n    let o = null;\n    if (H(n, /HX-Push:/i)) {\n      r = n.getResponseHeader(\"HX-Push\");\n      o = \"push\";\n    } else if (H(n, /HX-Push-Url:/i)) {\n      r = n.getResponseHeader(\"HX-Push-Url\");\n      o = \"push\";\n    } else if (H(n, /HX-Replace-Url:/i)) {\n      r = n.getResponseHeader(\"HX-Replace-Url\");\n      o = \"replace\";\n    }\n    if (r) {\n      if (r === \"false\") {\n        return {};\n      } else {\n        return { type: o, path: r };\n      }\n    }\n    const i = t.pathInfo.finalRequestPath;\n    const s = t.pathInfo.responsePath;\n    const l = ne(e, \"hx-push-url\");\n    const c = ne(e, \"hx-replace-url\");\n    const u = oe(e).boosted;\n    let f = null;\n    let a = null;\n    if (l) {\n      f = \"push\";\n      a = l;\n    } else if (c) {\n      f = \"replace\";\n      a = c;\n    } else if (u) {\n      f = \"push\";\n      a = s || i;\n    }\n    if (a) {\n      if (a === \"false\") {\n        return {};\n      }\n      if (a === \"true\") {\n        a = s || i;\n      }\n      if (t.pathInfo.anchor && a.indexOf(\"#\") === -1) {\n        a = a + \"#\" + t.pathInfo.anchor;\n      }\n      return { type: f, path: a };\n    } else {\n      return {};\n    }\n  }\n  function Fn(e, t) {\n    var n = new RegExp(e.code);\n    return n.test(t.toString(10));\n  }\n  function Xn(e) {\n    for (var t = 0; t < Q.config.responseHandling.length; t++) {\n      var n = Q.config.responseHandling[t];\n      if (Fn(n, e.status)) {\n        return n;\n      }\n    }\n    return { swap: false };\n  }\n  function Bn(e) {\n    if (e) {\n      const t = f(\"title\");\n      if (t) {\n        t.textContent = e;\n      } else {\n        window.document.title = e;\n      }\n    }\n  }\n  function Un(e, t) {\n    if (t === \"this\") {\n      return e;\n    }\n    const n = ce(ue(e, t));\n    if (n == null) {\n      fe(e, \"htmx:targetError\", { target: t });\n      throw new Error(`Invalid re-target ${t}`);\n    }\n    return n;\n  }\n  function jn(t, e) {\n    const n = e.xhr;\n    let r = e.target;\n    const o = e.etc;\n    const i = e.select;\n    if (!ae(t, \"htmx:beforeOnLoad\", e)) return;\n    if (H(n, /HX-Trigger:/i)) {\n      Je(n, \"HX-Trigger\", t);\n    }\n    if (H(n, /HX-Location:/i)) {\n      Gt();\n      let e = n.getResponseHeader(\"HX-Location\");\n      var s;\n      if (e.indexOf(\"{\") === 0) {\n        s = v(e);\n        e = s.path;\n        delete s.path;\n      }\n      Ln(\"get\", e, s).then(function () {\n        Wt(e);\n      });\n      return;\n    }\n    const l =\n      H(n, /HX-Refresh:/i) && n.getResponseHeader(\"HX-Refresh\") === \"true\";\n    if (H(n, /HX-Redirect:/i)) {\n      e.keepIndicators = true;\n      Q.location.href = n.getResponseHeader(\"HX-Redirect\");\n      l && Q.location.reload();\n      return;\n    }\n    if (l) {\n      e.keepIndicators = true;\n      Q.location.reload();\n      return;\n    }\n    const c = Mn(t, e);\n    const u = Xn(n);\n    const f = u.swap;\n    let a = !!u.error;\n    let h = Q.config.ignoreTitle || u.ignoreTitle;\n    let d = u.select;\n    if (u.target) {\n      e.target = Un(t, u.target);\n    }\n    var p = o.swapOverride;\n    if (p == null && u.swapOverride) {\n      p = u.swapOverride;\n    }\n    if (H(n, /HX-Retarget:/i)) {\n      e.target = Un(t, n.getResponseHeader(\"HX-Retarget\"));\n    }\n    if (H(n, /HX-Reswap:/i)) {\n      p = n.getResponseHeader(\"HX-Reswap\");\n    }\n    var g = n.response;\n    var m = le(\n      {\n        shouldSwap: f,\n        serverResponse: g,\n        isError: a,\n        ignoreTitle: h,\n        selectOverride: d,\n        swapOverride: p,\n      },\n      e,\n    );\n    if (u.event && !ae(r, u.event, m)) return;\n    if (!ae(r, \"htmx:beforeSwap\", m)) return;\n    r = m.target;\n    g = m.serverResponse;\n    a = m.isError;\n    h = m.ignoreTitle;\n    d = m.selectOverride;\n    p = m.swapOverride;\n    e.target = r;\n    e.failed = a;\n    e.successful = !a;\n    if (m.shouldSwap) {\n      if (n.status === 286) {\n        lt(t);\n      }\n      jt(t, function (e) {\n        g = e.transformResponse(g, n, t);\n      });\n      if (c.type) {\n        Gt();\n      }\n      var y = bn(t, p);\n      if (!y.hasOwnProperty(\"ignoreTitle\")) {\n        y.ignoreTitle = h;\n      }\n      r.classList.add(Q.config.swappingClass);\n      if (i) {\n        d = i;\n      }\n      if (H(n, /HX-Reselect:/i)) {\n        d = n.getResponseHeader(\"HX-Reselect\");\n      }\n      const x = ne(t, \"hx-select-oob\");\n      const b = ne(t, \"hx-select\");\n      $e(r, g, y, {\n        select: d === \"unset\" ? null : d || b,\n        selectOOB: x,\n        eventInfo: e,\n        anchor: e.pathInfo.anchor,\n        contextElement: t,\n        afterSwapCallback: function () {\n          if (H(n, /HX-Trigger-After-Swap:/i)) {\n            let e = t;\n            if (!se(t)) {\n              e = te().body;\n            }\n            Je(n, \"HX-Trigger-After-Swap\", e);\n          }\n        },\n        afterSettleCallback: function () {\n          if (H(n, /HX-Trigger-After-Settle:/i)) {\n            let e = t;\n            if (!se(t)) {\n              e = te().body;\n            }\n            Je(n, \"HX-Trigger-After-Settle\", e);\n          }\n        },\n        beforeSwapCallback: function () {\n          if (c.type) {\n            ae(te().body, \"htmx:beforeHistoryUpdate\", le({ history: c }, e));\n            if (c.type === \"push\") {\n              Wt(c.path);\n              ae(te().body, \"htmx:pushedIntoHistory\", { path: c.path });\n            } else {\n              Zt(c.path);\n              ae(te().body, \"htmx:replacedInHistory\", { path: c.path });\n            }\n          }\n        },\n      });\n    }\n    if (a) {\n      fe(\n        t,\n        \"htmx:responseError\",\n        le(\n          {\n            error:\n              \"Response Status Error Code \" +\n              n.status +\n              \" from \" +\n              e.pathInfo.requestPath,\n          },\n          e,\n        ),\n      );\n    }\n  }\n  const Vn = {};\n  function _n() {\n    return {\n      init: function (e) {\n        return null;\n      },\n      getSelectors: function () {\n        return null;\n      },\n      onEvent: function (e, t) {\n        return true;\n      },\n      transformResponse: function (e, t, n) {\n        return e;\n      },\n      isInlineSwap: function (e) {\n        return false;\n      },\n      handleSwap: function (e, t, n, r) {\n        return false;\n      },\n      encodeParameters: function (e, t, n) {\n        return null;\n      },\n    };\n  }\n  function zn(e, t) {\n    if (t.init) {\n      t.init(n);\n    }\n    Vn[e] = le(_n(), t);\n  }\n  function $n(e) {\n    delete Vn[e];\n  }\n  function Jn(e, n, r) {\n    if (n == undefined) {\n      n = [];\n    }\n    if (e == undefined) {\n      return n;\n    }\n    if (r == undefined) {\n      r = [];\n    }\n    const t = a(e, \"hx-ext\");\n    if (t) {\n      ie(t.split(\",\"), function (e) {\n        e = e.replace(/ /g, \"\");\n        if (e.slice(0, 7) == \"ignore:\") {\n          r.push(e.slice(7));\n          return;\n        }\n        if (r.indexOf(e) < 0) {\n          const t = Vn[e];\n          if (t && n.indexOf(t) < 0) {\n            n.push(t);\n          }\n        }\n      });\n    }\n    return Jn(ce(u(e)), n, r);\n  }\n  var Kn = false;\n  te().addEventListener(\"DOMContentLoaded\", function () {\n    Kn = true;\n  });\n  function Gn(e) {\n    if (Kn || te().readyState === \"complete\") {\n      e();\n    } else {\n      te().addEventListener(\"DOMContentLoaded\", e);\n    }\n  }\n  function Wn() {\n    if (Q.config.includeIndicatorStyles !== false) {\n      const e = Q.config.inlineStyleNonce\n        ? ` nonce=\"${Q.config.inlineStyleNonce}\"`\n        : \"\";\n      te().head.insertAdjacentHTML(\n        \"beforeend\",\n        \"<style\" +\n          e +\n          \">      .\" +\n          Q.config.indicatorClass +\n          \"{opacity:0}      .\" +\n          Q.config.requestClass +\n          \" .\" +\n          Q.config.indicatorClass +\n          \"{opacity:1; transition: opacity 200ms ease-in;}      .\" +\n          Q.config.requestClass +\n          \".\" +\n          Q.config.indicatorClass +\n          \"{opacity:1; transition: opacity 200ms ease-in;}      </style>\",\n      );\n    }\n  }\n  function Zn() {\n    const e = te().querySelector('meta[name=\"htmx-config\"]');\n    if (e) {\n      return v(e.content);\n    } else {\n      return null;\n    }\n  }\n  function Yn() {\n    const e = Zn();\n    if (e) {\n      Q.config = le(Q.config, e);\n    }\n  }\n  Gn(function () {\n    Yn();\n    Wn();\n    let e = te().body;\n    Ft(e);\n    const t = te().querySelectorAll(\n      \"[hx-trigger='restored'],[data-hx-trigger='restored']\",\n    );\n    e.addEventListener(\"htmx:abort\", function (e) {\n      const t = e.target;\n      const n = oe(t);\n      if (n && n.xhr) {\n        n.xhr.abort();\n      }\n    });\n    const n = window.onpopstate ? window.onpopstate.bind(window) : null;\n    window.onpopstate = function (e) {\n      if (e.state && e.state.htmx) {\n        en();\n        ie(t, function (e) {\n          ae(e, \"htmx:restored\", { document: te(), triggerEvent: ae });\n        });\n      } else {\n        if (n) {\n          n(e);\n        }\n      }\n    };\n    b().setTimeout(function () {\n      ae(e, \"htmx:load\", {});\n      e = null;\n    }, 0);\n  });\n  return Q;\n})();\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/imports/fiber.tmpl",
    "content": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\"\n\"github.com/gofiber/fiber/v2/middleware/adaptor\"\n\"github.com/gofiber/fiber/v2/middleware/filesystem\"\n\"net/http\""
  },
  {
    "path": "cmd/template/advanced/files/htmx/imports/gin.tmpl",
    "content": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\"\n\"io/fs\"\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/imports/standard_library.tmpl",
    "content": "\"github.com/a-h/templ\"\n\"{{.ProjectName}}/cmd/web\""
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/chi.tmpl",
    "content": "fileServer := http.FileServer(http.FS(web.Files))\n\tr.Handle(\"/assets/*\", fileServer)\n\tr.Get(\"/web\", templ.Handler(web.HelloForm()).ServeHTTP)\n\tr.Post(\"/hello\", web.HelloWebHandler)\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/echo.tmpl",
    "content": "fileServer := http.FileServer(http.FS(web.Files))\ne.GET(\"/assets/*\", echo.WrapHandler(fileServer))\n\ne.GET(\"/web\", echo.WrapHandler(templ.Handler(web.HelloForm())))\ne.POST(\"/hello\", echo.WrapHandler(http.HandlerFunc(web.HelloWebHandler)))\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/fiber.tmpl",
    "content": "s.App.Use(\"/assets\", filesystem.New(filesystem.Config{\n\t\tRoot:       http.FS(web.Files),\n\t\tPathPrefix: \"assets\",\n\t\tBrowse:     false,\n\t}))\n\ns.App.Get(\"/web\", adaptor.HTTPHandler(templ.Handler(web.HelloForm())))\n\ns.App.Post(\"/hello\", func(c *fiber.Ctx) error {\n  return web.HelloWebHandler(c)\n})\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/gin.tmpl",
    "content": "staticFiles, _ := fs.Sub(web.Files, \"assets\")\nr.StaticFS(\"/assets\", http.FS(staticFiles))\n\nr.GET(\"/web\", func(c *gin.Context) {\n  templ.Handler(web.HelloForm()).ServeHTTP(c.Writer, c.Request)\n})\n\nr.POST(\"/hello\", func(c *gin.Context) {\n  web.HelloWebHandler(c.Writer, c.Request)\n})\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/gorilla.tmpl",
    "content": "fileServer := http.FileServer(http.FS(web.Files))\nr.PathPrefix(\"/assets/\").Handler(fileServer)\n\nr.HandleFunc(\"/web\", func(w http.ResponseWriter, r *http.Request) {\n  templ.Handler(web.HelloForm()).ServeHTTP(w, r)\n})\n\nr.HandleFunc(\"/hello\", web.HelloWebHandler)\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/http_router.tmpl",
    "content": "  fileServer := http.FileServer(http.FS(web.Files))\n  r.Handler(http.MethodGet, \"/assets/*filepath\", fileServer)\n  r.Handler(http.MethodGet, \"/web\", templ.Handler(web.HelloForm()))\n  r.HandlerFunc(http.MethodPost, \"/hello\", web.HelloWebHandler)\n  \n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/routes/standard_library.tmpl",
    "content": "  fileServer := http.FileServer(http.FS(web.Files))\n\tmux.Handle(\"/assets/\", fileServer)\n\tmux.Handle(\"/web\", templ.Handler(web.HelloForm()))\n\tmux.HandleFunc(\"/hello\", web.HelloWebHandler)\n"
  },
  {
    "path": "cmd/template/advanced/files/htmx/tailwind/tailwind.config.js.tmpl",
    "content": "module.exports = {\n\tcontent: [\"./**/*.html\", \"./**/*.templ\", \"./**/*.go\",],\n\ttheme: { extend: {}, },\n\tplugins: [],\n}\n"
  },
  {
    "path": "cmd/template/advanced/files/react/app.tsx.tmpl",
    "content": "import { useState } from 'react'\nimport reactLogo from './assets/react.svg'\nimport viteLogo from '/vite.svg'\nimport './App.css'\n\nfunction App() {\n  const [count, setCount] = useState(0)\n\n  const fetchData = () => {\n    fetch(`http://localhost:${import.meta.env.VITE_PORT}/`)\n      .then(response => response.text())\n      .then(data => setMessage(data))\n      .catch(error => console.error('Error fetching data:', error))\n  }\n  const [message, setMessage] = useState<string>('')\n\n  return (\n    <>\n      <div>\n        <a href=\"https://vite.dev\" target=\"_blank\">\n          <img src={viteLogo} className=\"logo\" alt=\"Vite logo\" />\n        </a>\n        <a href=\"https://react.dev\" target=\"_blank\">\n          <img src={reactLogo} className=\"logo react\" alt=\"React logo\" />\n        </a>\n      </div>\n      <h1>Vite + React</h1>\n      <div className=\"card\">\n        <button onClick={() => setCount((count) => count + 1)}>\n          count is {count}\n        </button>\n        <p>\n          Edit <code>src/App.tsx</code> and save to test HMR\n        </p>\n      </div>\n      <p className=\"read-the-docs\">\n        Click on the Vite and React logos to learn more\n      </p>\n      <button onClick={fetchData}>\n        Click to fetch from Go server\n      </button>\n      {message && (\n        <div>\n          <h2>Server Response:</h2>\n          <p>{message}</p>\n        </div>\n      )}\n    </>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "cmd/template/advanced/files/react/tailwind/app.tsx.tmpl",
    "content": "import { useState } from 'react'\n\nfunction App() {\n  const [count, setCount] = useState(0)\n  const [message, setMessage] = useState<string>('')\n\n  const fetchData = () => {\n    fetch(`http://localhost:${import.meta.env.VITE_PORT}/`)\n      .then(response => response.text())\n      .then(data => setMessage(data))\n      .catch(error => console.error('Error fetching data:', error))\n  }\n\n  return (\n    <div className=\"min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8\">\n      <div className=\"max-w-md mx-auto space-y-8\">\n        <div className=\"text-center\">\n          <h1 className=\"text-4xl font-bold text-gray-900 mb-2\">\n            Welcome to Vite + React\n          </h1>\n          <p className=\"text-gray-600\">\n            Get started by editing <code className=\"text-sm bg-gray-100 p-1 rounded\">src/App.tsx</code>\n          </p>\n        </div>\n\n        <div className=\"bg-white p-6 rounded-lg shadow-md\">\n          <div className=\"text-center space-y-4\">\n            <button\n              onClick={() => setCount((count) => count + 1)}\n              className=\"bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-md transition-colors\"\n            >\n              Count is {count}\n            </button>\n            \n            <button\n              onClick={fetchData}\n              className=\"block w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded-md transition-colors\"\n            >\n              Fetch from Server\n            </button>\n\n            {message && (\n              <div className=\"mt-4 p-4 bg-gray-50 rounded-md\">\n                <p className=\"text-gray-700\">Server Response:</p>\n                <p className=\"text-gray-900 font-medium\">{message}</p>\n              </div>\n            )}\n          </div>\n        </div>\n\n        <div className=\"text-center text-gray-500 text-sm\">\n          Built with Vite, React, and Tailwind CSS\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default App"
  },
  {
    "path": "cmd/template/advanced/files/react/tailwind/index.css.tmpl",
    "content": "@import \"tailwindcss\";"
  },
  {
    "path": "cmd/template/advanced/files/react/tailwind/vite.config.ts.tmpl",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport tailwindcss from '@tailwindcss/vite'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [react(),tailwindcss()],\n})"
  },
  {
    "path": "cmd/template/advanced/files/tailwind/input.css.tmpl",
    "content": "@import \"tailwindcss\"\n"
  },
  {
    "path": "cmd/template/advanced/files/tailwind/output.css.tmpl",
    "content": ""
  },
  {
    "path": "cmd/template/advanced/files/websocket/imports/fiber.tmpl",
    "content": "\"github.com/gofiber/contrib/websocket\"\n"
  },
  {
    "path": "cmd/template/advanced/files/websocket/imports/standard_library.tmpl",
    "content": "\"github.com/coder/websocket\"\n"
  },
  {
    "path": "cmd/template/advanced/files/workflow/github/github_action_goreleaser.yml.tmpl",
    "content": "name: goreleaser\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\npermissions:\n  contents: write\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: '1.22.x'\n{{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}}\n      - name: Install templ\n        shell: bash\n        run: go install github.com/a-h/templ/cmd/templ@latest\n      - name: Run templ generate\n        shell: bash\n        run: templ generate -path .\n{{end}}\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        with:\n          distribution: goreleaser\n          version: ${{\"{{\"}} env.GITHUB_REF_NAME {{\"}}\"}}\n          args: release --clean\n          workdir: ./\n        env:\n          GITHUB_TOKEN: ${{\"{{\"}} secrets.GITHUB_TOKEN {{\"}}\"}}\n"
  },
  {
    "path": "cmd/template/advanced/files/workflow/github/github_action_gotest.yml.tmpl",
    "content": "name: Go-test\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: '1.22.x'\n{{if or ( .AdvancedOptions.htmx ) ( .AdvancedOptions.tailwind )}}\n      - name: Install templ\n        shell: bash\n        run: go install github.com/a-h/templ/cmd/templ@latest\n      - name: Run templ generate\n        shell: bash\n        run: templ generate -path .\n{{end}}\n      - name: Build\n        run: go build -v ./...\n      - name: Test with the Go CLI\n        run: go test ./... \n"
  },
  {
    "path": "cmd/template/advanced/files/workflow/github/github_action_releaser_config.yml.tmpl",
    "content": "version: 2\nbefore:\n  hooks:\n  - go mod tidy\n\nenv:\n  - PACKAGE_PATH=github.com/<user>/<repo>/cmd\n\nbuilds:\n- binary: \"{{\"{{\"}} .ProjectName {{\"}}\"}}\"\n  main: ./cmd/api\n  goos:\n  - darwin\n  - linux\n  - windows\n  goarch:\n  - amd64\n  - arm64\n  env:\n  - CGO_ENABLED=0\n  ldflags:\n  - -s -w -X {{\"{{\"}}.Env.PACKAGE_PATH{{\"}}\"}}={{\"{{\"}}.Version{{\"}}\"}} \nrelease:\n  prerelease: auto\n\nuniversal_binaries:\n- replace: true\n\narchives:\n  - name_template: >\n      {{\"{{\"}}- .ProjectName {{\"}}\"}}_{{\"{{\"}}- .Version {{\"}}\"}}_{{\"{{\"}}- title .Os {{\"}}\"}}_{{\"{{\"}}- if eq .Arch \"amd64\" {{\"}}\"}}x86_64{{\"{{\"}}- else if eq .Arch \"386\" {{\"}}\"}}i386{{\"{{\"}}- else {{\"}}\"}}{{\"{{\"}} .Arch {{\"}}\"}}{{\"{{\"}} end {{\"}}\"}}{{\"{{\"}}- if .Arm {{\"}}\"}}v{{\"{{\"}} .Arm {{\"}}\"}}{{\"{{\"}} end -{{\"}}\"}}\n    format_overrides:\n      - goos: windows\n        format: zip\n    builds_info:\n      group: root\n      owner: root\n    files:\n      - README.md\n\nchecksum:\n  name_template: 'checksums.txt'\n"
  },
  {
    "path": "cmd/template/advanced/gitHubAction.go",
    "content": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/workflow/github/github_action_goreleaser.yml.tmpl\nvar gitHubActionBuildTemplate []byte\n\n//go:embed files/workflow/github/github_action_gotest.yml.tmpl\nvar gitHubActionTestTemplate []byte\n\n//go:embed files/workflow/github/github_action_releaser_config.yml.tmpl\nvar gitHubActionConfigTemplate []byte\n\nfunc Releaser() []byte {\n\treturn gitHubActionBuildTemplate\n}\n\nfunc Test() []byte {\n\treturn gitHubActionTestTemplate\n}\n\nfunc ReleaserConfig() []byte {\n\treturn gitHubActionConfigTemplate\n}\n"
  },
  {
    "path": "cmd/template/advanced/routes.go",
    "content": "package advanced\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/htmx/hello.templ.tmpl\nvar helloTemplTemplate []byte\n\n//go:embed files/htmx/base.templ.tmpl\nvar baseTemplTemplate []byte\n\n//go:embed files/react/tailwind/index.css.tmpl\nvar inputCssTemplateReact []byte\n\n//go:embed files/react/tailwind/vite.config.ts.tmpl\nvar viteTailwindConfigFile []byte\n\n//go:embed files/react/tailwind/app.tsx.tmpl\nvar reactTailwindAppFile []byte\n\n//go:embed files/react/app.tsx.tmpl\nvar reactAppFile []byte\n\n//go:embed files/tailwind/input.css.tmpl\nvar inputCssTemplate []byte\n\n//go:embed files/tailwind/output.css.tmpl\nvar outputCssTemplate []byte\n\n//go:embed files/htmx/tailwind/tailwind.config.js.tmpl\nvar htmxTailwindConfigJsTemplate []byte\n\n//go:embed files/htmx/htmx.min.js.tmpl\nvar htmxMinJsTemplate []byte\n\n//go:embed files/htmx/efs.go.tmpl\nvar efsTemplate []byte\n\n//go:embed files/htmx/hello.go.tmpl\nvar helloGoTemplate []byte\n\n//go:embed files/htmx/hello_fiber.go.tmpl\nvar helloFiberGoTemplate []byte\n\n//go:embed files/htmx/routes/http_router.tmpl\nvar httpRouterHtmxTemplRoutes []byte\n\n//go:embed files/htmx/routes/standard_library.tmpl\nvar stdLibHtmxTemplRoutes []byte\n\n//go:embed files/htmx/imports/standard_library.tmpl\nvar stdLibHtmxTemplImports []byte\n\n//go:embed files/websocket/imports/standard_library.tmpl\nvar stdLibWebsocketImports []byte\n\n//go:embed files/htmx/routes/chi.tmpl\nvar chiHtmxTemplRoutes []byte\n\n//go:embed files/htmx/routes/gin.tmpl\nvar ginHtmxTemplRoutes []byte\n\n//go:embed files/htmx/imports/gin.tmpl\nvar ginHtmxTemplImports []byte\n\n//go:embed files/htmx/routes/gorilla.tmpl\nvar gorillaHtmxTemplRoutes []byte\n\n//go:embed files/htmx/routes/echo.tmpl\nvar echoHtmxTemplRoutes []byte\n\n//go:embed files/htmx/routes/fiber.tmpl\nvar fiberHtmxTemplRoutes []byte\n\n//go:embed files/htmx/imports/fiber.tmpl\nvar fiberHtmxTemplImports []byte\n\n//go:embed files/websocket/imports/fiber.tmpl\nvar fiberWebsocketTemplImports []byte\n\nfunc EchoHtmxTemplRoutesTemplate() []byte {\n\treturn echoHtmxTemplRoutes\n}\n\nfunc GorillaHtmxTemplRoutesTemplate() []byte {\n\treturn gorillaHtmxTemplRoutes\n}\n\nfunc ChiHtmxTemplRoutesTemplate() []byte {\n\treturn chiHtmxTemplRoutes\n}\n\nfunc GinHtmxTemplRoutesTemplate() []byte {\n\treturn ginHtmxTemplRoutes\n}\n\nfunc HttpRouterHtmxTemplRoutesTemplate() []byte {\n\treturn httpRouterHtmxTemplRoutes\n}\n\nfunc StdLibHtmxTemplRoutesTemplate() []byte {\n\treturn stdLibHtmxTemplRoutes\n}\n\nfunc StdLibHtmxTemplImportsTemplate() []byte {\n\treturn stdLibHtmxTemplImports\n}\n\nfunc StdLibWebsocketTemplImportsTemplate() []byte {\n\treturn stdLibWebsocketImports\n}\n\nfunc HelloTemplTemplate() []byte {\n\treturn helloTemplTemplate\n}\n\nfunc BaseTemplTemplate() []byte {\n\treturn baseTemplTemplate\n}\n\nfunc ReactTailwindAppfile() []byte {\n\treturn reactTailwindAppFile\n}\n\nfunc ReactAppfile() []byte {\n\treturn reactAppFile\n}\n\nfunc InputCssTemplateReact() []byte {\n\treturn inputCssTemplateReact\n}\n\nfunc ViteTailwindConfigFile() []byte {\n\treturn viteTailwindConfigFile\n}\n\nfunc InputCssTemplate() []byte {\n\treturn inputCssTemplate\n}\n\nfunc OutputCssTemplate() []byte {\n\treturn outputCssTemplate\n}\n\nfunc HtmxTailwindConfigJsTemplate() []byte {\n\treturn htmxTailwindConfigJsTemplate\n}\n\nfunc HtmxJSTemplate() []byte {\n\treturn htmxMinJsTemplate\n}\n\nfunc EfsTemplate() []byte {\n\treturn efsTemplate\n}\n\nfunc HelloGoTemplate() []byte {\n\treturn helloGoTemplate\n}\n\nfunc HelloFiberGoTemplate() []byte {\n\treturn helloFiberGoTemplate\n}\n\nfunc FiberHtmxTemplRoutesTemplate() []byte {\n\treturn fiberHtmxTemplRoutes\n}\n\nfunc FiberHtmxTemplImportsTemplate() []byte {\n\treturn fiberHtmxTemplImports\n}\n\nfunc FiberWebsocketTemplImportsTemplate() []byte {\n\treturn fiberWebsocketTemplImports\n}\n\nfunc GinHtmxTemplImportsTemplate() []byte {\n\treturn ginHtmxTemplImports\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/env/mongo.tmpl",
    "content": "{{ if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=mongo_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRINT_DB_PORT=27017\nBLUEPRINT_DB_USERNAME=melkey\nBLUEPRINT_DB_ROOT_PASSWORD=password1234\n"
  },
  {
    "path": "cmd/template/dbdriver/files/env/mysql.tmpl",
    "content": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=mysql_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRINT_DB_PORT=3306\nBLUEPRINT_DB_DATABASE=blueprint\nBLUEPRINT_DB_USERNAME=melkey\nBLUEPRINT_DB_PASSWORD=password1234\nBLUEPRINT_DB_ROOT_PASSWORD=password4321\n"
  },
  {
    "path": "cmd/template/dbdriver/files/env/postgres.tmpl",
    "content": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_HOST=psql_bp\n{{- else }}\nBLUEPRINT_DB_HOST=localhost\n{{- end }}\nBLUEPRINT_DB_PORT=5432\nBLUEPRINT_DB_DATABASE=blueprint\nBLUEPRINT_DB_USERNAME=melkey\nBLUEPRINT_DB_PASSWORD=password1234\nBLUEPRINT_DB_SCHEMA=public\n"
  },
  {
    "path": "cmd/template/dbdriver/files/env/redis.tmpl",
    "content": "{{- if .AdvancedOptions.docker }}\nBLUEPRINT_DB_ADDRESS=redis_bp\n{{- else }}\nBLUEPRINT_DB_ADDRESS=localhost\n{{- end }}\nBLUEPRINT_DB_PORT=6379\nBLUEPRINT_DB_PASSWORD=\nBLUEPRINT_DB_DATABASE=0\n"
  },
  {
    "path": "cmd/template/dbdriver/files/env/scylla.tmpl",
    "content": "{{- if .AdvancedOptions.docker }}\n# BLUEPRINT_DB_HOSTS=scylla_bp:9042 # ScyllaDB default port\nBLUEPRINT_DB_HOSTS=scylla_bp:19042 # ScyllaDB Shard-Aware port\n{{- else }}\n# BLUEPRINT_DB_HOSTS=localhost:9042 # ScyllaDB default port\nBLUEPRINT_DB_HOSTS=localhost:19042 # ScyllaDB Shard-Aware port\n{{- end }}\nBLUEPRINT_DB_CONSISTENCY=\"LOCAL_QUORUM\"\n# BLUEPRINT_DB_USERNAME=\n# BLUEPRINT_DB_PASSWORD="
  },
  {
    "path": "cmd/template/dbdriver/files/env/sqlite.tmpl",
    "content": "{{- 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",
    "content": "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/mongo-driver/mongo/options\"\n\t_ \"github.com/joho/godotenv/autoload\"\n)\n\ntype Service interface {\n\tHealth() map[string]string\n}\n\ntype service struct {\n\tdb *mongo.Client\n}\n\nvar (\n\thost     = os.Getenv(\"BLUEPRINT_DB_HOST\")\n\tport     = os.Getenv(\"BLUEPRINT_DB_PORT\")\n\t//database = os.Getenv(\"BLUEPRINT_DB_DATABASE\")\n)\n\nfunc New() Service {\n\tclient, err := mongo.Connect(context.Background(), options.Client().ApplyURI(fmt.Sprintf(\"mongodb://%s:%s\", host, port)))\n\n\tif err != nil {\n\t\tlog.Fatal(err)\n\n\t}\n\treturn &service{\n\t\tdb: client,\n\t}\n}\n\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\terr := s.db.Ping(ctx, nil)\n\tif err != nil {\n\t\tlog.Fatalf(\"db down: %v\", err) \n\t}\n\n\treturn map[string]string{\n\t\t\"message\": \"It's healthy\",\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/service/mysql.tmpl",
    "content": "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-driver/mysql\"\n\t_ \"github.com/joho/godotenv/autoload\"\n)\n\n// Service represents a service that interacts with a database.\ntype Service interface {\n\t// Health returns a map of health status information.\n\t// The keys and values in the map are service-specific.\n\tHealth() map[string]string\n\n\t// Close terminates the database connection.\n\t// It returns an error if the connection cannot be closed.\n\tClose() error\n}\n\ntype service struct {\n\tdb *sql.DB\n}\n\nvar (\n\tdbname     = os.Getenv(\"BLUEPRINT_DB_DATABASE\")\n\tpassword   = os.Getenv(\"BLUEPRINT_DB_PASSWORD\")\n\tusername   = os.Getenv(\"BLUEPRINT_DB_USERNAME\")\n\tport       = os.Getenv(\"BLUEPRINT_DB_PORT\")\n\thost       = os.Getenv(\"BLUEPRINT_DB_HOST\")\n\tdbInstance *service\n)\n\nfunc New() Service {\n\t// Reuse Connection\n\tif dbInstance != nil {\n\t\treturn dbInstance\n\t}\n\n\t// Opening a driver typically will not attempt to connect to the database.\n\tdb, err := sql.Open(\"mysql\", fmt.Sprintf(\"%s:%s@tcp(%s:%s)/%s\", username, password, host, port, dbname))\n\tif err != nil {\n\t\t// This will not be a connection error, but a DSN parse error or\n\t\t// another initialization error.\n\t\tlog.Fatal(err)\n\t}\n\tdb.SetConnMaxLifetime(0)\n\tdb.SetMaxIdleConns(50)\n\tdb.SetMaxOpenConns(50)\n\n\tdbInstance = &service{\n\t\tdb: db,\n\t}\n\treturn dbInstance\n}\n\n// Health checks the health of the database connection by pinging the database.\n// It returns a map with keys indicating various health statistics.\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\t// Ping the database\n\terr := s.db.PingContext(ctx)\n\tif err != nil {\n\t\tstats[\"status\"] = \"down\"\n\t\tstats[\"error\"] = fmt.Sprintf(\"db down: %v\", err)\n\t\tlog.Fatalf(\"db down: %v\", err)  // Log the error and terminate the program\n\t\treturn stats\n\t}\n\n\t// Database is up, add more statistics\n\tstats[\"status\"] = \"up\"\n\tstats[\"message\"] = \"It's healthy\"\n\n\t// Get database stats (like open connections, in use, idle, etc.)\n\tdbStats := s.db.Stats()\n\tstats[\"open_connections\"] = strconv.Itoa(dbStats.OpenConnections)\n\tstats[\"in_use\"] = strconv.Itoa(dbStats.InUse)\n\tstats[\"idle\"] = strconv.Itoa(dbStats.Idle)\n\tstats[\"wait_count\"] = strconv.FormatInt(dbStats.WaitCount, 10)\n\tstats[\"wait_duration\"] = dbStats.WaitDuration.String()\n\tstats[\"max_idle_closed\"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)\n\tstats[\"max_lifetime_closed\"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)\n\n\t// Evaluate stats to provide a health message\n\tif dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example\n\t\tstats[\"message\"] = \"The database is experiencing heavy load.\"\n\t}\n\tif dbStats.WaitCount > 1000 {\n\t\tstats[\"message\"] = \"The database has a high number of wait events, indicating potential bottlenecks.\"\n\t}\n\n\tif dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many idle connections are being closed, consider revising the connection pool settings.\"\n\t}\n\n\tif dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern.\"\n\t}\n\n\treturn stats\n}\n\n// Close closes the database connection.\n// It logs a message indicating the disconnection from the specific database.\n// If the connection is successfully closed, it returns nil.\n// If an error occurs while closing the connection, it returns the error.\nfunc (s *service) Close() error {\n\tlog.Printf(\"Disconnected from database: %s\", dbname)\n\treturn s.db.Close()\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/service/postgres.tmpl",
    "content": "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/v5/stdlib\"\n\t_ \"github.com/joho/godotenv/autoload\"\n)\n\n// Service represents a service that interacts with a database.\ntype Service interface {\n\t// Health returns a map of health status information.\n\t// The keys and values in the map are service-specific.\n\tHealth() map[string]string\n\n\t// Close terminates the database connection.\n\t// It returns an error if the connection cannot be closed.\n\tClose() error\n}\n\ntype service struct {\n\tdb *sql.DB\n}\n\nvar (\n\tdatabase = os.Getenv(\"BLUEPRINT_DB_DATABASE\")\n\tpassword = os.Getenv(\"BLUEPRINT_DB_PASSWORD\")\n\tusername = os.Getenv(\"BLUEPRINT_DB_USERNAME\")\n\tport     = os.Getenv(\"BLUEPRINT_DB_PORT\")\n\thost     = os.Getenv(\"BLUEPRINT_DB_HOST\")\n        schema   = os.Getenv(\"BLUEPRINT_DB_SCHEMA\")\n\tdbInstance *service\n)\n\nfunc New() Service {\n\t// Reuse Connection\n\tif dbInstance != nil {\n\t\treturn dbInstance\n\t}\n\tconnStr := fmt.Sprintf(\"postgres://%s:%s@%s:%s/%s?sslmode=disable&search_path=%s\", username, password, host, port, database, schema)\n\tdb, err := sql.Open(\"pgx\", connStr)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdbInstance = &service{\n\t\tdb: db,\n\t}\n\treturn dbInstance\n}\n\n// Health checks the health of the database connection by pinging the database.\n// It returns a map with keys indicating various health statistics.\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\t// Ping the database\n\terr := s.db.PingContext(ctx)\n\tif err != nil {\n\t\tstats[\"status\"] = \"down\"\n\t\tstats[\"error\"] = fmt.Sprintf(\"db down: %v\", err)\n\t\tlog.Fatalf(\"db down: %v\", err)  // Log the error and terminate the program\n\t\treturn stats\n\t}\n\n\t// Database is up, add more statistics\n\tstats[\"status\"] = \"up\"\n\tstats[\"message\"] = \"It's healthy\"\n\n\t// Get database stats (like open connections, in use, idle, etc.)\n\tdbStats := s.db.Stats()\n\tstats[\"open_connections\"] = strconv.Itoa(dbStats.OpenConnections)\n\tstats[\"in_use\"] = strconv.Itoa(dbStats.InUse)\n\tstats[\"idle\"] = strconv.Itoa(dbStats.Idle)\n\tstats[\"wait_count\"] = strconv.FormatInt(dbStats.WaitCount, 10)\n\tstats[\"wait_duration\"] = dbStats.WaitDuration.String()\n\tstats[\"max_idle_closed\"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)\n\tstats[\"max_lifetime_closed\"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)\n\n\t// Evaluate stats to provide a health message\n\tif dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example\n\t\tstats[\"message\"] = \"The database is experiencing heavy load.\"\n\t}\n\n\tif dbStats.WaitCount > 1000 {\n\t\tstats[\"message\"] = \"The database has a high number of wait events, indicating potential bottlenecks.\"\n\t}\n\n\tif dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many idle connections are being closed, consider revising the connection pool settings.\"\n\t}\n\n\tif dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern.\"\n\t}\n\n\treturn stats\n}\n\n// Close closes the database connection.\n// It logs a message indicating the disconnection from the specific database.\n// If the connection is successfully closed, it returns nil.\n// If an error occurs while closing the connection, it returns the error.\nfunc (s *service) Close() error {\n\tlog.Printf(\"Disconnected from database: %s\", database)\n\treturn s.db.Close()\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/service/redis.tmpl",
    "content": "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/godotenv/autoload\"\n\t\"github.com/redis/go-redis/v9\"\n)\n\ntype Service interface {\n\tHealth() map[string]string\n}\n\ntype service struct {\n\tdb *redis.Client\n}\n\nvar (\n\taddress  = os.Getenv(\"BLUEPRINT_DB_ADDRESS\")\n\tport     = os.Getenv(\"BLUEPRINT_DB_PORT\")\n\tpassword = os.Getenv(\"BLUEPRINT_DB_PASSWORD\")\n\tdatabase = os.Getenv(\"BLUEPRINT_DB_DATABASE\")\n)\n\nfunc New() Service {\n\tnum, err := strconv.Atoi(database)\n\tif err != nil {\n\t\tlog.Fatalf(\"database incorrect %v\", err)\n\t}\n\n\tfullAddress := fmt.Sprintf(\"%s:%s\", address, port)\n\n\trdb := redis.NewClient(&redis.Options{\n\t\tAddr:     fullAddress,\n\t\tPassword: password,\n\t\tDB:       num,\n\t\t// 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.\n\t\t// For manual setup, please refer to the Redis documentation: https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/\n\t\t// TLSConfig: &tls.Config{\n\t\t// \tMinVersion:   tls.VersionTLS12,\n\t\t// },\n\t})\n\n\ts := &service{db: rdb}\n\n\treturn s\n}\n\n// Health returns the health status and statistics of the Redis server.\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // Default is now 5s\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\t// Check Redis health and populate the stats map\n\tstats = s.checkRedisHealth(ctx, stats)\n\n\treturn stats\n}\n\n// checkRedisHealth checks the health of the Redis server and adds the relevant statistics to the stats map.\nfunc (s *service) checkRedisHealth(ctx context.Context, stats map[string]string) map[string]string {\n\t// Ping the Redis server to check its availability.\n\tpong, err := s.db.Ping(ctx).Result()\n\t// Note: By extracting and simplifying like this, `log.Fatalf(\"db down: %v\", err)`\n\t// can be changed into a standard error instead of a fatal error.\n\tif err != nil {\n\t\tlog.Fatalf(\"db down: %v\", err)\n\t}\n\n\t// Redis is up\n\tstats[\"redis_status\"] = \"up\"\n\tstats[\"redis_message\"] = \"It's healthy\"\n\tstats[\"redis_ping_response\"] = pong\n\n\t// Retrieve Redis server information.\n\tinfo, err := s.db.Info(ctx).Result()\n\tif err != nil {\n\t\tstats[\"redis_message\"] = fmt.Sprintf(\"Failed to retrieve Redis info: %v\", err)\n\t\treturn stats\n\t}\n\n\t// Parse the Redis info response.\n\tredisInfo := parseRedisInfo(info)\n\n\t// Get the pool stats of the Redis client.\n\tpoolStats := s.db.PoolStats()\n\n\t// Prepare the stats map with Redis server information and pool statistics.\n\t// Note: The \"stats\" map in the code uses string keys and values,\n\t// which is suitable for structuring and serializing the data for the frontend (e.g., JSON, XML, HTMX).\n\t// Using string types allows for easy conversion and compatibility with various data formats,\n\t// making it convenient to create health stats for monitoring or other purposes.\n\t// 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.\n\tstats[\"redis_version\"] = redisInfo[\"redis_version\"]\n\tstats[\"redis_mode\"] = redisInfo[\"redis_mode\"]\n\tstats[\"redis_connected_clients\"] = redisInfo[\"connected_clients\"]\n\tstats[\"redis_used_memory\"] = redisInfo[\"used_memory\"]\n\tstats[\"redis_used_memory_peak\"] = redisInfo[\"used_memory_peak\"]\n\tstats[\"redis_uptime_in_seconds\"] = redisInfo[\"uptime_in_seconds\"]\n\tstats[\"redis_hits_connections\"] = strconv.FormatUint(uint64(poolStats.Hits), 10)\n\tstats[\"redis_misses_connections\"] = strconv.FormatUint(uint64(poolStats.Misses), 10)\n\tstats[\"redis_timeouts_connections\"] = strconv.FormatUint(uint64(poolStats.Timeouts), 10)\n\tstats[\"redis_total_connections\"] = strconv.FormatUint(uint64(poolStats.TotalConns), 10)\n\tstats[\"redis_idle_connections\"] = strconv.FormatUint(uint64(poolStats.IdleConns), 10)\n\tstats[\"redis_stale_connections\"] = strconv.FormatUint(uint64(poolStats.StaleConns), 10)\n\tstats[\"redis_max_memory\"] = redisInfo[\"maxmemory\"]\n\n\t// Calculate the number of active connections.\n\t// Note: We use math.Max to ensure that activeConns is always non-negative,\n\t// avoiding the need for an explicit check for negative values.\n\t// This prevents a potential underflow situation.\n\tactiveConns := uint64(math.Max(float64(poolStats.TotalConns-poolStats.IdleConns), 0))\n\tstats[\"redis_active_connections\"] = strconv.FormatUint(activeConns, 10)\n\n\t// Calculate the pool size percentage.\n\tpoolSize := s.db.Options().PoolSize\n\tconnectedClients, _ := strconv.Atoi(redisInfo[\"connected_clients\"])\n\tpoolSizePercentage := float64(connectedClients) / float64(poolSize) * 100\n\tstats[\"redis_pool_size_percentage\"] = fmt.Sprintf(\"%.2f%%\", poolSizePercentage)\n\n\t// Evaluate Redis stats and update the stats map with relevant messages.\n\treturn s.evaluateRedisStats(redisInfo, stats)\n}\n\n// evaluateRedisStats evaluates the Redis server statistics and updates the stats map with relevant messages.\nfunc (s *service) evaluateRedisStats(redisInfo, stats map[string]string) map[string]string {\n\tpoolSize := s.db.Options().PoolSize\n\tpoolStats := s.db.PoolStats()\n\tconnectedClients, _ := strconv.Atoi(redisInfo[\"connected_clients\"])\n\thighConnectionThreshold := int(float64(poolSize) * 0.8)\n\n\t// Check if the number of connected clients is high.\n\tif connectedClients > highConnectionThreshold {\n\t\tstats[\"redis_message\"] = \"Redis has a high number of connected clients\"\n\t}\n\n\t// Check if the number of stale connections exceeds a threshold.\n\tminStaleConnectionsThreshold := 500\n\tif int(poolStats.StaleConns) > minStaleConnectionsThreshold {\n\t\tstats[\"redis_message\"] = fmt.Sprintf(\"Redis has %d stale connections.\", poolStats.StaleConns)\n\t}\n\n\t// Check if Redis is using a significant amount of memory.\n\tusedMemory, _ := strconv.ParseInt(redisInfo[\"used_memory\"], 10, 64)\n\tmaxMemory, _ := strconv.ParseInt(redisInfo[\"maxmemory\"], 10, 64)\n\tif maxMemory > 0 {\n\t\tusedMemoryPercentage := float64(usedMemory) / float64(maxMemory) * 100\n\t\tif usedMemoryPercentage >= 90 {\n\t\t\tstats[\"redis_message\"] = \"Redis is using a significant amount of memory\"\n\t\t}\n\t}\n\n\t// Check if Redis has been recently restarted.\n\tuptimeInSeconds, _ := strconv.ParseInt(redisInfo[\"uptime_in_seconds\"], 10, 64)\n\tif uptimeInSeconds < 3600 {\n\t\tstats[\"redis_message\"] = \"Redis has been recently restarted\"\n\t}\n\n\t// Check if the number of idle connections is high.\n\tidleConns := int(poolStats.IdleConns)\n\thighIdleConnectionThreshold := int(float64(poolSize) * 0.7)\n\tif idleConns > highIdleConnectionThreshold {\n\t\tstats[\"redis_message\"] = \"Redis has a high number of idle connections\"\n\t}\n\n\t// Check if the connection pool utilization is high.\n\tpoolUtilization := float64(poolStats.TotalConns-poolStats.IdleConns) / float64(poolSize) * 100\n\thighPoolUtilizationThreshold := 90.0\n\tif poolUtilization > highPoolUtilizationThreshold {\n\t\tstats[\"redis_message\"] = \"Redis connection pool utilization is high\"\n\t}\n\n\treturn stats\n}\n\n// parseRedisInfo parses the Redis info response and returns a map of key-value pairs.\nfunc parseRedisInfo(info string) map[string]string {\n\tresult := make(map[string]string)\n\tlines := strings.Split(info, \"\\r\\n\")\n\tfor _, line := range lines {\n\t\tif strings.Contains(line, \":\") {\n\t\t\tparts := strings.Split(line, \":\")\n\t\t\tkey := strings.TrimSpace(parts[0])\n\t\t\tvalue := strings.TrimSpace(parts[1])\n\t\t\tresult[key] = value\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/service/scylla.tmpl",
    "content": "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_ \"github.com/joho/godotenv/autoload\"\n)\n\n// Service defines the interface for health checks.\ntype Service interface {\n\tHealth() map[string]string\n\tClose() error\n}\n\n// service implements the Service interface.\ntype service struct {\n\tSession *gocql.Session\n}\n\n// Environment variables for ScyllaDB connection.\nvar (\n\thosts            = os.Getenv(\"BLUEPRINT_DB_HOSTS\")       // Comma-separated list of hosts:port\n\tusername         = os.Getenv(\"BLUEPRINT_DB_USERNAME\")    // Username for authentication\n\tpassword         = os.Getenv(\"BLUEPRINT_DB_PASSWORD\")    // Password for authentication\n\tconsistencyLevel = os.Getenv(\"BLUEPRINT_DB_CONSISTENCY\") // Consistency level\n)\n\n// New initializes a new Service with a ScyllaDB Session.\nfunc New() Service {\n\tcluster := gocql.NewCluster(strings.Split(hosts, \",\")...)\n\tcluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.RoundRobinHostPolicy())\n\n\t// Set authentication if provided\n\tif username != \"\" && password != \"\" {\n\t\tcluster.Authenticator = gocql.PasswordAuthenticator{\n\t\t\tUsername: username,\n\t\t\tPassword: password,\n\t\t}\n\t}\n\n\t// Set consistency level if provided\n\tif consistencyLevel != \"\" {\n\t\tif cl, err := parseConsistency(consistencyLevel); err == nil {\n\t\t\tcluster.Consistency = cl\n\t\t} else {\n\t\t\tlog.Printf(\"Invalid SCYLLA_DB_CONSISTENCY '%s', using default: %v\", consistencyLevel, err)\n\t\t}\n\t}\n\n\t// Create Session\n\tsession, err := cluster.CreateSession()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to connect to ScyllaDB cluster: %v\", err)\n\t}\n\n\ts := &service{Session: session}\n\treturn s\n}\n\n// parseConsistency converts a string to a gocql.Consistency value.\nfunc parseConsistency(cons string) (gocql.Consistency, error) {\n\tconsistencyMap := map[string]gocql.Consistency{\n\t\t\"ANY\":          gocql.Any,\n\t\t\"ONE\":          gocql.One,\n\t\t\"TWO\":          gocql.Two,\n\t\t\"THREE\":        gocql.Three,\n\t\t\"QUORUM\":       gocql.Quorum,\n\t\t\"ALL\":          gocql.All,\n\t\t\"LOCAL_ONE\":    gocql.LocalOne,\n\t\t\"LOCAL_QUORUM\": gocql.LocalQuorum,\n\t\t\"EACH_QUORUM\":  gocql.EachQuorum,\n\t}\n\n\tif consistency, ok := consistencyMap[strings.ToUpper(cons)]; ok {\n\t\treturn consistency, nil\n\t}\n\treturn gocql.LocalQuorum, fmt.Errorf(\"unknown consistency level: %s\", cons)\n}\n\n// Health returns the health status and statistics of the ScyllaDB cluster.\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\t// Check ScyllaDB health and populate the stats map\n\tstartedAt := time.Now()\n\n\t// Execute a simple query to check connectivity\n\tquery := \"SELECT now() FROM system.local\"\n\titer := s.Session.Query(query).WithContext(ctx).Iter()\n\tvar currentTime time.Time\n\tif !iter.Scan(&currentTime) {\n\t\tif err := iter.Close(); err != nil {\n\t\t\tstats[\"status\"] = \"down\"\n\t\t\tstats[\"message\"] = fmt.Sprintf(\"Failed to execute query: %v\", err)\n\t\t\treturn stats\n\t\t}\n\t}\n\tif err := iter.Close(); err != nil {\n\t\tstats[\"status\"] = \"down\"\n\t\tstats[\"message\"] = fmt.Sprintf(\"Error during query execution: %v\", err)\n\t\treturn stats\n\t}\n\n\t// ScyllaDB is up\n\tstats[\"status\"] = \"up\"\n\tstats[\"message\"] = \"It's healthy\"\n\tstats[\"scylla_current_time\"] = currentTime.String()\n\n\t// Retrieve cluster information\n\t// Get keyspace information\n\tgetKeyspacesQuery := \"SELECT keyspace_name FROM system_schema.keyspaces\"\n\tkeyspacesIterator := s.Session.Query(getKeyspacesQuery).Iter()\n\n\tstats[\"scylla_keyspaces\"] = strconv.Itoa(keyspacesIterator.NumRows())\n\tif err := keyspacesIterator.Close(); err != nil {\n\t\tlog.Fatalf(\"Failed to close keyspaces iterator: %v\", err)\n\t}\n\n\t// Get cluster information\n\tvar currentDatacenter string\n\tvar currentHostStatus bool\n\n\tvar clusterNodesUp uint\n\tvar clusterNodesDown uint\n\tvar clusterSize uint\n\n\tclusterNodesIterator := s.Session.Query(\"SELECT dc, up FROM system.cluster_status\").Iter()\n\tfor clusterNodesIterator.Scan(&currentDatacenter, &currentHostStatus) {\n\t\tclusterSize++\n\t\tif currentHostStatus {\n\t\t\tclusterNodesUp++\n\t\t} else {\n\t\t\tclusterNodesDown++\n\t\t}\n\t}\n\n\tif err := clusterNodesIterator.Close(); err != nil {\n\t\tlog.Fatalf(\"Failed to close cluster nodes iterator: %v\", err)\n\t}\n\n\tstats[\"scylla_cluster_size\"] = strconv.Itoa(int(clusterSize))\n\tstats[\"scylla_cluster_nodes_up\"] = strconv.Itoa(int(clusterNodesUp))\n\tstats[\"scylla_cluster_nodes_down\"] = strconv.Itoa(int(clusterNodesDown))\n\tstats[\"scylla_current_datacenter\"] = currentDatacenter\n\n\t// Calculate the time taken to perform the health check\n\tstats[\"scylla_health_check_duration\"] = time.Since(startedAt).String()\n\treturn stats\n}\n\n// Close gracefully closes the ScyllaDB Session.\nfunc (s *service) Close() error {\n\ts.Session.Close()\n\treturn nil\n}"
  },
  {
    "path": "cmd/template/dbdriver/files/service/sqlite.tmpl",
    "content": "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-sqlite3\"\n\t_ \"github.com/joho/godotenv/autoload\"\n)\n\n// Service represents a service that interacts with a database.\ntype Service interface {\n\t// Health returns a map of health status information.\n\t// The keys and values in the map are service-specific.\n\tHealth() map[string]string\n\n\t// Close terminates the database connection.\n\t// It returns an error if the connection cannot be closed.\n\tClose() error\n}\n\ntype service struct {\n\tdb *sql.DB\n}\n\nvar (\n\tdburl = os.Getenv(\"BLUEPRINT_DB_URL\")\n\tdbInstance *service\n)\n\nfunc New() Service {\n\t// Reuse Connection\n\tif dbInstance != nil {\n\t\treturn dbInstance\n\t}\n\n\tdb, err := sql.Open(\"sqlite3\", dburl)\n\tif err != nil {\n\t\t// This will not be a connection error, but a DSN parse error or\n\t\t// another initialization error.\n\t\tlog.Fatal(err)\n\t}\n\n\tdbInstance = &service{\n\t\tdb: db,\n\t}\n\treturn dbInstance\n}\n\n// Health checks the health of the database connection by pinging the database.\n// It returns a map with keys indicating various health statistics.\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\t// Ping the database\n\terr := s.db.PingContext(ctx)\n\tif err != nil {\n\t\tstats[\"status\"] = \"down\"\n\t\tstats[\"error\"] = fmt.Sprintf(\"db down: %v\", err)\n\t\tlog.Fatalf(\"db down: %v\", err)  // Log the error and terminate the program\n\t\treturn stats\n\t}\n\n\t// Database is up, add more statistics\n\tstats[\"status\"] = \"up\"\n\tstats[\"message\"] = \"It's healthy\"\n\n\t// Get database stats (like open connections, in use, idle, etc.)\n\tdbStats := s.db.Stats()\n\tstats[\"open_connections\"] = strconv.Itoa(dbStats.OpenConnections)\n\tstats[\"in_use\"] = strconv.Itoa(dbStats.InUse)\n\tstats[\"idle\"] = strconv.Itoa(dbStats.Idle)\n\tstats[\"wait_count\"] = strconv.FormatInt(dbStats.WaitCount, 10)\n\tstats[\"wait_duration\"] = dbStats.WaitDuration.String()\n\tstats[\"max_idle_closed\"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)\n\tstats[\"max_lifetime_closed\"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)\n\n\t// Evaluate stats to provide a health message\n\tif dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example\n\t\tstats[\"message\"] = \"The database is experiencing heavy load.\"\n\t}\n\n\tif dbStats.WaitCount > 1000 {\n\t\tstats[\"message\"] = \"The database has a high number of wait events, indicating potential bottlenecks.\"\n\t}\n\n\tif dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many idle connections are being closed, consider revising the connection pool settings.\"\n\t}\n\n\tif dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {\n\t\tstats[\"message\"] = \"Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern.\"\n\t}\n\n\treturn stats\n}\n\n// Close closes the database connection.\n// It logs a message indicating the disconnection from the specific database.\n// If the connection is successfully closed, it returns nil.\n// If an error occurs while closing the connection, it returns the error.\nfunc (s *service) Close() error {\n\tlog.Printf(\"Disconnected from database: %s\", dburl)\n\treturn s.db.Close()\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/tests/mongo.tmpl",
    "content": "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/testcontainers/testcontainers-go/modules/mongodb\"\n)\n\nfunc mustStartMongoContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) {\n\tdbContainer, err := mongodb.Run(context.Background(), \"mongo:latest\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbHost, err := dbContainer.Host(context.Background())\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\tdbPort, err := dbContainer.MappedPort(context.Background(), \"27017/tcp\")\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\thost = dbHost\n\tport = dbPort.Port()\n\n\treturn dbContainer.Terminate, err\n}\n\nfunc TestMain(m *testing.M) {\n\tteardown, err := mustStartMongoContainer()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not start mongodb container: %v\", err)\n\t}\n\n\tm.Run()\n\n\tif teardown != nil && teardown(context.Background()) != nil {\n\t\tlog.Fatalf(\"could not teardown mongodb container: %v\", err)\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\tsrv := New()\n\tif srv == nil {\n\t\tt.Fatal(\"New() returned nil\")\n\t}\n}\n\nfunc TestHealth(t *testing.T) {\n\tsrv := New()\n\n\tstats := srv.Health()\n\n\tif stats[\"message\"] != \"It's healthy\" {\n\t\tt.Fatalf(\"expected message to be 'It's healthy', got %s\", stats[\"message\"])\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/tests/mysql.tmpl",
    "content": "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.com/testcontainers/testcontainers-go/modules/mysql\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n)\n\nfunc mustStartMySQLContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) {\n\tvar (\n\t\tdbName = \"database\"\n\t\tdbPwd  = \"password\"\n\t\tdbUser = \"user\"\n\t)\n\n\tdbContainer, err := mysql.Run(context.Background(),\n\t\t\"mysql:8.0.36\",\n\t\tmysql.WithDatabase(dbName),\n\t\tmysql.WithUsername(dbUser),\n\t\tmysql.WithPassword(dbPwd),\n\t\ttestcontainers.WithWaitStrategy(wait.ForLog(\"port: 3306  MySQL Community Server - GPL\").WithStartupTimeout(30*time.Second)),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbname = dbName\n\tpassword = dbPwd\n\tusername = dbUser\n\n\tdbHost, err := dbContainer.Host(context.Background())\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\tdbPort, err := dbContainer.MappedPort(context.Background(), \"3306/tcp\")\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\thost = dbHost\n\tport = dbPort.Port()\n\n\treturn dbContainer.Terminate, err\n}\n\nfunc TestMain(m *testing.M) {\n\tteardown, err := mustStartMySQLContainer()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not start mysql container: %v\", err)\n\t}\n\n\tm.Run()\n\n\tif teardown != nil && teardown(context.Background()) != nil {\n\t\tlog.Fatalf(\"could not teardown mysql container: %v\", err)\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\tsrv := New()\n\tif srv == nil {\n\t\tt.Fatal(\"New() returned nil\")\n\t}\n}\n\nfunc TestHealth(t *testing.T) {\n\tsrv := New()\n\n\tstats := srv.Health()\n\n\tif stats[\"status\"] != \"up\" {\n\t\tt.Fatalf(\"expected status to be up, got %s\", stats[\"status\"])\n\t}\n\n\tif _, ok := stats[\"error\"]; ok {\n\t\tt.Fatalf(\"expected error not to be present\")\n\t}\n\n\tif stats[\"message\"] != \"It's healthy\" {\n\t\tt.Fatalf(\"expected message to be 'It's healthy', got %s\", stats[\"message\"])\n\t}\n}\n\nfunc TestClose(t *testing.T) {\n\tsrv := New()\n\n\tif srv.Close() != nil {\n\t\tt.Fatalf(\"expected Close() to return nil\")\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/tests/postgres.tmpl",
    "content": "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.com/testcontainers/testcontainers-go/modules/postgres\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n)\n\nfunc mustStartPostgresContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) {\n\tvar (\n\t\tdbName = \"database\"\n\t\tdbPwd  = \"password\"\n\t\tdbUser = \"user\"\n\t)\n\n\tdbContainer, err := postgres.Run(\n\t\tcontext.Background(),\n\t\t\"postgres:latest\",\n\t\tpostgres.WithDatabase(dbName),\n\t\tpostgres.WithUsername(dbUser),\n\t\tpostgres.WithPassword(dbPwd),\n\t\ttestcontainers.WithWaitStrategy(\n\t\t\twait.ForLog(\"database system is ready to accept connections\").\n\t\t\t\tWithOccurrence(2).\n\t\t\t\tWithStartupTimeout(5*time.Second)),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdatabase = dbName\n\tpassword = dbPwd\n\tusername = dbUser\n\n\tdbHost, err := dbContainer.Host(context.Background())\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\tdbPort, err := dbContainer.MappedPort(context.Background(), \"5432/tcp\")\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\thost = dbHost\n\tport = dbPort.Port()\n\n\treturn dbContainer.Terminate, err\n}\n\nfunc TestMain(m *testing.M) {\n\tteardown, err := mustStartPostgresContainer()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not start postgres container: %v\", err)\n\t}\n\n\tm.Run()\n\n\tif teardown != nil && teardown(context.Background()) != nil {\n\t\tlog.Fatalf(\"could not teardown postgres container: %v\", err)\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\tsrv := New()\n\tif srv == nil {\n\t\tt.Fatal(\"New() returned nil\")\n\t}\n}\n\nfunc TestHealth(t *testing.T) {\n\tsrv := New()\n\n\tstats := srv.Health()\n\n\tif stats[\"status\"] != \"up\" {\n\t\tt.Fatalf(\"expected status to be up, got %s\", stats[\"status\"])\n\t}\n\n\tif _, ok := stats[\"error\"]; ok {\n\t\tt.Fatalf(\"expected error not to be present\")\n\t}\n\n\tif stats[\"message\"] != \"It's healthy\" {\n\t\tt.Fatalf(\"expected message to be 'It's healthy', got %s\", stats[\"message\"])\n\t}\n}\n\nfunc TestClose(t *testing.T) {\n\tsrv := New()\n\n\tif srv.Close() != nil {\n\t\tt.Fatalf(\"expected Close() to return nil\")\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/tests/redis.tmpl",
    "content": "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/testcontainers/testcontainers-go/modules/redis\"\n)\n\nfunc mustStartRedisContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) {\n\tdbContainer, err := redis.Run(\n\t\tcontext.Background(),\n\t\t\"docker.io/redis:7.2.4\",\n\t\tredis.WithSnapshotting(10, 1),\n\t\tredis.WithLogLevel(redis.LogLevelVerbose),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbHost, err := dbContainer.Host(context.Background())\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\tdbPort, err := dbContainer.MappedPort(context.Background(), \"6379/tcp\")\n\tif err != nil {\n\t\treturn dbContainer.Terminate, err\n\t}\n\n\taddress = dbHost\n\tport = dbPort.Port()\n\tdatabase = \"0\"\n\n\treturn dbContainer.Terminate, err\n}\n\nfunc TestMain(m *testing.M) {\n\tteardown, err := mustStartRedisContainer()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not start redis container: %v\", err)\n\t}\n\n\tm.Run()\n\n\tif teardown != nil && teardown(context.Background()) != nil {\n\t\tlog.Fatalf(\"could not teardown redis container: %v\", err)\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\tsrv := New()\n\tif srv == nil {\n\t\tt.Fatal(\"New() returned nil\")\n\t}\n}\n\nfunc TestHealth(t *testing.T) {\n\tsrv := New()\n\n\tstats := srv.Health()\n\n\tif stats[\"redis_status\"] != \"up\" {\n\t\tt.Fatalf(\"expected status to be up, got %s\", stats[\"redis_status\"])\n\t}\n\n\tif _, ok := stats[\"redis_version\"]; !ok {\n\t\tt.Fatalf(\"expected redis_version to be present, got %v\", stats[\"redis_version\"])\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/files/tests/scylla.tmpl",
    "content": "package database\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/docker/go-connections/nat\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n\t\"io\"\n\t\"log\"\n\t\"strings\"\n\t\"testing\"\n)\n\nconst (\n\tport = nat.Port(\"19042/tcp\")\n)\n\nfunc mustStartScyllaDBContainer() (testcontainers.Container, error) {\n\n\t// Define the container\n\timage := \"scylladb/scylla:6.2\"\n\texposedPorts := []string{\"9042/tcp\", \"19042/tcp\"}\n\tcommands := []string{\n\t\t\"--smp=2\",\n\t\t\"--memory=1G\",\n\t\t\"--developer-mode=1\",\n\t\t\"--overprovisioned=1\",\n\t}\n\n\treq := testcontainers.ContainerRequest{\n\t\tFromDockerfile: testcontainers.FromDockerfile{},\n\t\tImage:          image,\n\t\tExposedPorts:   exposedPorts,\n\t\tCmd:            commands,\n\t\tWaitingFor: wait.ForAll(\n\t\t\twait.ForLog(\".*initialization completed.\").AsRegexp(),\n\t\t\twait.ForListeningPort(port),\n\t\t\twait.ForExec([]string{\"cqlsh\", \"-e\", \"SELECT bootstrapped FROM system.local\"}).WithResponseMatcher(func(body io.Reader) bool {\n\t\t\t\tdata, _ := io.ReadAll(body)\n\t\t\t\treturn strings.Contains(string(data), \"COMPLETED\")\n\t\t\t}),\n\t\t),\n\t}\n\n\t// Start the container\n\tscyllaDBContainer, err := testcontainers.GenericContainer(\n\t\tcontext.Background(), testcontainers.GenericContainerRequest{\n\t\t\tContainerRequest: req,\n\t\t\tStarted:          true,\n\t\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmappedPort, err := scyllaDBContainer.MappedPort(context.Background(), \"19042/tcp\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thosts = fmt.Sprintf(\"localhost:%v\", mappedPort.Port())\n\n\treturn scyllaDBContainer, nil\n}\n\nfunc TestMain(m *testing.M) {\n\n\tcontainer, err := mustStartScyllaDBContainer()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not start scylla container: %v\", err)\n\t}\n\n\tm.Run()\n\n\terr = container.Terminate(context.Background())\n\tif err != nil {\n\t\treturn\n\t}\n\n}\n\nfunc TestNew(t *testing.T) {\n\tsrv := New()\n\tif srv == nil {\n\t\tt.Fatal(\"New() returned nil\")\n\t}\n\n\terr := srv.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"expected Close() to return nil\")\n\t}\n}\n\nfunc TestHealth(t *testing.T) {\n\tsrv := New()\n\n\tstats := srv.Health()\n\n\tif stats[\"status\"] != \"up\" {\n\t\tt.Fatalf(\"expected status to be up, got %s\", stats[\"status\"])\n\t}\n\n\tif _, ok := stats[\"error\"]; ok {\n\t\tt.Fatalf(\"expected error not to be present\")\n\t}\n\n\tif stats[\"message\"] != \"It's healthy\" {\n\t\tt.Fatalf(\"expected message to be 'It's healthy', got %s\", stats[\"message\"])\n\t}\n\n\tif stats[\"scylla_cluster_nodes_up\"] != \"1\" {\n\t\tt.Fatalf(\"expected nodes up  '1', got %s\", stats[\"scylla_cluster_nodes_up\"])\n\t}\n\n\tif stats[\"scylla_cluster_nodes_down\"] != \"0\" {\n\t\tt.Fatalf(\"expected nodes down '0', got %s\", stats[\"scylla_cluster_nodes_down\"])\n\t}\n\n\tif stats[\"scylla_current_datacenter\"] != \"datacenter1\" {\n\t\tt.Fatalf(\"expected connected dc 'datacenter', got %s\", stats[\"scylla_current_datacenter\"])\n\t}\n\n\terr := srv.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"expected Close() to return nil\")\n\t}\n}\n\nfunc TestClose(t *testing.T) {\n\tsrv := New()\n\n\tif srv.Close() != nil {\n\t\tt.Fatalf(\"expected Close() to return nil\")\n\t}\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/mongo.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype MongoTemplate struct{}\n\n//go:embed files/service/mongo.tmpl\nvar mongoServiceTemplate []byte\n\n//go:embed files/env/mongo.tmpl\nvar mongoEnvTemplate []byte\n\n//go:embed files/tests/mongo.tmpl\nvar mongoTestcontainersTemplate []byte\n\nfunc (m MongoTemplate) Service() []byte {\n\treturn mongoServiceTemplate\n}\n\nfunc (m MongoTemplate) Env() []byte {\n\treturn mongoEnvTemplate\n}\n\nfunc (m MongoTemplate) Tests() []byte {\n\treturn mongoTestcontainersTemplate\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/mysql.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype MysqlTemplate struct{}\n\n//go:embed files/service/mysql.tmpl\nvar mysqlServiceTemplate []byte\n\n//go:embed files/env/mysql.tmpl\nvar mysqlEnvTemplate []byte\n\n//go:embed files/tests/mysql.tmpl\nvar mysqlTestcontainersTemplate []byte\n\nfunc (m MysqlTemplate) Service() []byte {\n\treturn mysqlServiceTemplate\n}\n\nfunc (m MysqlTemplate) Env() []byte {\n\treturn mysqlEnvTemplate\n}\n\nfunc (m MysqlTemplate) Tests() []byte {\n\treturn mysqlTestcontainersTemplate\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/postgres.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype PostgresTemplate struct{}\n\n//go:embed files/service/postgres.tmpl\nvar postgresServiceTemplate []byte\n\n//go:embed files/env/postgres.tmpl\nvar postgresEnvTemplate []byte\n\n//go:embed files/tests/postgres.tmpl\nvar postgresTestcontainersTemplate []byte\n\nfunc (m PostgresTemplate) Service() []byte {\n\treturn postgresServiceTemplate\n}\n\nfunc (m PostgresTemplate) Env() []byte {\n\treturn postgresEnvTemplate\n}\n\nfunc (m PostgresTemplate) Tests() []byte {\n\treturn postgresTestcontainersTemplate\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/redis.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype RedisTemplate struct{}\n\n//go:embed files/service/redis.tmpl\nvar redisServiceTemplate []byte\n\n//go:embed files/env/redis.tmpl\nvar redisEnvTemplate []byte\n\n//go:embed files/tests/redis.tmpl\nvar redisTestcontainersTemplate []byte\n\nfunc (r RedisTemplate) Service() []byte {\n\treturn redisServiceTemplate\n}\n\nfunc (r RedisTemplate) Env() []byte {\n\treturn redisEnvTemplate\n}\n\nfunc (r RedisTemplate) Tests() []byte {\n\treturn redisTestcontainersTemplate\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/scylla.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype ScyllaTemplate struct{}\n\n//go:embed files/service/scylla.tmpl\nvar scyllaServiceTemplate []byte\n\n//go:embed files/env/scylla.tmpl\nvar scyllaEnvTemplate []byte\n\n//go:embed files/tests/scylla.tmpl\nvar scyllaTestcontainersTemplate []byte\n\nfunc (r ScyllaTemplate) Service() []byte {\n\treturn scyllaServiceTemplate\n}\n\nfunc (r ScyllaTemplate) Env() []byte {\n\treturn scyllaEnvTemplate\n}\n\nfunc (r ScyllaTemplate) Tests() []byte {\n\treturn scyllaTestcontainersTemplate\n}\n"
  },
  {
    "path": "cmd/template/dbdriver/sqlite.go",
    "content": "package dbdriver\n\nimport (\n\t_ \"embed\"\n)\n\ntype SqliteTemplate struct{}\n\n//go:embed files/service/sqlite.tmpl\nvar sqliteServiceTemplate []byte\n\n//go:embed files/env/sqlite.tmpl\nvar sqliteEnvTemplate []byte\n\nfunc (m SqliteTemplate) Service() []byte {\n\treturn sqliteServiceTemplate\n}\n\nfunc (m SqliteTemplate) Env() []byte {\n\treturn sqliteEnvTemplate\n}\n\nfunc (m SqliteTemplate) Tests() []byte {\n\treturn []byte{}\n}\n"
  },
  {
    "path": "cmd/template/docker/files/docker-compose/mongo.tmpl",
    "content": "services:\n{{- if .AdvancedOptions.docker }}\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}\n      BLUEPRINT_DB_PORT:  ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}\n    depends_on:\n      mongo_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n{{- end }}\n{{- if and .AdvancedOptions.react .AdvancedOptions.docker }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n{{- end }}\n  mongo_bp:\n    image: mongo:latest\n    restart: unless-stopped\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      MONGO_INITDB_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:27017\"\n    volumes:\n      - mongo_volume_bp:/data/db\n    {{- if .AdvancedOptions.docker }}\n    healthcheck:\n      test: [\"CMD\",\"mongosh\", \"--eval\", \"db.adminCommand('ping')\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s\n    networks:\n      - blueprint\n    {{- end }}\n\nvolumes:\n  mongo_volume_bp:\n{{- if .AdvancedOptions.docker }}\nnetworks:\n  blueprint:\n{{- end }}\n"
  },
  {
    "path": "cmd/template/docker/files/docker-compose/mysql.tmpl",
    "content": "services:\n{{- if .AdvancedOptions.docker }}\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n    depends_on:\n      mysql_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n{{- end }}\n{{- if and .AdvancedOptions.react .AdvancedOptions.docker }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n{{- end }}\n  mysql_bp:\n    image: mysql:latest\n    restart: unless-stopped\n    environment:\n      MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      MYSQL_USER: ${BLUEPRINT_DB_USERNAME}\n      MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:3306\"\n    volumes:\n      - mysql_volume_bp:/var/lib/mysql\n    {{- if .AdvancedOptions.docker }}\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"${BLUEPRINT_DB_HOST}\", \"-u\", \"${BLUEPRINT_DB_USERNAME}\", \"--password=${BLUEPRINT_DB_PASSWORD}\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s \n    networks:\n      - blueprint\n    {{- end }}\n\nvolumes:\n  mysql_volume_bp:\n{{- if .AdvancedOptions.docker }}\nnetworks:\n  blueprint:\n{{- end }}\n"
  },
  {
    "path": "cmd/template/docker/files/docker-compose/postgres.tmpl",
    "content": "services:\n{{- if .AdvancedOptions.docker }}\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      BLUEPRINT_DB_SCHEMA: ${BLUEPRINT_DB_SCHEMA}\n    depends_on:\n      psql_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n{{- end }}\n{{- if and .AdvancedOptions.react .AdvancedOptions.docker }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n{{- end }}\n  psql_bp:\n    image: postgres:latest\n    restart: unless-stopped\n    environment:\n      POSTGRES_DB: ${BLUEPRINT_DB_DATABASE}\n      POSTGRES_USER: ${BLUEPRINT_DB_USERNAME}\n      POSTGRES_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:5432\"\n    volumes:\n      - psql_volume_bp:/var/lib/postgresql/data\n    {{- if .AdvancedOptions.docker }}\n    healthcheck:\n      test: [\"CMD-SHELL\", \"sh -c 'pg_isready -U ${BLUEPRINT_DB_USERNAME} -d ${BLUEPRINT_DB_DATABASE}'\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s\n    networks:\n      - blueprint\n    {{- end }}\n\nvolumes:\n  psql_volume_bp:\n{{- if .AdvancedOptions.docker }}\nnetworks:\n  blueprint:\n{{- end }}\n"
  },
  {
    "path": "cmd/template/docker/files/docker-compose/redis.tmpl",
    "content": "services:\n{{- if .AdvancedOptions.docker }}\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_ADDRESS: ${BLUEPRINT_DB_ADDRESS}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}\n    depends_on:\n      redis_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n{{- end }}\n{{- if and .AdvancedOptions.react .AdvancedOptions.docker }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n{{- end }}\n  redis_bp:\n    image: redis:7.2.4\n    restart: unless-stopped\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:6379\"\n    {{- if .AdvancedOptions.docker }}\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"ping\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s\n    networks:\n      - blueprint\n\nnetworks:\n  blueprint:\n{{- end }}\n"
  },
  {
    "path": "cmd/template/docker/files/docker-compose/scylla.tmpl",
    "content": "services:\n{{- if .AdvancedOptions.docker }}\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOSTS: ${BLUEPRINT_DB_HOSTS}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_CONSISTENCY: ${BLUEPRINT_DB_CONSISTENCY}\n      BLUEPRINT_DB_KEYSPACE: ${BLUEPRINT_DB_KEYSPACE}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      BLUEPRINT_DB_CONNECTIONS: ${BLUEPRINT_DB_CONNECTIONS}\n    depends_on:\n      scylla_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n{{- end }}\n{{- if and .AdvancedOptions.react .AdvancedOptions.docker }}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n{{- end }}\n  scylla_bp:\n    image: scylladb/scylla:6.2\n    restart: unless-stopped\n    command:\n      - --smp=2\n      - --memory=1GB\n      - --overprovisioned=1\n      - --developer-mode=1 # Disable for production\n      - --seeds=scylla_bp\n    ports:\n      - \"9042:9042\"\n      - \"19042:19042\"\n    volumes:\n      - scylla_bp:/var/lib/scylla\n    {{- if .AdvancedOptions.docker }}\n    healthcheck:\n      test: [\"CMD-SHELL\", 'cqlsh -e \"SHOW VERSION\" || exit 1']\n      interval: 15s\n      timeout: 30s\n      retries: 15\n      start_period: 30s\n    networks:\n      - blueprint\n    {{- end }}\nvolumes:\n  scylla_bp:\n{{- if .AdvancedOptions.docker }}\nnetworks:\n  blueprint:\n{{- end }}"
  },
  {
    "path": "cmd/template/docker/mongo.go",
    "content": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype MongoDockerTemplate struct{}\n\n//go:embed files/docker-compose/mongo.tmpl\nvar mongoDockerTemplate []byte\n\nfunc (m MongoDockerTemplate) Docker() []byte {\n\treturn mongoDockerTemplate\n}"
  },
  {
    "path": "cmd/template/docker/mysql.go",
    "content": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype MysqlDockerTemplate struct{}\n\n//go:embed files/docker-compose/mysql.tmpl\nvar mysqlDockerTemplate []byte\n\nfunc (m MysqlDockerTemplate) Docker() []byte {\n\treturn mysqlDockerTemplate\n}"
  },
  {
    "path": "cmd/template/docker/postgres.go",
    "content": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype PostgresDockerTemplate struct{}\n\n//go:embed files/docker-compose/postgres.tmpl\nvar postgresDockerTemplate []byte\n\nfunc (m PostgresDockerTemplate) Docker() []byte {\n\treturn postgresDockerTemplate\n}"
  },
  {
    "path": "cmd/template/docker/redis.go",
    "content": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype RedisDockerTemplate struct{}\n\n//go:embed files/docker-compose/redis.tmpl\nvar redisDockerTemplate []byte\n\nfunc (r RedisDockerTemplate) Docker() []byte {\n\treturn redisDockerTemplate\n}\n"
  },
  {
    "path": "cmd/template/docker/scylla.go",
    "content": "package docker\n\nimport (\n\t_ \"embed\"\n)\n\ntype ScyllaDockerTemplate struct{}\n\n//go:embed files/docker-compose/scylla.tmpl\nvar scyllaDockerTemplate []byte\n\nfunc (r ScyllaDockerTemplate) Docker() []byte {\n\treturn scyllaDockerTemplate\n}\n"
  },
  {
    "path": "cmd/template/framework/chiRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/chi.go.tmpl\nvar chiRoutesTemplate []byte\n\n//go:embed files/tests/default-test.go.tmpl\nvar chiTestHandlerTemplate []byte\n\n// ChiTemplates contains the methods used for building\n// an app that uses [github.com/go-chi/chi]\ntype ChiTemplates struct{}\n\nfunc (c ChiTemplates) Main() []byte {\n\treturn mainTemplate\n}\n\nfunc (c ChiTemplates) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (c ChiTemplates) Routes() []byte {\n\treturn chiRoutesTemplate\n}\n\nfunc (c ChiTemplates) TestHandler() []byte {\n\treturn chiTestHandlerTemplate\n}\n\nfunc (c ChiTemplates) HtmxTemplImports() []byte {\n\treturn advanced.StdLibHtmxTemplImportsTemplate()\n}\n\nfunc (c ChiTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.ChiHtmxTemplRoutesTemplate()\n}\n\nfunc (c ChiTemplates) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/echoRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/echo.go.tmpl\nvar echoRoutesTemplate []byte\n\n//go:embed files/tests/echo-test.go.tmpl\nvar echoTestHandlerTemplate []byte\n\n// EchoTemplates contains the methods used for building\n// an app that uses [github.com/labstack/echo]\ntype EchoTemplates struct{}\n\nfunc (e EchoTemplates) Main() []byte {\n\treturn mainTemplate\n}\nfunc (e EchoTemplates) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (e EchoTemplates) Routes() []byte {\n\treturn echoRoutesTemplate\n}\n\nfunc (e EchoTemplates) TestHandler() []byte {\n\treturn echoTestHandlerTemplate\n}\n\nfunc (e EchoTemplates) HtmxTemplImports() []byte {\n\treturn advanced.StdLibHtmxTemplImportsTemplate()\n}\n\nfunc (e EchoTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.EchoHtmxTemplRoutesTemplate()\n}\n\nfunc (e EchoTemplates) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/fiberServer.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/fiber.go.tmpl\nvar fiberRoutesTemplate []byte\n\n//go:embed files/server/fiber.go.tmpl\nvar fiberServerTemplate []byte\n\n//go:embed files/main/fiber_main.go.tmpl\nvar fiberMainTemplate []byte\n\n//go:embed files/tests/fiber-test.go.tmpl\nvar fiberTestHandlerTemplate []byte\n\n// FiberTemplates contains the methods used for building\n// an app that uses [github.com/gofiber/fiber]\ntype FiberTemplates struct{}\n\nfunc (f FiberTemplates) Main() []byte {\n\treturn fiberMainTemplate\n}\nfunc (f FiberTemplates) Server() []byte {\n\treturn fiberServerTemplate\n}\n\nfunc (f FiberTemplates) Routes() []byte {\n\treturn fiberRoutesTemplate\n}\n\nfunc (f FiberTemplates) TestHandler() []byte {\n\treturn fiberTestHandlerTemplate\n}\n\nfunc (f FiberTemplates) HtmxTemplImports() []byte {\n\treturn advanced.FiberHtmxTemplImportsTemplate()\n}\n\nfunc (f FiberTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.FiberHtmxTemplRoutesTemplate()\n}\n\nfunc (f FiberTemplates) WebsocketImports() []byte {\n\treturn advanced.FiberWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/files/README.md.tmpl",
    "content": "# Project {{.ProjectName}}\n\nOne Paragraph of project description goes here\n\n## Getting Started\n\nThese instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.\n\n## MakeFile\n\nRun build make command with tests\n```bash\nmake all\n```\n\nBuild the application\n```bash\nmake build\n```\n\nRun the application\n```bash\nmake run\n```\n\n{{- if or .AdvancedOptions.docker (and (ne .DBDriver \"none\") (ne .DBDriver \"sqlite\")) }}\nCreate DB container\n```bash\nmake docker-run\n```\n\nShutdown DB Container\n```bash\nmake docker-down\n```\n\nDB Integrations Test:\n```bash\nmake itest\n```\n{{- end }}\n\nLive reload the application:\n```bash\nmake watch\n```\n\nRun the test suite:\n```bash\nmake test\n```\n\nClean up binary from the last build:\n```bash\nmake clean\n```\n"
  },
  {
    "path": "cmd/template/framework/files/air.toml.tmpl",
    "content": "root = \".\"\ntestdata_dir = \"testdata\"\ntmp_dir = \"tmp\"\n\n[build]\n  args_bin = []\n  bin = {{if .OSCheck.UnixBased }}\"./main\"{{ else }}\".\\\\main.exe\"{{ end }}\n  cmd = \"make build\"\n  delay = 1000\n  exclude_dir = [\"assets\", \"tmp\", \"vendor\", \"testdata\", \"node_modules\"]\n  exclude_file = []\n  exclude_regex = [\"_test.go\"{{if .AdvancedOptions.htmx}}, \".*_templ.go\"{{end}}]\n  exclude_unchanged = false\n  follow_symlink = false\n  full_bin = \"\"\n  include_dir = []\n  include_ext = [\"go\", \"tpl\", \"tmpl\", \"html\"{{if .AdvancedOptions.htmx}}, \"templ\"{{end}}]\n  include_file = []\n  kill_delay = \"0s\"\n  log = \"build-errors.log\"\n  poll = false\n  poll_interval = 0\n  post_cmd = []\n  pre_cmd = []\n  rerun = false\n  rerun_delay = 500\n  send_interrupt = false\n  stop_on_error = false\n\n[color]\n  app = \"\"\n  build = \"yellow\"\n  main = \"magenta\"\n  runner = \"green\"\n  watcher = \"cyan\"\n\n[log]\n  main_only = false\n  time = false\n\n[misc]\n  clean_on_exit = false\n\n[screen]\n  clear_on_rebuild = false\n  keep_scroll = true\n"
  },
  {
    "path": "cmd/template/framework/files/gitignore.tmpl",
    "content": "# 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# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Go workspace file\ngo.work\ntmp/\n\n# IDE specific files\n.vscode\n.idea\n\n# .env file\n.env\n\n# Project build\nmain\n*templ.go\n\n# OS X generated file\n.DS_Store\n{{if ( .AdvancedOptions.tailwind )}}\n\n# Tailwind CSS\ncmd/web/assets/css/output.css\ntailwindcss\n{{end}}\n"
  },
  {
    "path": "cmd/template/framework/files/globalenv.tmpl",
    "content": "PORT=8080\nAPP_ENV=local\n"
  },
  {
    "path": "cmd/template/framework/files/main/fiber_main.go.tmpl",
    "content": "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}}/internal/server\"\n\n\t_ \"github.com/joho/godotenv/autoload\"\n)\n\nfunc gracefulShutdown(fiberServer *server.FiberServer, done chan bool) {\n\t// Create context that listens for the interrupt signal from the OS.\n\tctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)\n\tdefer stop()\n\n\t// Listen for the interrupt signal.\n\t<-ctx.Done()\n\n\tlog.Println(\"shutting down gracefully, press Ctrl+C again to force\")\n\tstop() // Allow Ctrl+C to force shutdown\n\n\t// The context is used to inform the server it has 5 seconds to finish\n\t// the request it is currently handling\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tif err := fiberServer.ShutdownWithContext(ctx); err != nil {\n\t\tlog.Printf(\"Server forced to shutdown with error: %v\", err)\n\t}\n\n\tlog.Println(\"Server exiting\")\n\n\t// Notify the main goroutine that the shutdown is complete\n\tdone <- true\n}\n\nfunc main() {\n\n\tserver := server.New()\n\n\tserver.RegisterFiberRoutes()\n\n\t// Create a done channel to signal when the shutdown is complete\n\tdone := make(chan bool, 1)\n\n\tgo func() {\n\t\tport, _ := strconv.Atoi(os.Getenv(\"PORT\"))\n\t\terr := server.Listen(fmt.Sprintf(\":%d\", port))\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"http server error: %s\", err))\n\t\t}\n\t}()\n\n\t// Run graceful shutdown in a separate goroutine\n\tgo gracefulShutdown(server, done)\n\n\t// Wait for the graceful shutdown to complete\n\t<-done\n\tlog.Println(\"Graceful shutdown complete.\")\n}\n"
  },
  {
    "path": "cmd/template/framework/files/main/main.go.tmpl",
    "content": "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/server\"\n)\n\nfunc gracefulShutdown(apiServer *http.Server, done chan bool) {\n\t// Create context that listens for the interrupt signal from the OS.\n\tctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)\n\tdefer stop()\n\n\t// Listen for the interrupt signal.\n\t<-ctx.Done()\n\n\tlog.Println(\"shutting down gracefully, press Ctrl+C again to force\")\n\tstop() // Allow Ctrl+C to force shutdown\n\n\t// The context is used to inform the server it has 5 seconds to finish\n\t// the request it is currently handling\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tif err := apiServer.Shutdown(ctx); err != nil {\n\t\tlog.Printf(\"Server forced to shutdown with error: %v\", err)\n\t}\n\n\tlog.Println(\"Server exiting\")\n\n\t// Notify the main goroutine that the shutdown is complete\n\tdone <- true\n}\n\nfunc main() {\n\n\tserver := server.NewServer()\n\n\t// Create a done channel to signal when the shutdown is complete\n\tdone := make(chan bool, 1)\n\n\t// Run graceful shutdown in a separate goroutine\n\tgo gracefulShutdown(server, done)\n\n\terr := server.ListenAndServe()\n\tif err != nil && err != http.ErrServerClosed {\n\t\tpanic(fmt.Sprintf(\"http server error: %s\", err))\n\t}\n\n\t// Wait for the graceful shutdown to complete\n\t<-done\n\tlog.Println(\"Graceful shutdown complete.\")\n}\n"
  },
  {
    "path": "cmd/template/framework/files/makefile.tmpl",
    "content": "# Simple Makefile for a Go project\n\n# Build the application\nall: build test\n\n{{- if and (or .AdvancedOptions.htmx .AdvancedOptions.tailwind) (not .AdvancedOptions.react) }}\n{{- if .OSCheck.UnixBased }}\ntempl-install:\n\t@if ! command -v templ > /dev/null; then \\\n\t\tread -p \"Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] \" choice; \\\n\t\tif [ \"$$choice\" != \"n\" ] && [ \"$$choice\" != \"N\" ]; then \\\n\t\t\tgo install github.com/a-h/templ/cmd/templ@latest; \\\n\t\t\tif [ ! -x \"$$(command -v templ)\" ]; then \\\n\t\t\t\techo \"templ installation failed. Exiting...\"; \\\n\t\t\t\texit 1; \\\n\t\t\tfi; \\\n\t\telse \\\n\t\t\techo \"You chose not to install templ. Exiting...\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\tfi\n{{- else }}\ntempl-install:\n\t@powershell -ExecutionPolicy Bypass -Command \"if (Get-Command templ -ErrorAction SilentlyContinue) { \\\n\t\t; \\\n\t} else { \\\n\t\tWrite-Output 'Installing templ...'; \\\n\t\tgo install github.com/a-h/templ/cmd/templ@latest; \\\n\t\tif (-not (Get-Command templ -ErrorAction SilentlyContinue)) { \\\n\t\t\tWrite-Output 'templ installation failed. Exiting...'; \\\n\t\t\texit 1; \\\n\t\t} else { \\\n\t\t\tWrite-Output 'templ installed successfully.'; \\\n\t\t} \\\n\t}\"\n{{- end }}\n{{- end }}\n\n{{- if and .AdvancedOptions.tailwind (not .AdvancedOptions.react) }}\n{{- if .OSCheck.UnixBased}}\ntailwind-install:\n\t{{ if .OSCheck.linux }}@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss; fi{{- end }}\n\t{{ if .OSCheck.darwin }}@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-x64 -o tailwindcss; fi{{- end }}\n\t@chmod +x tailwindcss\n{{- else }}\ntailwind-install:\n\t@if not exist tailwindcss.exe powershell -ExecutionPolicy Bypass -Command \"Invoke-WebRequest -Uri 'https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-windows-x64.exe' -OutFile 'tailwindcss.exe'\"{{- end }}\n{{- end }}\n\nbuild:{{- if and .AdvancedOptions.tailwind (not .AdvancedOptions.react) }} tailwind-install{{- end }}{{- if and (or .AdvancedOptions.htmx .AdvancedOptions.tailwind) (not .AdvancedOptions.react) }} templ-install{{- end }}\n\t@echo \"Building...\"\n\t{{ if and (or .AdvancedOptions.htmx .AdvancedOptions.tailwind) (not .AdvancedOptions.react) }}@templ generate{{- end }}\n\t{{ if and .AdvancedOptions.tailwind (not .AdvancedOptions.react) }}@{{ if .OSCheck.UnixBased }}./tailwindcss{{ else }}.\\tailwindcss.exe{{ end }} -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css{{ end }}\n\t{{ if .OSCheck.UnixBased }}@{{- if and (.AdvancedOptions.docker) (eq .DBDriver \"sqlite\") }}CGO_ENABLED=1 GOOS=linux {{ end }}go build -o main cmd/api/main.go{{- else }}@go build -o main.exe cmd/api/main.go{{- end }}\n\n# Run the application\nrun:\n\t@go run cmd/api/main.go{{- if .AdvancedOptions.react }} &\n\t@npm install --prefer-offline --no-fund --prefix ./frontend\n\t@npm run dev --prefix ./frontend\n\t{{- end }}\n\n\n{{- if or .AdvancedOptions.docker (and (ne .DBDriver \"none\") (ne .DBDriver \"sqlite\")) }}\n{{- if .OSCheck.UnixBased }}\n# Create DB container\ndocker-run:\n\t@if docker compose up --build 2>/dev/null; then \\\n\t\t: ; \\\n\telse \\\n\t\techo \"Falling back to Docker Compose V1\"; \\\n\t\tdocker-compose up --build; \\\n\tfi\n\n# Shutdown DB container\ndocker-down:\n\t@if docker compose down 2>/dev/null; then \\\n\t\t: ; \\\n\telse \\\n\t\techo \"Falling back to Docker Compose V1\"; \\\n\t\tdocker-compose down; \\\n\tfi\n{{- else }}\n# Create DB container\ndocker-run:\n\t@docker compose up --build\n\n# Shutdown DB container\ndocker-down:\n\t@docker compose down\n{{- end }}\n{{- end }}\n\n# Test the application\ntest:\n\t@echo \"Testing...\"\n\t@go test ./... -v\n\n{{- if and (ne .DBDriver \"none\") (ne .DBDriver \"sqlite\") }}\n# Integrations Tests for the application\nitest:\n\t@echo \"Running integration tests...\"\n\t@go test ./internal/database -v\n{{- end }}\n\n# Clean the binary\nclean:\n\t@echo \"Cleaning...\"\n\t@rm -f main\n\n# Live Reload\n{{- if .OSCheck.UnixBased }}\nwatch:\n\t@if command -v air > /dev/null; then \\\n            air; \\\n            echo \"Watching...\";\\\n        else \\\n            read -p \"Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] \" choice; \\\n            if [ \"$$choice\" != \"n\" ] && [ \"$$choice\" != \"N\" ]; then \\\n                go install github.com/air-verse/air@latest; \\\n                air; \\\n                echo \"Watching...\";\\\n            else \\\n                echo \"You chose not to install air. Exiting...\"; \\\n                exit 1; \\\n            fi; \\\n        fi\n{{- else }}\nwatch:\n\t@powershell -ExecutionPolicy Bypass -Command \"if (Get-Command air -ErrorAction SilentlyContinue) { \\\n\t\tair; \\\n\t\tWrite-Output 'Watching...'; \\\n\t} else { \\\n\t\tWrite-Output 'Installing air...'; \\\n\t\tgo install github.com/air-verse/air@latest; \\\n\t\tair; \\\n\t\tWrite-Output 'Watching...'; \\\n\t}\"\n{{- end }}\n\n.PHONY: all build run test clean watch{{- if and (not .AdvancedOptions.react) .AdvancedOptions.tailwind }} tailwind-install{{- end }}{{- if and (ne .DBDriver \"none\") (ne .DBDriver \"sqlite\") }} docker-run docker-down itest{{- end }}{{- if and (or .AdvancedOptions.htmx .AdvancedOptions.tailwind) (not .AdvancedOptions.react) }} templ-install{{- end }}\n"
  },
  {
    "path": "cmd/template/framework/files/routes/chi.go.tmpl",
    "content": "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}}\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/go-chi/chi/v5/middleware\"\n\t\"github.com/go-chi/cors\"\n  {{.AdvancedTemplates.TemplateImports}}\n\n)\n\nfunc (s *Server) RegisterRoutes() http.Handler {\n\tr := chi.NewRouter()\n\tr.Use(middleware.Logger)\n\n\tr.Use(cors.Handler(cors.Options{\n\t\tAllowedOrigins:   []string{\"https://*\", \"http://*\"},\n\t\tAllowedMethods:   []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"},\n\t\tAllowedHeaders:   []string{\"Accept\", \"Authorization\", \"Content-Type\"},\n\t\tAllowCredentials: true,\n\t\tMaxAge:           300,\n\t}))\n\n\tr.Get(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\tr.Get(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\tr.Get(\"/websocket\", s.websocketHandler)\n  {{end}}\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\treturn r\n}\n\nfunc (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {\n\tresp := make(map[string]string)\n\tresp[\"message\"] = \"Hello World\"\n\n\tjsonResp, err := json.Marshal(resp)\n\tif err != nil {\n\t\tlog.Fatalf(\"error handling JSON marshal. Err: %v\", err)\n\t}\n\n\t_, _ = w.Write(jsonResp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {\n\tjsonResp, _ := json.Marshal(s.db.Health())\n\t_, _ = w.Write(jsonResp)\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/echo.go.tmpl",
    "content": "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/labstack/echo/v4\"\n\t\"github.com/labstack/echo/v4/middleware\"\n    {{.AdvancedTemplates.TemplateImports}}\n)\nfunc (s *Server) RegisterRoutes() http.Handler {\n\te := echo.New()\n\te.Use(middleware.Logger())\n\te.Use(middleware.Recover())\n\n\te.Use(middleware.CORSWithConfig(middleware.CORSConfig{\n\t\tAllowOrigins:     []string{\"https://*\", \"http://*\"},\n\t\tAllowMethods:     []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"},\n\t\tAllowHeaders:     []string{\"Accept\", \"Authorization\", \"Content-Type\", \"X-CSRF-Token\"},\n\t\tAllowCredentials: true,\n\t\tMaxAge:           300,\n\t}))\n\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\te.GET(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\te.GET(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\te.GET(\"/websocket\", s.websocketHandler)\n  {{end}}\n\n\treturn e\n}\n\nfunc (s *Server) HelloWorldHandler(c echo.Context) error {\n\tresp := map[string]string{\n\t\t\"message\": \"Hello World\",\n\t}\n\n\treturn c.JSON(http.StatusOK, resp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(c echo.Context) error {\n\treturn c.JSON(http.StatusOK, s.db.Health())\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(c echo.Context) error {\n\tw := c.Response().Writer\n\tr := c.Request()\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn nil\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n\treturn nil\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/fiber.go.tmpl",
    "content": "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/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/middleware/cors\"\n  {{.AdvancedTemplates.TemplateImports}}\n)\n\nfunc (s *FiberServer) RegisterFiberRoutes() {\n\t// Apply CORS middleware\n\ts.App.Use(cors.New(cors.Config{\n\t\tAllowOrigins:     \"*\",\n\t\tAllowMethods:     \"GET,POST,PUT,DELETE,OPTIONS,PATCH\",\n\t\tAllowHeaders:     \"Accept,Authorization,Content-Type\",\n\t\tAllowCredentials: false, // credentials require explicit origins\n\t\tMaxAge:           300,\n\t}))\n\n\ts.App.Get(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\ts.App.Get(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\ts.App.Get(\"/websocket\", websocket.New(s.websocketHandler))\n  {{end}}\n\n  {{.AdvancedTemplates.TemplateRoutes}}\n}\n\nfunc (s *FiberServer) HelloWorldHandler(c *fiber.Ctx) error {\n\tresp := fiber.Map{\n\t\t\"message\": \"Hello World\",\n\t}\n\n\treturn c.JSON(resp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *FiberServer) healthHandler(c *fiber.Ctx) error {\n\treturn c.JSON(s.db.Health())\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *FiberServer) websocketHandler(con *websocket.Conn) {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tgo func() {\n\t\tfor {\n\t\t\t_, _, err := con.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tcancel()\n\t\t\t\tlog.Println(\"Receiver Closing\", err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\t\tif err := con.WriteMessage(websocket.TextMessage, []byte(payload)); err != nil {\n\t\t\t\tlog.Printf(\"could not write to socket: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(time.Second * 2)\n\t\t}\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/gin.go.tmpl",
    "content": "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/gin-gonic/gin\"\n\t\"github.com/gin-contrib/cors\"\n\n  {{.AdvancedTemplates.TemplateImports}}\n)\n\nfunc (s *Server) RegisterRoutes() http.Handler {\n\tr := gin.Default()\n\n\tr.Use(cors.New(cors.Config{\n\t\tAllowOrigins:     []string{\"http://localhost:5173\"}, // Add your frontend URL\n\t\tAllowMethods:     []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"},\n\t\tAllowHeaders:     []string{\"Accept\", \"Authorization\", \"Content-Type\"},\n\t\tAllowCredentials: true, // Enable cookies/auth\n\t}))\n\n\tr.GET(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\tr.GET(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\tr.GET(\"/websocket\", s.websocketHandler)\n  {{end}}\n\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\treturn r\n}\n\nfunc (s *Server) HelloWorldHandler(c *gin.Context) {\n\tresp := make(map[string]string)\n\tresp[\"message\"] = \"Hello World\"\n\n\tc.JSON(http.StatusOK, resp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(c *gin.Context) {\n\tc.JSON(http.StatusOK, s.db.Health())\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(c *gin.Context) {\n\tw := c.Writer\n\tr := c.Request\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/gorilla.go.tmpl",
    "content": "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}}\n\n\t\"github.com/gorilla/mux\"\n  {{.AdvancedTemplates.TemplateImports}}\n)\n\nfunc (s *Server) RegisterRoutes() http.Handler {\n\tr := mux.NewRouter()\n\n\t// Apply CORS middleware\n\tr.Use(s.corsMiddleware)\n\n\tr.HandleFunc(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\tr.HandleFunc(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\tr.HandleFunc(\"/websocket\", s.websocketHandler)\n  {{end}}\n\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\treturn r\n}\n\n// CORS middleware\nfunc (s *Server) corsMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// CORS Headers\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\") // Wildcard allows all origins\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS, PATCH\")\n\t\tw.Header().Set(\"Access-Control-Allow-Headers\", \"Accept, Authorization, Content-Type\")\n\t\tw.Header().Set(\"Access-Control-Allow-Credentials\", \"false\") // Credentials not allowed with wildcard origins\n\n\t\t// Handle preflight OPTIONS requests\n\t\tif r.Method == http.MethodOptions {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\nfunc (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {\n\tresp := make(map[string]string)\n\tresp[\"message\"] = \"Hello World\"\n\n\tjsonResp, err := json.Marshal(resp)\n\tif err != nil {\n\t\tlog.Fatalf(\"error handling JSON marshal. Err: %v\", err)\n\t}\n\n\t_, _ = w.Write(jsonResp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {\n\tjsonResp, err := json.Marshal(s.db.Health())\n\n\tif err != nil {\n\t\tlog.Fatalf(\"error handling JSON marshal. Err: %v\", err)\n\t}\n\n\t_, _ = w.Write(jsonResp)\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/http_router.go.tmpl",
    "content": "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}}\n\n\t\"github.com/julienschmidt/httprouter\"\n  {{.AdvancedTemplates.TemplateImports}}\n)\n\nfunc (s *Server) RegisterRoutes() http.Handler {\n\tr := httprouter.New()\n\n\t// Wrap all routes with CORS middleware\n\tcorsWrapper := s.corsMiddleware(r)\n\n\tr.HandlerFunc(http.MethodGet, \"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\tr.HandlerFunc(http.MethodGet, \"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\tr.HandlerFunc(http.MethodGet, \"/websocket\", s.websocketHandler)\n  {{end}}\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\treturn corsWrapper\n}\n\n// CORS middleware\nfunc (s *Server) corsMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// CORS headers\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\") // Use \"*\" for all origins, or replace with specific origins\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS, PATCH\")\n\t\tw.Header().Set(\"Access-Control-Allow-Headers\", \"Accept, Authorization, Content-Type, X-CSRF-Token\")\n\t\tw.Header().Set(\"Access-Control-Allow-Credentials\", \"false\") // Set to \"true\" if credentials are needed\n\n\t\t// Handle preflight OPTIONS requests\n\t\tif r.Method == http.MethodOptions {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\nfunc (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {\n\tresp := make(map[string]string)\n\tresp[\"message\"] = \"Hello World\"\n\n\tjsonResp, err := json.Marshal(resp)\n\tif err != nil {\n\t\tlog.Fatalf(\"error handling JSON marshal. Err: %v\", err)\n\t}\n\n\t_, _ = w.Write(jsonResp)\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {\n\tjsonResp, err := json.Marshal(s.db.Health())\n\n\tif err != nil {\n\t\tlog.Fatalf(\"error handling JSON marshal. Err: %v\", err)\n\t}\n\n\t_, _ = w.Write(jsonResp)\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/routes/standard_library.go.tmpl",
    "content": "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}}\n\n  {{.AdvancedTemplates.TemplateImports}}\n)\n\nfunc (s *Server) RegisterRoutes() http.Handler {\n\tmux := http.NewServeMux()\n\n\t// Register routes\n\tmux.HandleFunc(\"/\", s.HelloWorldHandler)\n  {{if ne .DBDriver \"none\"}}\n\tmux.HandleFunc(\"/health\", s.healthHandler)\n  {{end}}\n  {{if .AdvancedOptions.websocket}}\n\tmux.HandleFunc(\"/websocket\", s.websocketHandler)\n  {{end}}\n  {{.AdvancedTemplates.TemplateRoutes}}\n\n\t// Wrap the mux with CORS middleware\n\treturn s.corsMiddleware(mux)\n}\n\nfunc (s *Server) corsMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// Set CORS headers\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\") // Replace \"*\" with specific origins if needed\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS, PATCH\")\n\t\tw.Header().Set(\"Access-Control-Allow-Headers\", \"Accept, Authorization, Content-Type, X-CSRF-Token\")\n\t\tw.Header().Set(\"Access-Control-Allow-Credentials\", \"false\") // Set to \"true\" if credentials are required\n\n\t\t// Handle preflight OPTIONS requests\n\t\tif r.Method == http.MethodOptions {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\treturn\n\t\t}\n\n\t\t// Proceed with the next handler\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\nfunc (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {\n\tresp := map[string]string{\"message\": \"Hello World\"}\n\tjsonResp, err := json.Marshal(resp)\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to marshal response\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif _, err := w.Write(jsonResp); err != nil {\n\t\tlog.Printf(\"Failed to write response: %v\", err)\n\t}\n}\n\n{{if ne .DBDriver \"none\"}}\nfunc (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {\n\tresp, err := json.Marshal(s.db.Health())\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to marshal health check response\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif _, err := w.Write(resp); err != nil {\n\t\tlog.Printf(\"Failed to write response: %v\", err)\n\t}\n}\n{{end}}\n\n{{if .AdvancedOptions.websocket}}\nfunc (s *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {\n\tsocket, err := websocket.Accept(w, r, nil)\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to open websocket\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer socket.Close(websocket.StatusGoingAway, \"Server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\tif err := socket.Write(socketCtx, websocket.MessageText, []byte(payload)); err != nil {\n\t\t\tlog.Printf(\"Failed to write to socket: %v\", err)\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(2 * time.Second)\n\t}\n}\n{{end}}\n\n"
  },
  {
    "path": "cmd/template/framework/files/server/fiber.go.tmpl",
    "content": "package server\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n  {{if ne .DBDriver \"none\"}}\n\t\"{{.ProjectName}}/internal/database\"\n  {{end}}\n)\n\ntype FiberServer struct {\n\t*fiber.App\n  {{if ne .DBDriver \"none\"}}\n\tdb database.Service\n  {{end}}\n}\n\nfunc New() *FiberServer {\n\tserver := &FiberServer{\n\t\tApp: fiber.New(fiber.Config{\n\t\tServerHeader:            \"{{.ProjectName}}\",\n\t\tAppName:                 \"{{.ProjectName}}\",\n\t}),\n  {{if ne .DBDriver \"none\"}}\n\t\tdb:  database.New(),\n  {{end}}\n\t}\n\n\treturn server\n}\n"
  },
  {
    "path": "cmd/template/framework/files/server/standard_library.go.tmpl",
    "content": "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 .DBDriver \"none\"}}\n\t\"{{.ProjectName}}/internal/database\"\n  {{end}}\n)\n\ntype Server struct {\n\tport int\n  {{if ne .DBDriver \"none\"}}\n\tdb   database.Service\n  {{end}}\n}\n\nfunc NewServer() *http.Server {\n\tport, _ := strconv.Atoi(os.Getenv(\"PORT\"))\n\tNewServer := &Server{\n\t\tport: port,\n  {{if ne .DBDriver \"none\"}}\n\t\tdb:   database.New(),\n  {{end}}\n\t}\n\n\t// Declare Server config\n\tserver := &http.Server{\n\t\tAddr:         fmt.Sprintf(\":%d\", NewServer.port),\n\t\tHandler:      NewServer.RegisterRoutes(),\n\t\tIdleTimeout:  time.Minute,\n\t\tReadTimeout:  10 * time.Second,\n\t\tWriteTimeout: 30 * time.Second,\n\t}\n\n\treturn server\n}\n"
  },
  {
    "path": "cmd/template/framework/files/tests/default-test.go.tmpl",
    "content": "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 := &Server{}\n    server := httptest.NewServer(http.HandlerFunc(s.HelloWorldHandler))\n    defer server.Close()\n    resp, err := http.Get(server.URL)\n    if err != nil {\n        t.Fatalf(\"error making request to server. Err: %v\", err)\n    }\n    defer resp.Body.Close()\n\t// Assertions\n    if resp.StatusCode != http.StatusOK {\n        t.Errorf(\"expected status OK; got %v\", resp.Status)\n    }\n    expected := \"{\\\"message\\\":\\\"Hello World\\\"}\"\n    body, err := io.ReadAll(resp.Body)\n    if err != nil {\n        t.Fatalf(\"error reading response body. Err: %v\", err)\n    }\n    if expected != string(body) {\n        t.Errorf(\"expected response body to be %v; got %v\", expected, string(body))\n    }\n}\n"
  },
  {
    "path": "cmd/template/framework/files/tests/echo-test.go.tmpl",
    "content": "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/echo/v4\"\n)\nfunc TestHandler(t *testing.T) {\n\te := echo.New()\n\treq := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\tresp := httptest.NewRecorder()\n\tc := e.NewContext(req, resp)\n\ts := &Server{}\n\t// Assertions\n\tif err := s.HelloWorldHandler(c); err != nil {\n\t\tt.Errorf(\"handler() error = %v\", err)\n\t\treturn\n\t}\n\tif resp.Code != http.StatusOK {\n\t\tt.Errorf(\"handler() wrong status code = %v\", resp.Code)\n\t\treturn\n\t}\n\texpected := map[string]string{\"message\": \"Hello World\"}\n\tvar actual map[string]string\n\t// Decode the response body into the actual map\n\tif err := json.NewDecoder(resp.Body).Decode(&actual); err != nil {\n\t\tt.Errorf(\"handler() error decoding response body: %v\", err)\n\t\treturn\n\t}\n\t// Compare the decoded response with the expected value\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"handler() wrong response body. expected = %v, actual = %v\", expected, actual)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "cmd/template/framework/files/tests/fiber-test.go.tmpl",
    "content": "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// Create a Fiber app for testing\n\tapp := fiber.New()\n\t// Inject the Fiber app into the server\n\ts := &FiberServer{App: app}\n\t// Define a route in the Fiber app\n\tapp.Get(\"/\", s.HelloWorldHandler)\n\t// Create a test HTTP request\n\treq,err := http.NewRequest(\"GET\", \"/\", nil)\n    if err != nil {\n        t.Fatalf(\"error creating request. Err: %v\", err)\n    }\n\t// Perform the request\n\tresp, err := app.Test(req)\n\tif err != nil {\n\t\tt.Fatalf(\"error making request to server. Err: %v\", err)\n\t}\n\t// Your test assertions...\n\tif resp.StatusCode != http.StatusOK {\n\t\tt.Errorf(\"expected status OK; got %v\", resp.Status)\n\t}\n\texpected := \"{\\\"message\\\":\\\"Hello World\\\"}\"\n    body, err := io.ReadAll(resp.Body)\n    if err != nil {\n        t.Fatalf(\"error reading response body. Err: %v\", err)\n    }\n    if expected != string(body) {\n        t.Errorf(\"expected response body to be %v; got %v\", expected, string(body))\n    }\n}\n"
  },
  {
    "path": "cmd/template/framework/files/tests/gin-test.go.tmpl",
    "content": "package server\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"github.com/gin-gonic/gin\"\n)\nfunc TestHelloWorldHandler(t *testing.T) {\n\ts := &Server{}\n\tr := gin.New()\n\tr.GET(\"/\", s.HelloWorldHandler)\n\t// Create a test HTTP request\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Create a ResponseRecorder to record the response\n\trr := httptest.NewRecorder()\n\t// Serve the HTTP request\n\tr.ServeHTTP(rr, req)\n\t// Check the status code\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"Handler returned wrong status code: got %v want %v\", status, http.StatusOK)\n\t}\n    // Check the response body\n    expected := \"{\\\"message\\\":\\\"Hello World\\\"}\"\n    if rr.Body.String() != expected {\n        t.Errorf(\"Handler returned unexpected body: got %v want %v\", rr.Body.String(), expected)\n    }\n}\n"
  },
  {
    "path": "cmd/template/framework/ginRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/gin.go.tmpl\nvar ginRoutesTemplate []byte\n\n//go:embed files/tests/gin-test.go.tmpl\nvar ginTestHandlerTemplate []byte\n\n// GinTemplates contains the methods used for building\n// an app that uses [github.com/gin-gonic/gin]\ntype GinTemplates struct{}\n\nfunc (g GinTemplates) Main() []byte {\n\treturn mainTemplate\n}\n\nfunc (g GinTemplates) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (g GinTemplates) Routes() []byte {\n\treturn ginRoutesTemplate\n}\n\nfunc (g GinTemplates) TestHandler() []byte {\n\treturn ginTestHandlerTemplate\n}\n\nfunc (g GinTemplates) HtmxTemplImports() []byte {\n\treturn advanced.GinHtmxTemplImportsTemplate()\n}\n\nfunc (g GinTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.GinHtmxTemplRoutesTemplate()\n}\n\nfunc (g GinTemplates) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/gorillaRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/gorilla.go.tmpl\nvar gorillaRoutesTemplate []byte\n\n//go:embed files/tests/default-test.go.tmpl\nvar gorillaTestHandlerTemplate []byte\n\n// GorillaTemplates contains the methods used for building\n// an app that uses [github.com/gorilla/mux]\ntype GorillaTemplates struct{}\n\nfunc (g GorillaTemplates) Main() []byte {\n\treturn mainTemplate\n}\n\nfunc (g GorillaTemplates) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (g GorillaTemplates) Routes() []byte {\n\treturn gorillaRoutesTemplate\n}\n\nfunc (g GorillaTemplates) TestHandler() []byte {\n\treturn gorillaTestHandlerTemplate\n}\n\nfunc (g GorillaTemplates) HtmxTemplImports() []byte {\n\treturn advanced.StdLibHtmxTemplImportsTemplate()\n}\n\nfunc (g GorillaTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.GorillaHtmxTemplRoutesTemplate()\n}\n\nfunc (g GorillaTemplates) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/httpRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/standard_library.go.tmpl\nvar standardRoutesTemplate []byte\n\n//go:embed files/server/standard_library.go.tmpl\nvar standardServerTemplate []byte\n\n//go:embed files/tests/default-test.go.tmpl\nvar standardTestHandlerTemplate []byte\n\n// StandardLibTemplate contains the methods used for building\n// an app that uses [net/http]\ntype StandardLibTemplate struct{}\n\nfunc (s StandardLibTemplate) Main() []byte {\n\treturn mainTemplate\n}\n\nfunc (s StandardLibTemplate) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (s StandardLibTemplate) Routes() []byte {\n\treturn standardRoutesTemplate\n}\n\nfunc (s StandardLibTemplate) TestHandler() []byte {\n\treturn standardTestHandlerTemplate\n}\n\nfunc (s StandardLibTemplate) HtmxTemplImports() []byte {\n\treturn advanced.StdLibHtmxTemplImportsTemplate()\n}\n\nfunc (s StandardLibTemplate) HtmxTemplRoutes() []byte {\n\treturn advanced.StdLibHtmxTemplRoutesTemplate()\n}\n\nfunc (s StandardLibTemplate) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/framework/main.go",
    "content": "// Package template provides utility functions that\n// help with the templating of created files.\npackage framework\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed files/main/main.go.tmpl\nvar mainTemplate []byte\n\n//go:embed files/air.toml.tmpl\nvar airTomlTemplate []byte\n\n//go:embed files/README.md.tmpl\nvar readmeTemplate []byte\n\n//go:embed files/makefile.tmpl\nvar makeTemplate []byte\n\n//go:embed files/gitignore.tmpl\nvar gitIgnoreTemplate []byte\n\n// MakeTemplate returns a byte slice that represents\n// the default Makefile template.\nfunc MakeTemplate() []byte {\n\treturn makeTemplate\n}\n\nfunc GitIgnoreTemplate() []byte {\n\treturn gitIgnoreTemplate\n}\n\nfunc AirTomlTemplate() []byte {\n\treturn airTomlTemplate\n}\n\n// ReadmeTemplate returns a byte slice that represents\n// the default README.md file template.\nfunc ReadmeTemplate() []byte {\n\treturn readmeTemplate\n}\n"
  },
  {
    "path": "cmd/template/framework/routerRoutes.go",
    "content": "package framework\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/template/advanced\"\n)\n\n//go:embed files/routes/http_router.go.tmpl\nvar httpRouterRoutesTemplate []byte\n\n//go:embed files/tests/default-test.go.tmpl\nvar httpRouterTestHandlerTemplate []byte\n\n// RouterTemplates contains the methods used for building\n// an app that uses [github.com/julienschmidt/httprouter]\ntype RouterTemplates struct{}\n\nfunc (r RouterTemplates) Main() []byte {\n\treturn mainTemplate\n}\nfunc (r RouterTemplates) Server() []byte {\n\treturn standardServerTemplate\n}\n\nfunc (r RouterTemplates) Routes() []byte {\n\treturn httpRouterRoutesTemplate\n}\n\nfunc (r RouterTemplates) TestHandler() []byte {\n\treturn httpRouterTestHandlerTemplate\n}\n\nfunc (r RouterTemplates) HtmxTemplImports() []byte {\n\treturn advanced.StdLibHtmxTemplImportsTemplate()\n}\n\nfunc (r RouterTemplates) HtmxTemplRoutes() []byte {\n\treturn advanced.HttpRouterHtmxTemplRoutesTemplate()\n}\n\nfunc (r RouterTemplates) WebsocketImports() []byte {\n\treturn advanced.StdLibWebsocketTemplImportsTemplate()\n}\n"
  },
  {
    "path": "cmd/template/globalEnv.go",
    "content": "package template\n\nimport (\n\t_ \"embed\"\n)\n\n//go:embed framework/files/globalenv.tmpl\nvar globalEnvTemplate []byte\n\nfunc GlobalEnvTemplate() []byte {\n\treturn globalEnvTemplate\n}\n"
  },
  {
    "path": "cmd/ui/multiInput/multiInput.go",
    "content": "// Package multiInput provides functions that\n// help define and draw a multi-input step\npackage multiInput\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/program\"\n\t\"github.com/melkeydev/go-blueprint/cmd/steps\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\n// Change this\nvar (\n\tfocusedStyle          = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#01FAC6\")).Bold(true)\n\ttitleStyle            = lipgloss.NewStyle().Background(lipgloss.Color(\"#01FAC6\")).Foreground(lipgloss.Color(\"#030303\")).Bold(true).Padding(0, 1, 0)\n\tselectedItemStyle     = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"170\")).Bold(true)\n\tselectedItemDescStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"170\"))\n\tdescriptionStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#40BDA3\"))\n)\n\n// A Selection represents a choice made in a multiInput step\ntype Selection struct {\n\tChoice string\n}\n\n// Update changes the value of a Selection's Choice\nfunc (s *Selection) Update(value string) {\n\ts.Choice = value\n}\n\n// A multiInput.model contains the data for the multiInput step.\n//\n// It has the required methods that make it a bubbletea.Model\ntype model struct {\n\tcursor   int\n\tchoices  []steps.Item\n\tselected map[int]struct{}\n\tchoice   *Selection\n\theader   string\n\texit     *bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\n// InitialModelMulti initializes a multiInput step with\n// the given data\nfunc InitialModelMulti(choices []steps.Item, selection *Selection, header string, program *program.Project) model {\n\treturn model{\n\t\tchoices:  choices,\n\t\tselected: make(map[int]struct{}),\n\t\tchoice:   selection,\n\t\theader:   titleStyle.Render(header),\n\t\texit:     &program.Exit,\n\t}\n}\n\n// Update is called when \"things happen\", it checks for\n// important keystrokes to signal when to quit, change selection,\n// and confirm the selection.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\t*m.exit = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"up\", \"k\":\n\t\t\tif m.cursor > 0 {\n\t\t\t\tm.cursor--\n\t\t\t}\n\t\tcase \"down\", \"j\":\n\t\t\tif m.cursor < len(m.choices)-1 {\n\t\t\t\tm.cursor++\n\t\t\t}\n\t\tcase \"enter\", \" \":\n\t\t\tif len(m.selected) == 1 {\n\t\t\t\tm.selected = make(map[int]struct{})\n\t\t\t}\n\t\t\t_, ok := m.selected[m.cursor]\n\t\t\tif ok {\n\t\t\t\tdelete(m.selected, m.cursor)\n\t\t\t} else {\n\t\t\t\tm.selected[m.cursor] = struct{}{}\n\t\t\t}\n\t\tcase \"y\":\n\t\t\tif len(m.selected) == 1 {\n\t\t\t\tfor selectedKey := range m.selected {\n\t\t\t\t\tm.choice.Update(m.choices[selectedKey].Title)\n\t\t\t\t\tm.cursor = selectedKey\n\t\t\t\t}\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\t\t}\n\t}\n\treturn m, nil\n}\n\n// View is called to draw the multiInput step\nfunc (m model) View() string {\n\ts := m.header + \"\\n\\n\"\n\n\tfor i, choice := range m.choices {\n\t\tcursor := \" \"\n\t\tif m.cursor == i {\n\t\t\tcursor = focusedStyle.Render(\">\")\n\t\t\tchoice.Title = selectedItemStyle.Render(choice.Title)\n\t\t\tchoice.Desc = selectedItemDescStyle.Render(choice.Desc)\n\t\t}\n\n\t\tchecked := \" \"\n\t\tif _, ok := m.selected[i]; ok {\n\t\t\tchecked = focusedStyle.Render(\"x\")\n\t\t}\n\n\t\ttitle := focusedStyle.Render(choice.Title)\n\t\tdescription := descriptionStyle.Render(choice.Desc)\n\n\t\ts += fmt.Sprintf(\"%s [%s] %s\\n%s\\n\\n\", cursor, checked, title, description)\n\t}\n\n\ts += fmt.Sprintf(\"Press %s to confirm choice.\\n\\n\", focusedStyle.Render(\"y\"))\n\treturn s\n}\n"
  },
  {
    "path": "cmd/ui/multiSelect/multiSelect.go",
    "content": "// Package multiSelect provides functions that\n// help define and draw a multi-select step\npackage multiSelect\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/melkeydev/go-blueprint/cmd/program\"\n\t\"github.com/melkeydev/go-blueprint/cmd/steps\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\n// Change this\nvar (\n\tfocusedStyle          = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#01FAC6\")).Bold(true)\n\ttitleStyle            = lipgloss.NewStyle().Background(lipgloss.Color(\"#01FAC6\")).Foreground(lipgloss.Color(\"#030303\")).Bold(true).Padding(0, 1, 0)\n\tselectedItemStyle     = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"170\")).Bold(true)\n\tselectedItemDescStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color(\"170\"))\n\tdescriptionStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#40BDA3\"))\n)\n\n// A Selection represents a choice made in a multiSelect step\ntype Selection struct {\n\tChoices map[string]bool\n}\n\n// Update changes the value of a Selection's Choice\nfunc (s *Selection) Update(optionName string, value bool) {\n\ts.Choices[optionName] = value\n}\n\n// A multiSelect.model contains the data for the multiSelect step.\n//\n// It has the required methods that make it a bubbletea.Model\ntype model struct {\n\tcursor   int\n\toptions  []steps.Item\n\tselected map[int]struct{}\n\tchoices  *Selection\n\theader   string\n\texit     *bool\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn nil\n}\n\n// InitialModelMulti initializes a multiSelect step with\n// the given data\nfunc InitialModelMultiSelect(options []steps.Item, selection *Selection, header string, program *program.Project) model {\n\treturn model{\n\t\toptions:  options,\n\t\tselected: make(map[int]struct{}),\n\t\tchoices:  selection,\n\t\theader:   titleStyle.Render(header),\n\t\texit:     &program.Exit,\n\t}\n}\n\n// Update is called when \"things happen\", it checks for\n// important keystrokes to signal when to quit, change selection,\n// and confirm the selection.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"ctrl+c\", \"q\":\n\t\t\t*m.exit = true\n\t\t\treturn m, tea.Quit\n\t\tcase \"up\", \"k\":\n\t\t\tif m.cursor > 0 {\n\t\t\t\tm.cursor--\n\t\t\t}\n\t\tcase \"down\", \"j\":\n\t\t\tif m.cursor < len(m.options)-1 {\n\t\t\t\tm.cursor++\n\t\t\t}\n\t\tcase \"enter\", \" \":\n\t\t\t_, ok := m.selected[m.cursor]\n\t\t\tif ok {\n\t\t\t\tdelete(m.selected, m.cursor)\n\t\t\t} else {\n\t\t\t\tm.selected[m.cursor] = struct{}{}\n\t\t\t}\n\t\tcase \"y\":\n\t\t\tfor selectedKey := range m.selected {\n\t\t\t\tm.choices.Update(m.options[selectedKey].Flag, true)\n\t\t\t\tm.cursor = selectedKey\n\t\t\t}\n\t\t\treturn m, tea.Quit\n\t\t}\n\t}\n\treturn m, nil\n}\n\n// View is called to draw the multiSelect step\nfunc (m model) View() string {\n\ts := m.header + \"\\n\\n\"\n\n\tfor i, option := range m.options {\n\t\tcursor := \" \"\n\t\tif m.cursor == i {\n\t\t\tcursor = focusedStyle.Render(\">\")\n\t\t\toption.Title = selectedItemStyle.Render(option.Title)\n\t\t\toption.Desc = selectedItemDescStyle.Render(option.Desc)\n\t\t}\n\n\t\tchecked := \" \"\n\t\tif _, ok := m.selected[i]; ok {\n\t\t\tchecked = focusedStyle.Render(\"*\")\n\t\t}\n\n\t\ttitle := focusedStyle.Render(option.Title)\n\t\tdescription := descriptionStyle.Render(option.Desc)\n\n\t\ts += fmt.Sprintf(\"%s [%s] %s\\n%s\\n\\n\", cursor, checked, title, description)\n\t}\n\n\ts += fmt.Sprintf(\"Press %s to confirm choice.\\n\", focusedStyle.Render(\"y\"))\n\treturn s\n}\n"
  },
  {
    "path": "cmd/ui/spinner/spinner.go",
    "content": "package spinner\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/charmbracelet/bubbles/spinner\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype errMsg error\n\ntype model struct {\n\tspinner  spinner.Model\n\tquitting bool\n\terr      error\n}\n\nfunc InitialModelNew() model {\n\ts := spinner.New()\n\ts.Spinner = spinner.Line\n\ts.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(\"205\"))\n\treturn model{spinner: s}\n}\n\nfunc (m model) Init() tea.Cmd {\n\treturn m.spinner.Tick\n}\n\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.String() {\n\t\tcase \"q\", \"esc\", \"ctrl+c\":\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tdefault:\n\t\t\treturn m, nil\n\t\t}\n\n\tcase errMsg:\n\t\tm.err = msg\n\t\treturn m, nil\n\n\tdefault:\n\t\tvar cmd tea.Cmd\n\t\tm.spinner, cmd = m.spinner.Update(msg)\n\t\treturn m, cmd\n\t}\n}\n\nfunc (m model) View() string {\n\n\tif m.err != nil {\n\t\treturn m.err.Error()\n\t}\n\tstr := fmt.Sprintf(\"%s Preparing...\", m.spinner.View())\n\tif m.quitting {\n\t\treturn str + \"\\n\"\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "cmd/ui/textinput/textinput.go",
    "content": "// Package textinput provides functions that\n// help define and draw a text-input step\npackage textinput\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.com/melkeydev/go-blueprint/cmd/program\"\n)\n\nvar (\n\ttitleStyle = lipgloss.NewStyle().Background(lipgloss.Color(\"#01FAC6\")).Foreground(lipgloss.Color(\"#030303\")).Bold(true).Padding(0, 1, 0)\n\terrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(\"#FF8700\")).Bold(true).Padding(0, 0, 0)\n)\n\ntype (\n\terrMsg error\n)\n\n// Output represents the text provided in a textinput step\ntype Output struct {\n\tOutput string\n}\n\n// Output.update updates the value of the Output\nfunc (o *Output) update(val string) {\n\to.Output = val\n}\n\n// A textnput.model contains the data for the textinput step.\n//\n// It has the required methods that make it a bubbletea.Model\ntype model struct {\n\ttextInput textinput.Model\n\terr       error\n\toutput    *Output\n\theader    string\n\texit      *bool\n}\n\n// sanitizeInput verifies that an input text string gets validated\nfunc sanitizeInput(input string) error {\n\tmatched, err := regexp.MatchString(\"^[a-zA-Z0-9_\\\\/.-]+$\", input)\n\tif !matched {\n\t\treturn fmt.Errorf(\"string violates the input regex pattern, err: %v\", err)\n\t}\n\treturn nil\n}\n\n// InitialTextInputModel initializes a textinput step\n// with the given data\nfunc InitialTextInputModel(output *Output, header string, program *program.Project) model {\n\tti := textinput.New()\n\tti.Focus()\n\tti.CharLimit = 156\n\tti.Width = 20\n\tti.Validate = sanitizeInput\n\n\treturn model{\n\t\ttextInput: ti,\n\t\terr:       nil,\n\t\toutput:    output,\n\t\theader:    titleStyle.Render(header),\n\t\texit:      &program.Exit,\n\t}\n}\n\n// CreateErrorInputModel creates a textinput step\n// with the given error\nfunc CreateErrorInputModel(err error) model {\n\tti := textinput.New()\n\tti.Focus()\n\tti.CharLimit = 156\n\tti.Width = 20\n\texit := true\n\n\treturn model{\n\t\ttextInput: ti,\n\t\terr:       errors.New(errorStyle.Render(err.Error())),\n\t\toutput:    nil,\n\t\theader:    \"\",\n\t\texit:      &exit,\n\t}\n}\n\n// Init is called at the beginning of a textinput step\n// and sets the cursor to blink\nfunc (m model) Init() tea.Cmd {\n\treturn textinput.Blink\n}\n\n// Update is called when \"things happen\", it checks for the users text input,\n// and for Ctrl+C or Esc to close the program.\nfunc (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\n\tswitch msg := msg.(type) {\n\tcase tea.KeyMsg:\n\t\tswitch msg.Type {\n\t\tcase tea.KeyEnter:\n\t\t\tif len(m.textInput.Value()) > 1 {\n\t\t\t\tm.output.update(m.textInput.Value())\n\t\t\t\treturn m, tea.Quit\n\t\t\t}\n\t\tcase tea.KeyCtrlC, tea.KeyEsc:\n\t\t\t*m.exit = true\n\t\t\treturn m, tea.Quit\n\t\t}\n\n\t// We handle errors just like any other message\n\tcase errMsg:\n\t\tm.err = msg\n\t\t*m.exit = true\n\t\treturn m, nil\n\t}\n\n\tm.textInput, cmd = m.textInput.Update(msg)\n\treturn m, cmd\n}\n\n// View is called to draw the textinput step\nfunc (m model) View() string {\n\treturn fmt.Sprintf(\"%s\\n\\n%s\\n\\n\",\n\t\tm.header,\n\t\tm.textInput.View(),\n\t)\n}\n\nfunc (m model) Err() string {\n\treturn m.err.Error()\n}\n"
  },
  {
    "path": "cmd/ui/textinput/textinput_test.go",
    "content": "package textinput\n\nimport \"testing\"\n\nfunc TestInputSanitization(t *testing.T) {\n\tpassTestCases := []string{\n\t\t\"chi\",\n\t\t\"new_project\",\n\t\t\"NEW_PROJECT\",\n\t\t\"new-project\",\n\t\t\"new/project\",\n\t\t\"new.project\",\n\t}\n\tfor _, testCase := range passTestCases {\n\t\tif err := sanitizeInput(testCase); err != nil {\n\t\t\tt.Errorf(\"expected no error, got: %v\", err)\n\t\t}\n\t}\n\tfailTestCases := []string{\n\t\t\"new project\",\n\t\t\"NEW\\\\PROJECT\",\n\t\t\"new%project\",\n\t\t\" \",\n\t\t\"  \",\n\t\t\"#\",\n\t\t\"@\",\n\t}\n\tfor _, testCase := range failTestCases {\n\t\tif err := sanitizeInput(testCase); err == nil {\n\t\t\tt.Errorf(\"expected error for input %v, got nil\", testCase)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/utils/utils.go",
    "content": "// 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\"regexp\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n)\n\nconst ProgramName = \"go-blueprint\"\n\n// NonInteractiveCommand creates the command string from a flagSet\n// to be used for getting the equivalent non-interactive shell command\nfunc NonInteractiveCommand(use string, flagSet *pflag.FlagSet) string {\n\tnonInteractiveCommand := fmt.Sprintf(\"%s %s\", ProgramName, use)\n\n\tvisitFn := func(flag *pflag.Flag) {\n\t\tif flag.Name != \"help\" {\n\t\t\tif flag.Name == \"feature\" {\n\t\t\t\tfeatureFlagsString := \"\"\n\t\t\t\t// Creates string representation for the feature flags to be\n\t\t\t\t// concatenated with the nonInteractiveCommand\n\t\t\t\tfor _, k := range strings.Split(flag.Value.String(), \",\") {\n\t\t\t\t\tif k != \"\" {\n\t\t\t\t\t\tfeatureFlagsString += fmt.Sprintf(\" --feature %s\", k)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnonInteractiveCommand += featureFlagsString\n\t\t\t} else if flag.Value.Type() == \"bool\" {\n\t\t\t\tif flag.Value.String() == \"true\" {\n\t\t\t\t\tnonInteractiveCommand = fmt.Sprintf(\"%s --%s\", nonInteractiveCommand, flag.Name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnonInteractiveCommand = fmt.Sprintf(\"%s --%s %s\", nonInteractiveCommand, flag.Name, flag.Value.String())\n\t\t\t}\n\t\t}\n\t}\n\n\tflagSet.SortFlags = false\n\tflagSet.VisitAll(visitFn)\n\n\treturn nonInteractiveCommand\n}\n\nfunc RegisterStaticCompletions(cmd *cobra.Command, flag string, options []string) {\n\terr := cmd.RegisterFlagCompletionFunc(flag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn options, cobra.ShellCompDirectiveNoFileComp\n\t})\n\n\tif err != nil {\n\t\tlog.Printf(\"warning: could not register completion for --%s: %v\", flag, err)\n\t}\n}\n\n// ExecuteCmd provides a shorthand way to run a shell command\nfunc ExecuteCmd(name string, args []string, dir string) error {\n\tcommand := exec.Command(name, args...)\n\tcommand.Dir = dir\n\tvar out bytes.Buffer\n\tvar stdErr bytes.Buffer\n\tcommand.Stdout = &out\n\tcommand.Stderr = &stdErr\n\tif err := command.Run(); err != nil {\n\t\treturn fmt.Errorf(\"%v\\n%v\", err, stdErr.String())\n\t}\n\treturn nil\n}\n\n// InitGoMod initializes go.mod with the given project name\n// in the selected directory\nfunc InitGoMod(projectName string, appDir string) error {\n\tif err := ExecuteCmd(\"go\",\n\t\t[]string{\"mod\", \"init\", projectName},\n\t\tappDir); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GoGetPackage runs \"go get\" for a given package in the\n// selected directory\nfunc GoGetPackage(appDir string, packages []string) error {\n\tfor _, packageName := range packages {\n\t\tif err := ExecuteCmd(\"go\",\n\t\t\t[]string{\"get\", \"-u\", packageName},\n\t\t\tappDir); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GoFmt runs \"gofmt\" in a selected directory using the\n// simplify and overwrite flags\nfunc GoFmt(appDir string) error {\n\tif err := ExecuteCmd(\"gofmt\",\n\t\t[]string{\"-s\", \"-w\", \".\"},\n\t\tappDir); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GoModReplace runs \"go mod edit -replace\" in the selected\n// replace_payload e.g: github.com/gocql/gocql=github.com/scylladb/gocql@v1.14.4\nfunc GoModReplace(appDir string, replace string) error {\n\tif err := ExecuteCmd(\"go\",\n\t\t[]string{\"mod\", \"edit\", \"-replace\", replace},\n\t\tappDir,\n\t); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc GoTidy(appDir string) error {\n\terr := ExecuteCmd(\"go\", []string{\"mod\", \"tidy\"}, appDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc CheckGitConfig(key string) (bool, error) {\n\tcmd := exec.Command(\"git\", \"config\", \"--get\", key)\n\tif err := cmd.Run(); err != nil {\n\t\tif exitError, ok := err.(*exec.ExitError); ok {\n\t\t\t// The command failed to run.\n\t\t\tif exitError.ExitCode() == 1 {\n\t\t\t\t// The 'git config --get' command returns 1 if the key was not found.\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\t// Some other error occurred.\n\t\treturn false, err\n\t}\n\t// The command ran successfully, so the key is set.\n\treturn true, nil\n}\n\n// ValidateModuleName returns true if it's a valid module name.\n// It allows any number of / and . characters in between.\nfunc ValidateModuleName(moduleName string) bool {\n\tmatched, _ := regexp.MatchString(\"^[a-zA-Z0-9_-]+(?:[\\\\/.][a-zA-Z0-9_-]+)*$\", moduleName)\n\treturn matched\n}\n\n// GetRootDir returns the project directory name from the module path.\n// Returns the last token by splitting the moduleName with /\nfunc GetRootDir(moduleName string) string {\n\ttokens := strings.Split(moduleName, \"/\")\n\treturn tokens[len(tokens)-1]\n}\n"
  },
  {
    "path": "cmd/utils/utils_test.go",
    "content": "package utils\n\nimport \"testing\"\n\nfunc TestValidateModuleName(t *testing.T) {\n\tpassTestCases := []string{\n\t\t\"github.com/user/project\",\n\t\t\"github.com/user/projec1-hyphen\",\n\t\t\"github.com/user/projecT_under_Score\",\n\t\t\"github.com/user/project.hyphen3\",\n\t\t\"project\",\n\t\t\"ProJEct\",\n\t\t\"PRo_45-.4Jc\",\n\t\t\"PRo_/4J/c\",\n\t}\n\tfor _, testCase := range passTestCases {\n\t\tok := ValidateModuleName(testCase)\n\t\tif !ok {\n\t\t\tt.Errorf(\"testing:%s expected:true got:%v\", testCase, ok)\n\t\t}\n\t}\n\n\tfailTestCases := []string{\n\t\t\"\",\n\t\t\"/\",\n\t\t\".\",\n\t\t\"//\",\n\t\t\"/project\",\n\t\t\"ProJEct/\",\n\t\t\"PRo_$4Jc\",\n\t\t\"PRo_@J/c\",\n\t}\n\tfor _, testCase := range failTestCases {\n\t\tok := ValidateModuleName(testCase)\n\t\tif ok {\n\t\t\tt.Errorf(\"testing:%s expected:false got:%v\", testCase, ok)\n\t\t}\n\t}\n}\n\nfunc TestGeRootDir(t *testing.T) {\n\ttestCases := map[string]string{\n\t\t\"github.com/user/pro-ject\": \"pro-ject\",\n\t\t\"pro-ject\":                 \"pro-ject\",\n\t\t\"/\":                        \"\",\n\t\t\"\":                         \"\",\n\t\t\"//\":                       \"\",\n\t\t\"@\":                        \"@\",\n\t}\n\n\tfor intput, output := range testCases {\n\t\trootDir := GetRootDir(intput)\n\t\tif rootDir != output {\n\t\t\tt.Errorf(\"testing:%s expected:%s got:%s\", intput, output, rootDir)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/version.go",
    "content": "/*\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// GoBlueprintVersion is the version of the cli to be overwritten by goreleaser in the CI run with the version of the release in github\nvar GoBlueprintVersion string\n\n// Go Blueprint needs to be built in a specific way to provide useful version information.\n// First we try to get the version from ldflags embedded into GoBlueprintVersion.\n// Then we try to get the version from from the go.mod build info. \n// If Go Blueprint is installed with a specific version tag or using @latest then that version will be included in bi.Main.Version. \n// This won't give any version info when running 'go install' with the source code locally.\n// Finally we try to get the version from other embedded VCS info.\nfunc getGoBlueprintVersion() string {\n\tnoVersionAvailable := \"No version info available for this build, run 'go-blueprint help version' for additional info\"\n\t\n\tif len(GoBlueprintVersion) != 0 {\n\t\treturn GoBlueprintVersion\n\t}\n\n\tbi, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn noVersionAvailable\n\t}\n\n\t// If no main version is available, Go defaults it to (devel)\n\tif bi.Main.Version != \"(devel)\" {\n\t\treturn bi.Main.Version\n\t}\n\n\tvar vcsRevision string\n\tvar vcsTime time.Time\n\tfor _, setting := range bi.Settings {\n\t\tswitch setting.Key {\n\t\tcase \"vcs.revision\":\n\t\t\tvcsRevision = setting.Value\n\t\tcase \"vcs.time\":\n\t\t\tvcsTime, _ = time.Parse(time.RFC3339, setting.Value)\n\t\t}\n\t}\n\n\tif vcsRevision != \"\" {\n\t\treturn fmt.Sprintf(\"%s, (%s)\", vcsRevision, vcsTime)\n\t}\n\n\treturn noVersionAvailable\n}\n\n// versionCmd represents the version command\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Display application version information.\",\n\tLong: `\nThe version command provides information about the application's version.\n\nGo Blueprint requires version information to be embedded at compile time.\nFor detailed version information, Go Blueprint needs to be built as specified in the README installation instructions.\nIf Go Blueprint is built within a version control repository and other version info isn't available,\nthe revision hash will be used instead.\n\t`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tversion := getGoBlueprintVersion()\n\t\tfmt.Printf(\"Go Blueprint CLI version: %v\\n\", version)\n\t},\n}\n"
  },
  {
    "path": "contributors.yml",
    "content": "- Melkeydev\n- Ujstor\n- tylermeekel\n- MitchellBerend\n- H0llyW00dzZ\n- SudoSurya\n- mimatache\n- SputNikPlop\n- limesten\n- itschip\n- CamPen21\n- xsadia\n- arnevm123\n- muandane\n- joshjms\n- brijesh-amin\n- briancbarrow\n- arafays\n- LrsK\n- juleszs\n- vadhe\n- Patel-Raj\n- PrajvalBadiger\n- pradytpk\n- pellizzetti\n- Owbird\n- Jamlie\n- alexandear\n- NimishKashyap\n- narasaka\n- mubashiroliyantakath\n- abhishekmj303\n- Sakelig\n- reavessm\n- young-steveo\n- sibteali786\n- tomasohCHOM\n- vinitparekh17\n- vsnaichuk\n- Waldeedle\n- jpx40\n- nhlmg93\n- rustafariandev\n- s0up4200\n- alexanderilyin\n- Yoquec\n- jexroid\n- andrerocco\n- ashupednekar\n- basokant\n- schoolboybru\n- brendonotto\n- danielhe4rt\n- spankie\n- Echo5678\n- EinarLogi\n- silaselisha\n- eric-jacobson\n- KennyMwendwaX\n- KibuuleNoah\n- LarsArtmann\n- mdelapenya\n- Marcellofabrizio\n- kobamkode\n- MatthewAraujo\n- mikelerch\n- MohammadAlhallaq\n"
  },
  {
    "path": "docs/Makefile",
    "content": ".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 --upgrade\n\tpip install -r requirements.txt\n\nupgrade:\n\tpip install pip --upgrade\n\tpip install -r requirements.txt --upgrade\n\n\ns serve:\n\tmkdocs serve --strict\n\n\nb build:\n\tmkdocs build --strict\n\nd deploy:\n\tmkdocs gh-deploy --strict --force\n"
  },
  {
    "path": "docs/custom_theme/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block libs %}\n    {{ super() }}\n    <script defer data-domain=\"docs.go-blueprint.dev\" src=\"https://analytics.go-blueprint.dev/js/script.js\"></script>\n{% endblock %}"
  },
  {
    "path": "docs/docs/advanced-flag/advanced-flag.md",
    "content": "# Advanced Flag in Blueprint\n\nThe `--advanced` flag in Blueprint serves as a switch to enable additional features during project creation. It is applied with the `create` command and unlocks the following features:\n\n- **HTMX Support using Templ:**\nEnables the integration of HTMX support for dynamic web pages using Templ.\n\n- **CI/CD Workflow Setup using GitHub Actions:**\nAutomates the setup of a CI/CD workflow using GitHub Actions.\n\n- **Websocket Support:**\nWebSocket endpoint that sends continuous data streams through the WS protocol.\n\n- **Tailwind:**\nAdds Tailwind CSS support to the project.\n\n- **Docker:**\nDocker configuration for go project.\n\n- **React:**\nFrontend written in TypeScript, including an example fetch request to the backend.\n\n\nTo utilize the `--advanced` flag, use the following command:\n\n```bash\ngo-blueprint create --name <project_name> --framework <selected_framework> --driver <selected_driver> --advanced\n```\n\nBy including the `--advanced` flag, users can choose one or all of the advanced features. The flag enhances the simplicity of Blueprint while offering flexibility for users who require additional functionality.\n\nTo recreate the project using the same configuration semi-interactively, use the following command:\n```bash\ngo-blueprint create --name my-project --framework chi --driver mysql --advanced\n```\n\nNon-Interactive Setup is also possible:\n\n```bash\ngo-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind\n```\n"
  },
  {
    "path": "docs/docs/advanced-flag/docker.md",
    "content": "The Docker advanced flag provides the app's Dockerfile configuration and creates or updates the docker-compose.yml file, which is generated if a DB driver is used.\nThe Dockerfile includes a two-stage build, and the final config depends on the use of advanced features. In the end, you will have a smaller image without unnecessary build dependencies.\n\n## Dockerfile\n\n```dockerfile\nFROM golang:1.24.4-alpine AS build\n\nRUN apk add --no-cache curl libstdc++ libgcc\n\nWORKDIR /app\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\nRUN go install github.com/a-h/templ/cmd/templ@latest && \\\n    templ generate && \\\n    curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64-musl -o tailwindcss && \\\n    chmod +x tailwindcss && \\\n    ./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css\n\nRUN go build -o main cmd/api/main.go\n\nFROM alpine:3.20.1 AS prod\nWORKDIR /app\nCOPY --from=build /app/main /app/main\nEXPOSE ${PORT}\nCMD [\"./main\"]\n```\n\nDocker config if React flag is used:\n\n```dockerfile\nFROM golang:1.24.4-alpine AS build\n\nWORKDIR /app\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\nRUN go build -o main cmd/api/main.go\n\nFROM alpine:3.20.1 AS prod\nWORKDIR /app\nCOPY --from=build /app/main /app/main\nEXPOSE ${PORT}\nCMD [\"./main\"]\n\n\nFROM node:20 AS frontend_builder\nWORKDIR /frontend\n\nCOPY frontend/package*.json ./\nRUN npm install\nCOPY frontend/. .\nRUN npm run build\n\nFROM node:20-slim AS frontend\nRUN npm install -g serve\nCOPY --from=frontend_builder /frontend/dist /app/dist\nEXPOSE 5173\nCMD [\"serve\", \"-s\", \"/app/dist\", \"-l\", \"5173\"]\n```\n## Docker compose\nDocker and docker-compose.yml pull environment variables from the .env file.\n\nExample if the Docker flag is used with the MySQL DB driver:\n```yaml\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n    depends_on:\n      mysql_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n  mysql_bp:\n    image: mysql:latest\n    restart: unless-stopped\n    environment:\n      MYSQL_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      MYSQL_USER: ${BLUEPRINT_DB_USERNAME}\n      MYSQL_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      MYSQL_ROOT_PASSWORD: ${BLUEPRINT_DB_ROOT_PASSWORD}\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:3306\"\n    volumes:\n      - mysql_volume_bp:/var/lib/mysql\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"${BLUEPRINT_DB_HOST}\", \"-u\", \"${BLUEPRINT_DB_USERNAME}\", \"--password=${BLUEPRINT_DB_PASSWORD}\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s\n    networks:\n      - blueprint\n\nvolumes:\n  mysql_volume_bp:\nnetworks:\n  blueprint:\n```\n\n## Note\nIf you are testing more than one framework locally, be aware of Docker leftovers such as volumes.\nFor proper cleaning and building, use `docker compose down --volumes` and `docker compose up --build`.\n\nor\n\n```bash\ndocker compose build --no-cache && docker compose up\n```\n"
  },
  {
    "path": "docs/docs/advanced-flag/goreleaser.md",
    "content": "Release process for Go projects, providing extensive customization options through its configuration file, `.goreleaser.yml`. By default, it ensures dependency cleanliness, builds binaries for various platforms and architectures, facilitates pre-release creation, and organizes binary packaging into archives with naming schemes.\n\nFor comprehensive insights into customization possibilities, refer to the [GoReleaser documentation](https://goreleaser.com/customization/).\n\n## Usage with Tags\n\nTo initiate release builds with GoReleaser, you need to follow these steps:\n\n- **Tag Creation:**\n  When your project is ready for a release, create a new tag in your Git repository. For example:\n```bash\ngit tag v1.0.0\n```\n\n- **Tag Pushing:**\n  Push the tag to the repository to trigger GoReleaser:\n```bash\ngit push origin v1.0.0\n```\n\nFollowing these steps ensures proper tagging of your project, prompting GoReleaser to execute configured releases. This approach simplifies release management and automates artifact distribution.\n\n## Go Test - Continuous Integration for Go Projects\n\nThe `go-test.yml` file defines a GitHub Actions workflow for continuous integration (CI) of Go projects within a GitHub repository.\n\n## Workflow Steps\n\nThe job outlined in this workflow includes the following steps:\n\n1. **Checkout:**\n   Fetches the project's codebase from the repository.\n\n2. **Go Setup:**\n   Configures the Go environment with version 1.21.x.\n\n3. **Build and Test:**\n   Builds the project using `go build` and runs tests across all packages (`./...`) using `go test`. \n\nThis workflow serves to automate the testing process of a Go project within a GitHub repository, ensuring code quality and reliability with each commit and pull request.\n"
  },
  {
    "path": "docs/docs/advanced-flag/htmx-templ.md",
    "content": "The WEB directory contains the web-related components and assets for the project. It leverages [htmx](https://github.com/bigskysoftware/htmx) and [templ](https://github.com/a-h/templ) in Go for dynamic web content generation.\n\n## Structure\n\n```\nweb/\n│\n│\n├── assets/\n│   └── js/\n│       └── htmx.min.js     # htmx library for dynamic HTML content\n│\n├── base.templ              # Base template for HTML structure\n├── base_templ.go           # Generated Go code for base template\n├── efs.go                  # Embeds static files into the Go binary\n│\n├── hello.go                # Handler for the Hello Web functionality\n├── hello.templ             # Template for rendering the Hello form and post data\n└── hello_templ.go          # Generated Go code for hello template\n```\n\n## Usage\n\n- **Navigate to Project Directory:**\n```bash\ncd my-project\n```\n\n- **Install Templ CLI:**\n```bash\ngo install github.com/a-h/templ/cmd/templ@latest\n```\n\n- **Generate Templ Function Files:**\n```bash\ntempl generate\n```\n\n- **Start Server:**\n```bash\nmake run\n```\n\n## Makefile\n\nAutomates templ with Makefile entries, which are automatically created if the htmx advanced flag is used.\nIt detects if templ is installed or not and generates templates with the make build command.\nBoth Windows and Unix-like OS are supported.\n\n```bash\nall: build\n\ntempl-install:\n\t@if ! command -v templ > /dev/null; then \\\n\t\tread -p \"Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] \" choice; \\\n\t\tif [ \"$$choice\" != \"n\" ] && [ \"$$choice\" != \"N\" ]; then \\\n\t\t\tgo install github.com/a-h/templ/cmd/templ@latest; \\\n\t\t\tif [ ! -x \"$$(command -v templ)\" ]; then \\\n\t\t\t\techo \"templ installation failed. Exiting...\"; \\\n\t\t\t\texit 1; \\\n\t\t\tfi; \\\n\t\telse \\\n\t\t\techo \"You chose not to install templ. Exiting...\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\tfi\n\nbuild: templ-install\n\t@echo \"Building...\"\n\t@templ generate\n\t@go build -o main cmd/api/main.go\n```\n\n## Templating\n\nTemplates are generated using the `templ generate` command after project creation. These templates are then compiled into Go code for efficient execution.\n\nYou can test HTMX functionality on `localhost:PORT/web` endpoint.\n"
  },
  {
    "path": "docs/docs/advanced-flag/react-vite.md",
    "content": "This template provides a minimal setup for getting React working with Vite for the frontend and go on the backend. It allows you to easily integrate React with Tailwind CSS and Vite for fast development.\n\nThe React advanced flag can be combined with the Tailwind flag for enhanced styling capabilities.\n\n## Project Structure\n\n```bash\n/ (Root)\n├── frontend/                     # React advanced flag. Excludes HTMX.\n│   ├── .env                      # Frontend environment configuration.\n│   ├── node_modules/             # Node dependencies.\n│   ├── public/\n│   │   ├── index.html\n│   │   └── favicon.ico\n│   ├── src/                      # React source files.\n│   │   ├── App.tsx               # Main React component.\n│   │   ├── assets/               # React assets directory.\n│   │   │   └── logo.svg\n│   │   ├── components/           # React components directory.\n│   │   │   ├── Header.tsx\n│   │   │   └── Footer.tsx\n│   │   ├── styles/               # CSS/SCSS styles directory.\n│   │   │   └── global.css\n│   │   └── index.tsx             # Main entry point for React\n│   ├── eslint.config.js          # ESLint configuration file.\n│   ├── index.html                # Base HTML template.\n│   ├── package.json              # Node.js package configuration.\n│   ├── package-lock.json         # Lock file for Node.js dependencies.\n│   ├── README.md                 # README file for the React project.\n│   ├── tsconfig.app.json         # TypeScript configuration for the app.\n│   ├── tsconfig.json             # Root TypeScript configuration.\n│   ├── tsconfig.node.json        # TypeScript configuration for Node.js.\n│   └── vite.config.ts            # Vite configuration file.\n```\n\n## Usage\n\n- **Navigate to the `frontend` directory**:\n   First, navigate to the `frontend` directory where the React project resides.\n\n```bash\ncd frontend\n```\n\n- **Install Dependencies**:\n   Use npm to install all necessary dependencies.\n\n```bash\nnpm install\n```\n\n- **Run the Development Server**:\n   Start the Vite development server for local development. This will launch a live-reloading server on a default port.\n\n```bash\nnpm run dev\n```\n\n   You should now be able to access the React application by opening a browser and navigating to `http://localhost:5173`.\n\n\nYou can extend the `vite.config.ts` to include additional configurations as needed, such as adding plugins for optimizing the build process, enabling TypeScript support, or configuring Tailwind CSS.\n\n## Makefile\n\nThe make run target will start the Go server in the backend, install frontend dependencies, and run the Vite development server for the frontend.\n\n```bash\nrun:\n\t@go run cmd/api/main.go &\n\t@npm install --prefix ./frontend\n\t@npm run dev --prefix ./frontend\n```\n\nAfter running this command, you can verify the connection between the frontend and backend by checking the console. You can also fetch data from the backend to test the integration.\n\n![React](../public/react.png)\n\n## Dockerfile\n\nCombine React advanced flag with Docker flag to get Docker and docker-compose configuration and run them with:\n\n```bash\nmake docker-run\n```\n\n### Dockerfile\n\n```dockerfile\nFROM golang:1.24.4-alpine AS build\n\nWORKDIR /app\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\n\nRUN go build -o main cmd/api/main.go\n\nFROM alpine:3.20.1 AS prod\nWORKDIR /app\nCOPY --from=build /app/main /app/main\nEXPOSE ${PORT}\nCMD [\"./main\"]\n\n\nFROM node:20 AS frontend_builder\nWORKDIR /frontend\n\nCOPY frontend/package*.json ./\nRUN npm install\nCOPY frontend/. .\nRUN npm run build\n\nFROM node:20-slim AS frontend\nRUN npm install -g serve\nCOPY --from=frontend_builder /frontend/dist /app/dist\nEXPOSE 5173\nCMD [\"serve\", \"-s\", \"/app/dist\", \"-l\", \"5173\"]\n```\n\n### Docker compose without db\n\n```yaml\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    ports:\n      - 5173:5173\n    depends_on:\n      - app\n```\n\n### Docker compose with db\n\n```yaml\nservices:\n  app:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: prod\n    restart: unless-stopped\n    ports:\n      - ${PORT}:${PORT}\n    environment:\n      APP_ENV: ${APP_ENV}\n      PORT: ${PORT}\n      BLUEPRINT_DB_HOST: ${BLUEPRINT_DB_HOST}\n      BLUEPRINT_DB_PORT: ${BLUEPRINT_DB_PORT}\n      BLUEPRINT_DB_DATABASE: ${BLUEPRINT_DB_DATABASE}\n      BLUEPRINT_DB_USERNAME: ${BLUEPRINT_DB_USERNAME}\n      BLUEPRINT_DB_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n      BLUEPRINT_DB_SCHEMA: ${BLUEPRINT_DB_SCHEMA}\n    depends_on:\n      psql_bp:\n        condition: service_healthy\n    networks:\n      - blueprint\n  frontend:\n    build:\n      context: .\n      dockerfile: Dockerfile\n      target: frontend\n    restart: unless-stopped\n    depends_on:\n      - app\n    ports:\n      - 5173:5173\n    networks:\n      - blueprint\n  psql_bp:\n    image: postgres:latest\n    restart: unless-stopped\n    environment:\n      POSTGRES_DB: ${BLUEPRINT_DB_DATABASE}\n      POSTGRES_USER: ${BLUEPRINT_DB_USERNAME}\n      POSTGRES_PASSWORD: ${BLUEPRINT_DB_PASSWORD}\n    ports:\n      - \"${BLUEPRINT_DB_PORT}:5432\"\n    volumes:\n      - psql_volume_bp:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"sh -c 'pg_isready -U ${BLUEPRINT_DB_USERNAME} -d ${BLUEPRINT_DB_DATABASE}'\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n      start_period: 15s\n    networks:\n      - blueprint\n\nvolumes:\n  psql_volume_bp:\nnetworks:\n  blueprint:\n```\n\n## Environment Variables\n\nThe `VITE_PORT` in .env refers `PORT` from .env in project root ( for backend ). If value of `PORT` is changed than `VITE_PORT` must also be changed so that requests to backend work fine and have no conflicts.\n\n## Notes\n\n- First time running the project creation with Tailwind can take longer (~10 mins) as npm needs to download and cache all packages\n\n- Subsequent runs will be faster as they utilize npm's cache, which we enforce during project creation.\n\n- If encountering issues with package installation, try these npm commands:\n\n```bash\n# Check cache status\nnpm cache verify\n\n# View cache contents\nnpm cache ls\n\n# Clean cache if needed\nnpm cache clean --force\n```\n"
  },
  {
    "path": "docs/docs/advanced-flag/tailwind.md",
    "content": "Tailwind is closely coupled with the advanced HTMX flag, and HTMX will be automatically used if you select Tailwind in your project.\n\nWe do not introduce outside dependencies automatically, and you need compile output.css (file is empty by default) with the Tailwind CLI tool.\n\nThe project tree would look like this:\n\n```bash\n/ (Root)\n├── cmd/\n│   ├── api/\n│   │   └── main.go\n│   └── web/\n│       ├── styles/\n│       │   └── input.css\n│       ├── assets/\n│       │   ├── css/\n│       │   │   └── output.css\n│       │   └── js/\n│       │       └── htmx.min.js\n│       ├── base.templ\n│       ├── base_templ.go\n│       ├── efs.go\n│       ├── hello.go\n│       ├── hello.templ\n│       └── hello_templ.go\n├── internal/\n│   └── server/\n│       ├── routes.go\n│       ├── routes_test.go\n│       └── server.go\n├── go.mod\n├── go.sum\n├── Makefile\n└── README.md\n```\n\n## Standalone Tailwind CLI\n\nThe idea is to avoid using Node.js and npm to build output.css.\n\nThe Makefile will have entries for downloading and compiling CSS. It will automatically detect the OS and download the latest release from the [official repository](https://github.com/tailwindlabs/tailwindcss/releases).\n\n## Linux Makefile Example\n\n```bash\nall: build\ntempl-install:\n\t@if ! command -v templ > /dev/null; then \\\n\t\tread -p \"Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] \" choice; \\\n\t\tif [ \"$$choice\" != \"n\" ] && [ \"$$choice\" != \"N\" ]; then \\\n\t\t\tgo install github.com/a-h/templ/cmd/templ@latest; \\\n\t\t\tif [ ! -x \"$$(command -v templ)\" ]; then \\\n\t\t\t\techo \"templ installation failed. Exiting...\"; \\\n\t\t\t\texit 1; \\\n\t\t\tfi; \\\n\t\telse \\\n\t\t\techo \"You chose not to install templ. Exiting...\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\tfi\n\ntailwind-install:\n\t@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss; fi\n\t@chmod +x tailwindcss\n\nbuild: tailwind-install templ-install\n\t@echo \"Building...\"\n\t@templ generate\n\t@./tailwindcss -i cmd/web/styles/input.css -o cmd/web/assets/css/output.css\n\t@go build -o main cmd/api/main.go\n```\n\n## Use Tailwind CSS in your project\n\nBy default, simple CSS examples are included in the codebase.\nUpdate base.templ and hello.templ, then rerun templ generate to see the changes at the `localhost:PORT/web` endpoint.\n\n![Tailwind](../public/tailwind.png)\n"
  },
  {
    "path": "docs/docs/advanced-flag/websocket.md",
    "content": "A `/websocket` endpoint is added in `routes.go` to facilitate websocket connections. Upon accessing this endpoint, the server establishes a websocket connection and begins transmitting timestamp messages at 2-second intervals. WS is utilized across all Go-blueprint supported frameworks. This simple implementation showcases how flexible a project is.\n\n### Code Implementation\n\n```go\nfunc (s *Server) websocketHandler(c *gin.Context) {\n\tw := c.Writer\n\tr := c.Request\n\tsocket, err := websocket.Accept(w, r, nil)\n\n\tif err != nil {\n\t\tlog.Printf(\"could not open websocket: %v\", err)\n\t\t_, _ = w.Write([]byte(\"could not open websocket\"))\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdefer socket.Close(websocket.StatusGoingAway, \"server closing websocket\")\n\n\tctx := r.Context()\n\tsocketCtx := socket.CloseRead(ctx)\n\n\tfor {\n\t\tpayload := fmt.Sprintf(\"server timestamp: %d\", time.Now().UnixNano())\n\t\terr := socket.Write(socketCtx, websocket.MessageText, []byte(payload))\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second * 2)\n\t}\n}\n```\n"
  },
  {
    "path": "docs/docs/blueprint-core/db-drivers.md",
    "content": "To extend the project with database functionality, users can choose from a variety of Go database drivers. Each driver is tailored to work with specific database systems, providing flexibility based on project requirements:\n\n1. [Mongo](https://go.mongodb.org/mongo-driver): Provides necessary tools for connecting and interacting with MongoDB databases.\n2. [Mysql](https://github.com/go-sql-driver/mysql): Enables seamless integration with MySQL databases.\n3. [Postgres](https://github.com/jackc/pgx/): Facilitates connectivity to PostgreSQL databases.\n4. [Redis](https://github.com/redis/go-redis): Provides tools for connecting and interacting with Redis.\n5. [Sqlite](https://github.com/mattn/go-sqlite3): Suitable for projects requiring a lightweight, self-contained database.\n6. [ScyllaDB](https://github.com/scylladb/gocql): Facilitates connectivity to ScyllaDB databases.\n\n## Updated Project Structure\n\nIntegrating a database adds a new layer to the project structure, primarily in the `internal/database` directory:\n\n```bash\n/(Root)\n├── /cmd\n│   └── /api\n│       └── main.go\n├── /internal\n│   ├── /database\n│   │   ├── database_test.go\n│   │   └── database.go\n│   └── /server\n│       ├── routes.go\n│       ├── routes_test.go\n│       └── server.go\n├── go.mod\n├── go.sum\n├── Makefile\n└── README.md\n```\n\n## Database Driver Implementation\n\nUsers can select the desired database driver based on their project's specific needs. The chosen driver is then imported into the project, and the `database.go` file is adjusted accordingly to establish a connection and manage interactions with the selected database.\n\n## Integration Tests for Database Operations\n\nFor all the database drivers but `Sqlite`, integration tests are automatically generated to ensure that the database connection is working correctly. It uses [Testcontainers for Go](https://golang.testcontainers.org/) to spin up a containerized instance of the database server, run the tests, and then tear down the container.\n\n[Testcontainers for Go](https://golang.testcontainers.org/) is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done.\n\n\n### Requirements\n\nYou need a container runtime installed on your machine. Testcontainers supports Docker and any other container runtime that implements the Docker APIs.\n\nTo install Docker:\n\n```bash\ncurl -sLO get.docker.com\n```\n\n### Running the tests\n\nGo to the `internal/database` directory and run the following command:\n\n```bash\ngo test -v\n```\n\nOr, just run the following command from the root directory:\n\n```bash\nmake itest\n```\n\nTestcontainers automatically pulls the required Docker images and start the containers. The tests run against the containers, and once the tests are done, the containers are stopped and removed. For further information, refer to the [official documentation](https://golang.testcontainers.org/).\n\n## Docker-Compose for Quick Database Spinup\n\nTo facilitate quick setup and testing, a `docker-compose.yml` file is provided. This file defines a service for the chosen database system with the necessary environment variables. Running `docker-compose up` will quickly spin up a containerized instance of the database, allowing users to test their application against a real database server.\n\nThis Docker Compose approach simplifies the process of setting up a database for development or testing purposes, providing a convenient and reproducible environment for the project.\n"
  },
  {
    "path": "docs/docs/blueprint-core/frameworks.md",
    "content": "Created projects can utilize several Go web frameworks to handle HTTP routing and server functionality. The chosen frameworks are:\n\n1. [**Chi**](https://github.com/go-chi/chi): Lightweight and flexible router for building Go HTTP services.\n2. [**Echo**](https://github.com/labstack/echo): High-performance, extensible, minimalist Go web framework.\n3. [**Fiber**](https://github.com/gofiber/fiber): Express-inspired web framework designed to be fast, simple, and efficient.\n4. [**Gin**](https://github.com/gin-gonic/gin): A web framework with a martini-like API, but with much better performance.\n5. [**Gorilla/mux**](https://github.com/gorilla/mux): A powerful URL router and dispatcher for Golang.\n6. [**HttpRouter**](https://github.com/julienschmidt/httprouter): A high-performance HTTP request router that scales well.\n\n## Project Structure\n\nThe project is structured with a simple layout, focusing on the cmd, internal, and tests directories:\n\n```bash\n/(Root)\n├── /cmd\n│   └── /api\n│       └── main.go\n├── /internal\n│   └── /server\n│       ├── routes.go\n│       ├── routes_test.go\n│       └── server.go\n├── go.mod\n├── go.sum\n├── Makefile\n└── README.md\n```\n"
  },
  {
    "path": "docs/docs/blueprint-ui.md",
    "content": "The Blueprint UI is a crucial component of the Go Blueprint ecosystem, providing a user-friendly interface for creating CLI commands and visualizing project structures.\n\nBy visiting the Blueprint UI website at [go-blueprint.dev](https://go-blueprint.dev), users can interact with a visual representation of their project setup before executing commands.\n\n![BlueprintUI](public/blueprint_ui.png)\n\nThis enhances the overall experience of using Go Blueprint by providing a visual representation of project setups and simplifying the command generation process. Check Blueprint UI [code](https://github.com/briancbarrow/go-blueprint-htmx).\n"
  },
  {
    "path": "docs/docs/creating-project/air.md",
    "content": "## Air - Live Reloading Utility\n\n[Air](https://github.com/cosmtrek/air) is a live-reloading utility designed to enhance the development experience by automatically rebuilding and restarting your Go application whenever changes are detected in the source code.\n\nThe Makefile provided in the project repository includes a command make watch, which triggers Air to monitor file changes and initiate rebuilds and restarts as necessary. Additionally, if Air is not installed on your machine, the Makefile provides an option to install it automatically.\n\nAir's `.air.toml` configuration file allows customization of various aspects of its behavior.\n\n## Live Preview\n\n```bash\nmake watch\n\n  __    _   ___  \n / /\\  | | | |_) \n/_/--\\ |_| |_| \\_ v1.51.0, built with Go go1.22.0\n\nmkdir /home/ujstor/code/blueprint-version-test/ws-test4/tmp\nwatching .\nwatching cmd\nwatching cmd/api\nwatching cmd/web\nwatching cmd/web/assets\nwatching cmd/web/assets/js\nwatching internal\nwatching internal/database\nwatching internal/server\nwatching tests\n!exclude tmp\nbuilding...\nmake[1]: Entering directory '/home/ujstor/code/blueprint-version-test/ws-test4'\nBuilding...\nProcessing path: /home/ujstor/code/blueprint-version-test/ws-test4\nGenerated code for \"/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/base.templ\" in 914.556µs\nGenerated code for \"/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/hello.templ\" in 963.157µs\nGenerated code for 2 templates with 0 errors in 1.274392ms\nmake[1]: Leaving directory '/home/ujstor/code/blueprint-version-test/ws-test4'\nrunning...\ninternal/server/routes.go has changed\nbuilding...\nmake[1]: Entering directory '/home/ujstor/code/blueprint-version-test/ws-test4'\nBuilding...\nProcessing path: /home/ujstor/code/blueprint-version-test/ws-test4\nGenerated code for \"/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/base.templ\" in 907.426µs\nGenerated code for \"/home/ujstor/code/blueprint-version-test/ws-test4/cmd/web/hello.templ\" in 1.16142ms\nGenerated code for 2 templates with 0 errors in 1.527556ms\nmake[1]: Leaving directory '/home/ujstor/code/blueprint-version-test/ws-test4'\nrunning...\n```\n\nIntegrating Air into your development workflow alongside the provided Makefile enables a smooth and efficient process for building, testing, and running your Go applications. With automatic live-reloading, you can focus more on coding and less on manual build and restart steps.\n"
  },
  {
    "path": "docs/docs/creating-project/makefile.md",
    "content": "## Makefile Project Management\n\nMakefile is designed for building, running, and testing a Go project. It includes support for advanced options like HTMX and Tailwind CSS, and handles OS-specific operations for Unix-based systems (Linux/macOS) and Windows.\n\n## Targets\n\n***`all`***\n\nThe default target that builds and test the application by running the `build` and `test` target.\n\n***`templ-install`***\n\nThis target installs the Go-based templating tool, `templ`, if it is not already installed. It supports:\n\n- **Unix-based systems**: Prompts the user to install `templ` if it is missing.\n- **Windows**: Uses PowerShell to check for and install `templ`.\n\n***`tailwind-install`***\n\nThis target downloads and sets up `tailwindcss`, depending on the user's operating system:\n\n- **Linux**: Downloads the Linux binary.\n- **macOS**: Downloads the macOS binary.\n- **Windows**: Uses PowerShell to download the Windows executable.\n\n***`build`***\n\nBuilds the Go application and generates assets with `templ` and `tailwind`, if the corresponding advanced options are enabled:\n\n- Uses `templ` to generate templates.\n- Runs `tailwindcss` to compile CSS.\n\n***`run`***\n\nRuns the Go application by executing the `cmd/api/main.go` file and npm install with run dev if React flag is used.\n\n***`docker-run`*** and ***`docker-down`***\n\nThese targets manage a database container:\n\n- **Unix-based systems**: Tries Docker Compose V2 first, falls back to V1 if needed.\n- **Windows**: Uses Docker Compose without version fallback.\n\n***`test`***\n\nRuns unit tests for the application using `go test`.\n\n***`itest`***\n\nRuns integration tests if a database, with the exception of SQLite, is used.\n\n***`clean`***\n\nRemoves the compiled binary (`main` or `main.exe` depending on the OS).\n\n***`watch`***\n\nEnables live reload for the project using the `air` tool:\n\n- **Unix-based systems**: Checks if `air` is installed and prompts for installation if missing.\n- **Windows**: Uses PowerShell to manage `air` installation and execution.\n\n"
  },
  {
    "path": "docs/docs/creating-project/project-init.md",
    "content": "## Creating a Project\n\nAfter installing the Go-Blueprint CLI tool, you can create a new project with the default settings by running the following command:\n\n```bash\ngo-blueprint create\n```\n\nThis command will interactively guide you through the project setup process, allowing you to choose the project name, framework, and database driver.\n\n![BlueprintInteractive](../public/blueprint_1.png)\n\n## Using Flags for Non-Interactive Setup\n\nFor a non-interactive setup, you can use flags to provide the necessary information during project creation. Here's an example:\n\n```\ngo-blueprint create --name my-project --framework gin --driver postgres --git commit\n```\n\nIn this example:\n\n- `--name`: Specifies the name of the project (replace \"my-project\" with your desired project name).\n- `--framework`: Specifies the Go framework to be used (e.g., \"gin\").\n- `--driver`: Specifies the database driver to be integrated (e.g., \"postgres\").\n- `--git`: Specifies the git configuration option of the project (e.g., \"commit\").\n\nCustomize the flags according to your project requirements.\n\n## Advanced Flag\n\nBy including the `--advanced` flag, users can choose one or all of the advanced features, HTMX, GitHub Actions for CI/CD, Websocket, Docker and TailwindCSS support, during the project creation process. The flag enhances the simplicity of Blueprint while offering flexibility for users who require additional functionality.\n\n```bash\ngo-blueprint create --advanced\n```\n\nTo recreate the project using the same configuration semi-interactively, use the following command:\n```bash\ngo-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced\n```\nThis approach opens interactive mode only for advanced features, which allows you to choose the one or combination of available features.\n\n![AdvancedFlag](../public/blueprint_advanced.png)\n\n## Non-Interactive Setup\n\nAdvanced features can be enabled using the `--feature` flag along with the `--advanced` flag:\n\nHTMX:\n```bash\ngo-blueprint create --advanced --feature htmx\n```\n\nCI/CD workflow:\n```bash\ngo-blueprint create --advanced --feature githubaction\n```\n\nWebsocket:\n```bash\ngo-blueprint create --advanced --feature websocket\n```\nTailwindCSS:\n```bash\ngo-blueprint create --advanced --feature tailwind\n```\nDocker:\n```bash\ngo-blueprint create --advanced --feature docker\n```\n\nOr all features at once:\n```bash\ngo-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker\n```\n"
  },
  {
    "path": "docs/docs/endpoints-test/mongo.md",
    "content": "To test the MongoDB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```\n## Health Function\n\nThe `Health` function checks the health of the MongoDB by pinging it. It returns a simple map containing a health message.\n\n### Functionality\n\n**Ping MongoDB Server**: The function pings the MongoDB thru server to check its availability.\n\n   - If the ping fails, it logs the error and terminates the program.\n   - If the ping succeeds, it returns a health message indicating that the server is healthy.\n\n### Sample Output\n\nThe `Health` returns a JSON-like map structure with a single key indicating the health status:\n\n```json\n{\n  \"message\": \"It's healthy\"\n}\n```\n\n## Code implementation\n\n```go\nfunc (s *service) Health() map[string]string {\n    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n    defer cancel()\n\n    err := s.db.Ping(ctx, nil)\n    if err != nil {\n        log.Fatalf(\"db down: %v\", err) \n    }\n\n    return map[string]string{\n        \"message\": \"It's healthy\",\n    }\n}\n```\n\n## Note\n\nMongoDB does not support advanced health check functions like SQL databases or Redis. The implementation is basic, providing only a simple ping response to indicate if the server is reachable and DB connection healthy.\n"
  },
  {
    "path": "docs/docs/endpoints-test/redis.md",
    "content": "To test the Redis Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```\n\n## Health Function\n\nThe `Health` function orchestrates the health assessment of the Redis server by invoking the `checkRedisHealth` function and returning the collected statistics.\n\n### Functionality\n\n**Check Redis Health**: The function pings the Redis server to check its availability and adds the response to the stats map.\n\n   - If the ping fails, it logs the error and terminates the program.\n   - If the ping succeeds, it proceeds to retrieve additional information.\n\n**Retrieve Redis Information**: The function retrieves information about the Redis server, including version, mode, connected clients, memory usage, uptime, etc.\n\n   - If an error occurs during info retrieval, it updates the health message accordingly.\n\n**Evaluate Redis Statistics**: The function evaluates the collected statistics to identify potential issues and updates the health message accordingly.\n\n   - It checks for high number of connected clients, stale connections, memory usage, recent restart, high idle connections, and high connection pool utilization.\n\n### Sample Output\n\nThe `Health` function returns a JSON-like map structure with various keys representing different health metrics and their corresponding values.\n\n```json\n{\n  \"redis_active_connections\": \"0\",\n  \"redis_connected_clients\": \"1\",\n  \"redis_hits_connections\": \"1\",\n  \"redis_idle_connections\": \"1\",\n  \"redis_max_memory\": \"0\",\n  \"redis_message\": \"Redis has been recently restarted\",\n  \"redis_misses_connections\": \"1\",\n  \"redis_mode\": \"standalone\",\n  \"redis_ping_response\": \"PONG\",\n  \"redis_pool_size_percentage\": \"0.42%\",\n  \"redis_stale_connections\": \"0\",\n  \"redis_status\": \"up\",\n  \"redis_timeouts_connections\": \"0\",\n  \"redis_total_connections\": \"1\",\n  \"redis_uptime_in_seconds\": \"55\",\n  \"redis_used_memory\": \"980704\",\n  \"redis_used_memory_peak\": \"980704\",\n  \"redis_version\": \"7.2.4\"\n}\n```\n\n### Serialization/deserialization\n\nThe `Sample Output` is dynamic and unstructured since it depends on the raw map. To make it structurable, it must implement `JSON serialization/deserialization` or `Other serialization/deserialization` (e.g, `XML serialization/deserialization`) to bind it. For example:\n\n- `JSON serialization/deserialization`\n\n```json\n{\n  \"redis_health\": {\n    \"status\": \"up\",\n    \"message\": \"Redis connection pool utilization is high\",\n    \"stats\": {\n      \"version\": \"7.0.15\",\n      \"mode\": \"standalone\",\n      \"connected_clients\": \"10\",\n      \"memory\": {\n        \"used\": {\n          \"mb\": \"22.38\",\n          \"gb\": \"0.02\"\n        },\n        \"peak\": {\n          \"mb\": \"46.57\",\n          \"gb\": \"0.05\"\n        },\n        \"free\": {\n          \"mb\": \"1130.00\",\n          \"gb\": \"1.10\"\n        },\n        \"percentage\": \"1.98%\"\n      },\n      \"uptime_stats\": \"6 days, 3 hours, 37 minutes, 20 seconds\",\n      \"uptime\": [\n        {\n          \"day\": \"6\"\n        },\n        {\n          \"hour\": \"3\"\n        },\n        {\n          \"minute\": \"37\"\n        },\n        {\n          \"second\": \"20\"\n        }\n      ],\n      \"pooling\": {\n        \"figures\": {\n          \"hits\": \"10\",\n          \"misses\": \"2\",\n          \"timeouts\": \"0\",\n          \"total\": \"4\",\n          \"stale\": \"9\",\n          \"idle\": \"5\",\n          \"active\": \"0\",\n          \"percentage\": \"62.50%\"\n        },\n        \"observed_total\": \"26\"\n      }\n    }\n  }\n}\n```\n\n- `XML serialization/deserialization`\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<redis_health>\n  <status>up</status>\n  <message>Redis connection pool utilization is high</message>\n  <stats>\n    <version>7.0.15</version>\n    <mode>standalone</mode>\n    <connected_clients>10</connected_clients>\n    <memory>\n      <used>\n        <mb>22.38</mb>\n        <gb>0.02</gb>\n      </used>\n      <peak>\n        <mb>46.57</mb>\n        <gb>0.05</gb>\n      </peak>\n      <free>\n        <mb>1130.00</mb>\n        <gb>1.10</gb>\n      </free>\n      <percentage>1.98%</percentage>\n    </memory>\n    <uptime_stats>6 days, 3 hours, 37 minutes, 20 seconds</uptime_stats>\n    <uptime>\n      <day>6</day>\n      <hour>3</hour>\n      <minute>37</minute>\n      <second>20</second>\n    </uptime>\n    <pooling>\n      <figures>\n        <hits>10</hits>\n        <misses>2</misses>\n        <timeouts>0</timeouts>\n        <total>4</total>\n        <stale>9</stale>\n        <idle>5</idle>\n        <active>0</active>\n        <percentage>62.50%</percentage>\n      </figures>\n      <observed_total>26</observed_total>\n    </pooling>\n  </stats>\n</redis_health>\n```\n\n## Code Implementation\n\n```go\nfunc (s *service) Health() map[string]string {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // Default is now 5s\n\tdefer cancel()\n\n\tstats := make(map[string]string)\n\n\tstats = s.checkRedisHealth(ctx, stats)\n\n\treturn stats\n}\n\nfunc (s *service) checkRedisHealth(ctx context.Context, stats map[string]string) map[string]string {\n\tpong, err := s.db.Ping(ctx).Result()\n\tif err != nil {\n\t\tlog.Fatalf(\"db down: %v\", err) \n\t}\n\n\tstats[\"redis_status\"] = \"up\"\n\tstats[\"redis_message\"] = \"It's healthy\"\n\tstats[\"redis_ping_response\"] = pong\n\n\tinfo, err := s.db.Info(ctx).Result()\n\tif err != nil {\n\t\tstats[\"redis_message\"] = fmt.Sprintf(\"Failed to retrieve Redis info: %v\", err)\n\t\treturn stats\n\t}\n\n\tredisInfo := parseRedisInfo(info)\n\n\tpoolStats := s.db.PoolStats()\n\n\tstats[\"redis_version\"] = redisInfo[\"redis_version\"]\n\tstats[\"redis_mode\"] = redisInfo[\"redis_mode\"]\n\tstats[\"redis_connected_clients\"] = redisInfo[\"connected_clients\"]\n\tstats[\"redis_used_memory\"] = redisInfo[\"used_memory\"]\n\tstats[\"redis_used_memory_peak\"] = redisInfo[\"used_memory_peak\"]\n\tstats[\"redis_uptime_in_seconds\"] = redisInfo[\"uptime_in_seconds\"]\n\tstats[\"redis_hits_connections\"] = strconv.FormatUint(uint64(poolStats.Hits), 10)\n\tstats[\"redis_misses_connections\"] = strconv.FormatUint(uint64(poolStats.Misses), 10)\n\tstats[\"redis_timeouts_connections\"] = strconv.FormatUint(uint64(poolStats.Timeouts), 10)\n\tstats[\"redis_total_connections\"] = strconv.FormatUint(uint64(poolStats.TotalConns), 10)\n\tstats[\"redis_idle_connections\"] = strconv.FormatUint(uint64(poolStats.IdleConns), 10)\n\tstats[\"redis_stale_connections\"] = strconv.FormatUint(uint64(poolStats.StaleConns), 10)\n\tstats[\"redis_max_memory\"] = redisInfo[\"maxmemory\"]\n\n\tactiveConns := uint64(math.Max(float64(poolStats.TotalConns-poolStats.IdleConns), 0))\n\tstats[\"redis_active_connections\"] = strconv.FormatUint(activeConns, 10)\n\n\tpoolSize := s.db.Options().PoolSize\n\tconnectedClients, _ := strconv.Atoi(redisInfo[\"connected_clients\"])\n\tpoolSizePercentage := float64(connectedClients) / float64(poolSize) * 100\n\tstats[\"redis_pool_size_percentage\"] = fmt.Sprintf(\"%.2f%%\", poolSizePercentage)\n\n\treturn s.evaluateRedisStats(redisInfo, stats)\n}\n\nfunc (s *service) evaluateRedisStats(redisInfo, stats map[string]string) map[string]string {\n\tpoolSize := s.db.Options().PoolSize\n\tpoolStats := s.db.PoolStats()\n\tconnectedClients, _ := strconv.Atoi(redisInfo[\"connected_clients\"])\n\thighConnectionThreshold := int(float64(poolSize) * 0.8)\n\n\tif connectedClients > highConnectionThreshold {\n\t\tstats[\"redis_message\"] = \"Redis has a high number of connected clients\"\n\t}\n\n\tminStaleConnectionsThreshold := 500\n\tif int(poolStats.StaleConns) > minStaleConnectionsThreshold {\n\t\tstats[\"redis_message\"] = fmt.Sprintf(\"Redis has %d stale connections.\", poolStats.StaleConns)\n\t}\n\n\tusedMemory, _ := strconv.ParseInt(redisInfo[\"used_memory\"], 10, 64)\n\tmaxMemory, _ := strconv.ParseInt(redisInfo[\"maxmemory\"], 10, 64)\n\tif maxMemory > 0 {\n\t\tusedMemoryPercentage := float64(usedMemory) / float64(maxMemory) * 100\n\t\tif usedMemoryPercentage >= 90 {\n\t\t\tstats[\"redis_message\"] = \"Redis is using a significant amount of memory\"\n\t\t}\n\t}\n\n\tuptimeInSeconds, _ := strconv.ParseInt(redisInfo[\"uptime_in_seconds\"], 10, 64)\n\tif uptimeInSeconds < 3600 {\n\t\tstats[\"redis_message\"] = \"Redis has been recently restarted\"\n\t}\n\n\tidleConns := int(poolStats.IdleConns)\n\thighIdleConnectionThreshold := int(float64(poolSize) * 0.7)\n\tif idleConns > highIdleConnectionThreshold {\n\t\tstats[\"redis_message\"] = \"Redis has a high number of idle connections\"\n\t}\n\n\tpoolUtilization := float64(poolStats.TotalConns-poolStats.IdleConns) / float64(poolSize) * 100\n\thighPoolUtilizationThreshold := 90.0\n\tif poolUtilization > highPoolUtilizationThreshold {\n\t\tstats[\"redis_message\"] = \"Redis connection pool utilization is high\"\n\t}\n\n\treturn stats\n}\n```\n"
  },
  {
    "path": "docs/docs/endpoints-test/scylladb.md",
    "content": "To test the ScyllaDB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```\n\n## Health Function\n\nThe `Health` function checks the health of the ScyllaDB Cluster by pinging\nthe [Coordinator Node](https://opensource.docs.scylladb.com/stable/architecture/architecture-fault-tolerance.html). It\nreturns a simple map containing a health message.\n\n### Functionality\n\n**Ping ScyllaDB Server**: The function pings the ScyllaDB through server to check its availability.\n\n- If the ping fails, it logs the error and terminates the program.\n- If the ping succeeds, it returns a health message indicating that the server with some .\n\n### Sample Output\n\nThe `Health` returns a JSON-like map structure with a single key indicating the health status:\n\n```json\n{\n  \"message\": \"It's healthy\",\n  \"status\": \"up\",\n  \"scylla_cluster_nodes_up\": \"3\",\n  \"scylla_cluster_nodes_down\": \"0\",\n  \"scylla_cluster_size\": \"1\",\n  \"scylla_current_datacenter\": \"datacenter1\",\n  \"scylla_current_time\": \"2024-11-04 22:59:21.69 +0000 UTC\",\n  \"scylla_health_check_duration\": \"16.896976ms\",\n  \"scylla_keyspaces\": \"6\"\n}\n```\n\n## ScyllaDB Setup\n\nBefore starting the cluster, ensure the [fs.aio-max-nr](https://www.kernel.org/doc/Documentation/sysctl/fs.txt) value is\nsufficient (e.g. `1048576` or `2097152` or more).\n\nIf you prefer to configure it manually, run one of the following commands to check the current value:\n\n```sh\nsysctl --all | grep --word-regexp -- 'aio-max-nr'\n```\n\n```sh\nsysctl fs.aio-max-nr\n```\n\n```sh\ncat /proc/sys/fs/aio-max-nr\n```\n\nIf the value is lower than required, you can use one of these commands:\n\n```sh\n# Update config non-persistent\nsysctl --write fs.aio-max-nr=1048576\n```\n\nHere's some links for more relevant information and automation:\n\n* [Repository: gvieira/ws-scylla](https://github.com/gvieira18/ws-scylla/) - Simple ScyllaDB Cluster management with\n  Makefiles\n* [ScyllaDB University: 101 Essentials Track](https://university.scylladb.com/courses/scylla-essentials-overview) -\n  Learn the base concepts of ScyllaDB\n\n## Code implementation\n\nHere you can check how the Health Check is done under the hood:\n\n```go\nfunc (s *service) Health() map[string]string {\n    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n    defer cancel()\n    \n    stats := make(map[string]string)\n    \n    // Check ScyllaDB health and populate the stats map\n    startedAt := time.Now()\n    \n    // Execute a simple query to check connectivity\n    query := \"SELECT now() FROM system.local\"\n    iter := s.Session.Query(query).WithContext(ctx).Iter()\n    var currentTime time.Time\n    if !iter.Scan(&currentTime) {\n        if err := iter.Close(); err != nil {\n            stats[\"status\"] = \"down\"\n            stats[\"message\"] = fmt.Sprintf(\"Failed to execute query: %v\", err)\n            return stats\n        }\n    }\n    if err := iter.Close(); err != nil {\n        stats[\"status\"] = \"down\"\n        stats[\"message\"] = fmt.Sprintf(\"Error during query execution: %v\", err)\n        return stats\n    }\n    \n    // ScyllaDB is up\n    stats[\"status\"] = \"up\"\n    stats[\"message\"] = \"It's healthy\"\n    stats[\"scylla_current_time\"] = currentTime.String()\n    \n    // Retrieve cluster information\n    // Get keyspace information\n    getKeyspacesQuery := \"SELECT keyspace_name FROM system_schema.keyspaces\"\n    keyspacesIterator := s.Session.Query(getKeyspacesQuery).Iter()\n    \n    stats[\"scylla_keyspaces\"] = strconv.Itoa(keyspacesIterator.NumRows())\n    if err := keyspacesIterator.Close(); err != nil {\n        log.Fatalf(\"Failed to close keyspaces iterator: %v\", err)\n    }\n    \n    // Get cluster information\n    var currentDatacenter string\n    var currentHostStatus bool\n    \n    var clusterNodesUp uint\n    var clusterNodesDown uint\n    var clusterSize uint\n    \n    clusterNodesIterator := s.Session.Query(\"SELECT dc, up FROM system.cluster_status\").Iter()\n    for clusterNodesIterator.Scan(&currentDatacenter, &currentHostStatus) {\n        clusterSize++\n        if currentHostStatus {\n            clusterNodesUp++\n        } else {\n            clusterNodesDown++\n        }\n    }\n    \n    if err := clusterNodesIterator.Close(); err != nil {\n        log.Fatalf(\"Failed to close cluster nodes iterator: %v\", err)\n    }\n    \n    stats[\"scylla_cluster_size\"] = strconv.Itoa(int(clusterSize))\n    stats[\"scylla_cluster_nodes_up\"] = strconv.Itoa(int(clusterNodesUp))\n    stats[\"scylla_cluster_nodes_down\"] = strconv.Itoa(int(clusterNodesDown))\n    stats[\"scylla_current_datacenter\"] = currentDatacenter\n    \n    // Calculate the time taken to perform the health check\n    stats[\"scylla_health_check_duration\"] = time.Since(startedAt).String()\n    return stats\n}\n\n```\n\n## Note\n\nScylladb does not support advanced health check functions like SQL databases or Redis.\nThe current implementation is based on queries at `system` related keyspaces.\n"
  },
  {
    "path": "docs/docs/endpoints-test/server.md",
    "content": "## Testing Endpoints with CURL and WebSocat\n\nTesting endpoints is an essential part of ensuring the correctness and functionality of your app. Depending on what options are used for go-blueprint project creation, you have various endpoints for testing your init application status.\n\n\nBefore proceeding, ensure you have the following tools installed:\n\n- [CURL](https://curl.se/docs/manpage.html): A command-line tool for transferring data with URLs.\n- [WebSocat](https://github.com/vi/websocat): A command-line WebSocket client.\n\nYou can utilize alternative tools that support the WebSocket protocol to establish connections with the server. WebSocat is an open-source CLI tool, while [POSTMAN](https://www.postman.com/) serves as a GUI tool specifically designed for testing APIs and WebSocket functionality.\n\n## Hello World Endpoint\n\nTo test the Hello World endpoint, execute the following curl command:\n\n```bash\ncurl http://localhost:PORT\n```\n\nSample Output:\n```json\n{\"message\": \"Hello World\"}\n```\nIf the server is running and it is healthy, you should see the message 'Hello World' in the response.\nAlso, depending on the framework you are using, there will be logs in the terminal:\n\n```bash\nmake run\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:   export GIN_MODE=release\n - using code:  gin.SetMode(gin.ReleaseMode)\n\n[GIN-debug] GET    /                         --> websocket-test/internal/server.(*Server).HelloWorldHandler-fm (3 handlers)\n[GIN-debug] GET    /health                   --> websocket-test/internal/server.(*Server).healthHandler-fm (3 handlers)\n[GIN-debug] GET    /websocket                --> websocket-test/internal/server.(*Server).websocketHandler-fm (3 handlers)\n[GIN] 2024/05/28 - 17:44:31 | 200 |       27.93µs |       127.0.0.1 | GET      \"/\"\n```\n"
  },
  {
    "path": "docs/docs/endpoints-test/sql.md",
    "content": "To test the SQL DB Health Check endpoint, use the following curl command:\n\n```bash\ncurl http://localhost:PORT/health\n```\n## Health Function\n\nThe `Health` function checks the health of the database connection by pinging the database and retrieving various statistics. It returns a map with keys indicating different health metrics.\n\n### Functionality\n\n**Ping the Database**: The function pings the database to ensure it is reachable.\n\n   - If the database is down, it logs the error, sets the status to \"down,\" and terminates the program.\n   - If the database is up, it proceeds to gather additional statistics.\n\n**Collect Database Statistics**: The function retrieves the following statistics from the database connection:\n\n   - `open_connections`: Number of open connections to the database.\n   - `in_use`: Number of connections currently in use.\n   - `idle`: Number of idle connections.\n   - `wait_count`: Number of times a connection has to wait.\n   - `wait_duration`: Total time connections have spent waiting.\n   - `max_idle_closed`: Number of connections closed due to exceeding idle time.\n   - `max_lifetime_closed`: Number of connections closed due to exceeding their lifetime.\n\n**Evaluate Statistics**: Evaluates the collected statistics to provide a health message. Based on predefined thresholds, it updates the health message to indicate potential issues, such as heavy load or high wait events.\n\n### Sample Output\n\nThe `Health` function returns a JSON-like map structure with the following keys and example values:\n\n```json\n{\n  \"idle\": \"1\",\n  \"in_use\": \"0\",\n  \"max_idle_closed\": \"0\",\n  \"max_lifetime_closed\": \"0\",\n  \"message\": \"It's healthy\",\n  \"open_connections\": \"1\",\n  \"status\": \"up\",\n  \"wait_count\": \"0\",\n  \"wait_duration\": \"0s\"\n}\n```\n\n## Code Implementation\n\n```go\nfunc (s *service) Health() map[string]string {\n    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\n    defer cancel()\n\n    stats := make(map[string]string)\n\n    err := s.db.PingContext(ctx)\n    if err != nil {\n        stats[\"status\"] = \"down\"\n        stats[\"error\"] = fmt.Sprintf(\"db down: %v\", err)\n        log.Fatalf(\"db down: %v\", err)  \n        return stats\n    }\n\n    stats[\"status\"] = \"up\"\n    stats[\"message\"] = \"It's healthy\"\n\n    dbStats := s.db.Stats()\n    stats[\"open_connections\"] = strconv.Itoa(dbStats.OpenConnections)\n    stats[\"in_use\"] = strconv.Itoa(dbStats.InUse)\n    stats[\"idle\"] = strconv.Itoa(dbStats.Idle)\n    stats[\"wait_count\"] = strconv.FormatInt(dbStats.WaitCount, 10)\n    stats[\"wait_duration\"] = dbStats.WaitDuration.String()\n    stats[\"max_idle_closed\"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)\n    stats[\"max_lifetime_closed\"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)\n\n    if dbStats.OpenConnections > 40 { \n        stats[\"message\"] = \"The database is experiencing heavy load.\"\n    }\n\n    if dbStats.WaitCount > 1000 {\n        stats[\"message\"] = \"The database has a high number of wait events, indicating potential bottlenecks.\"\n    }\n\n    if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {\n        stats[\"message\"] = \"Many idle connections are being closed, consider revising the connection pool settings.\"\n    }\n\n    if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {\n        stats[\"message\"] = \"Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern.\"\n    }\n\n    return stats\n}\n```\n"
  },
  {
    "path": "docs/docs/endpoints-test/web.md",
    "content": "\nTo test the /web endpoint when HTMX and Temp are used, you can simply open it in a web browser. This endpoint serves an HTML page with a form.\n\nNavigate to `http://localhost:PORT/web`\n\nThis page contains a form with a single input field and a submit button. Upon submitting the form, \"Hello, [input]\" will be displayed.\n\n## Sample output\n\n![htmx](../public/htmx.png)\n\n## Terminal log\n\n```bash\nmake run\n2024/05/28 20:42:06 \"POST http://localhost:8070/hello HTTP/1.1\" from 127.0.0.1:45494 - 200 24B in 53.23µs\n```\n"
  },
  {
    "path": "docs/docs/endpoints-test/websocket.md",
    "content": "## Testing with WebSocat\n[WebSocat](https://github.com/vi/websocat) is a versatile tool for working with websockets from the command line. Below are some examples of using WebSocat to test the websocket endpoint:\n\n```bash\n# Start server\nmake run\n``` \n\n```bash\n# Connect to the websocket endpoint\n$ websocat ws://localhost:PORT/websocket\n```\n\nReplace `PORT` with the port number on which your server is running.\n\n## Sample Output\nUpon successful connection, the client should start receiving timestamp messages from the server every 2 seconds.\n\n```bash\nserver timestamp: 1709046650354893857\nserver timestamp: 1709046652355956336\nserver timestamp: 1709046654357101642\nserver timestamp: 1709046656357202535\nserver timestamp: 1709046658358258120\nserver timestamp: 1709046660359338389\nserver timestamp: 1709046662360422533\nserver timestamp: 1709046664361194735\nserver timestamp: 1709046666362308678\nserver timestamp: 1709046668363390475\nserver timestamp: 1709046670364477838\nserver timestamp: 1709046672365193667\nserver timestamp: 1709046674366265199\nserver timestamp: 1709046676366564490\nserver timestamp: 1709046678367646090\nserver timestamp: 1709046680367851980\nserver timestamp: 1709046682368920527\n```\n"
  },
  {
    "path": "docs/docs/index.md",
    "content": "---\nhide:\n  - toc\n---\n\n## Go Blueprint - Ultimate Golang Blueprint Library\n\n![logo](./public/logo.png)\n\nPowerful CLI tool designed to streamline the process of creating Go projects with a robust and standardized structure. Not only does Go Blueprint facilitate project initialization, but it also offers seamless integration with popular Go frameworks, allowing you to focus on your application's code from the very beginning.\n\n## Why Choose Go Blueprint?\n\n- **Easy Setup and Installation**: Go Blueprint simplifies the setup process, making it a breeze to install and get started with your Go projects.\n\n- **Pre-established Go Project Structure**: Save time and effort by having the entire Go project structure set up automatically. No need to worry about directory layouts or configuration files.\n\n- **HTTP Server Configuration Made Easy**: Whether you prefer Go's standard library HTTP package, Chi, Gin, Fiber, HttpRouter, Gorilla/mux or Echo, Go Blueprint caters to your server setup needs.\n\n- **Focus on Your Application Code**: With Go Blueprint handling the project scaffolding, you can dedicate more time and energy to developing your application logic.\n\n## Project Structure\n\nHere's an overview of the project structure created by Go Blueprint when all options are utilized:\n\n```bash\n/ (Root)\n├── .github/\n│   └── workflows/\n│       ├── go-test.yml           # GitHub Actions workflow for running tests.\n│       └── release.yml           # GitHub Actions workflow for releasing the application.\n├── cmd/\n│   ├── api/\n│   │   └── main.go               # Main file for starting the server.\n│   └── web/\n│       ├── styles/               # Only for generating css will not be served public.\n│       │   └── input.css         # Tailwind input file for compiling output.css with CLI when HTMX is used.\n│       ├── assets/\n│       │   ├── css/\n│       │   │   └── output.css    # Generated CSS file.\n│       │   └── js/\n│       │       └── htmx.min.js   # HTMX library for dynamic HTML content.\n│       ├── base.templ            # Base HTML template file.\n│       ├── base_templ.go         # Generated Go code for base template.\n│       ├── efs.go                # Includes assets into compiled binary.\n│       ├── hello.go              # Logic for handling \"hello\" form.\n│       ├── hello.templ           # Template file for the \"hello\" endpoint.\n│       └── hello_templ.go        # Generated Go code for the \"hello\" template.\n├── frontend/                     # React advanced flag. Excludes HTMX.\n│   ├── node_modules/             # Node dependencies.\n│   ├── public/\n│   │   ├── index.html\n│   │   └── favicon.ico\n│   ├── src/                      # React source files.\n│   │   ├── App.tsx               # Main React component.\n│   │   ├── assets/               # React assets directory.\n│   │   │   └── logo.svg\n│   │   ├── components/           # React components directory.\n│   │   │   ├── Header.tsx\n│   │   │   └── Footer.tsx\n│   │   ├── styles/               # CSS/SCSS styles directory.\n│   │   │   └── global.css\n│   │   └── index.tsx             # Main entry point for React\n│   ├── eslint.config.js          # ESLint configuration file.\n│   ├── index.html                # Base HTML template.\n│   ├── package.json              # Node.js package configuration.\n│   ├── package-lock.json         # Lock file for Node.js dependencies.\n│   ├── README.md                 # README file for the React project.\n│   ├── tsconfig.app.json         # TypeScript configuration for the app.\n│   ├── tsconfig.json             # Root TypeScript configuration.\n│   ├── tsconfig.node.json        # TypeScript configuration for Node.js.\n│   └── vite.config.ts            # Vite configuration file.\n├── internal/\n│   ├── database/\n│   │   ├── database_test.go      # File containing integration tests for the database operations.\n│   │   └── database.go           # File containing functions related to database operations.\n│   └── server/\n│       ├── routes.go             # File defining HTTP routes.\n│       ├── routes_test.go        # Test file for testing HTTP handlers.\n│       └── server.go             # Main server logic.\n├── .air.toml                     # Configuration file for Air, a live-reload utility.\n├── docker-compose.yml            # Docker Compose configuration.\n├── Dockerfile                    # Dockerfile configuration for the Go project.\n├── .env                          # Environment configuration file.\n├── .gitignore                    # File specifying which files and directories to ignore in Git.\n├── go.mod                        # Go module file for managing dependencies.\n├── .goreleaser.yml               # Configuration file for GoReleaser, a tool for building and releasing binaries.\n├── go.sum                        # Go module file containing checksums for dependencies.\n├── Makefile                      # Makefile for defining and running commands.\n└── README.md                     # Project's README file containing essential information about the project.\n\n```\n\nThis structure provides a comprehensive organization of your project, separating source code, tests, configurations and documentation.\n"
  },
  {
    "path": "docs/docs/installation.md",
    "content": "---\nhide:\n  - toc\n---\n\nGo-Blueprint provides a convenient CLI tool to effortlessly set up your Go projects. Follow the steps below to install the tool on your system.\n\n## Binary Installation\n\nTo install the Go-Blueprint CLI tool as a binary, run the following command:\n\n```sh\ngo install github.com/melkeydev/go-blueprint@latest\n```\n\nThis command installs the Go-Blueprint binary, automatically binding it to your `$GOPATH`.\n\n> If you’re using Zsh, you’ll need to add it manually to `~/.zshrc`.\n\n> After running the installation command, you need to update your `PATH` environment variable. To do this, you need to find out the correct `GOPATH` for your system. You can do this by running the following command:\n> Check your `GOPATH`\n>\n> ```\n> go env GOPATH\n> ```\n>\n> Then, add the following line to your `~/.zshrc` file:\n>\n> ```\n> GOPATH=$HOME/go  PATH=$PATH:/usr/local/go/bin:$GOPATH/bin\n> ```\n>\n> Save the changes to your `~/.zshrc` file by running the following command:\n>\n> ```\n> source ~/.zshrc\n> ```\n\n## NPM Install\n\nIf you prefer using Node.js package manager, you can install Go-Blueprint via NPM. This method is convenient for developers who are already working in JavaScript/Node.js environments and want to integrate Go-Blueprint into their existing workflow.\n\n```bash\nnpm install -g @melkeydev/go-blueprint\n```\n\nThe `-g` flag installs Go-Blueprint globally, making it accessible from any directory on your system.\n\n## Homebrew Install\n\nFor macOS and Linux users, Homebrew provides a simple way to install Go-Blueprint. Homebrew automatically handles dependencies and keeps the tool updated through its package management system.\n\n```bash\nbrew install go-blueprint\n```\n\nAfter installation via Homebrew, Go-Blueprint will be automatically added to your PATH, making it immediately available in your terminal.\n\n## Building and Installing from Source\n\nIf you prefer to build and install Go-Blueprint directly from the source code, you can follow these steps:\n\nClone the Go-Blueprint repository from GitHub:\n\n```sh\ngit clone https://github.com/melkeydev/go-blueprint\n```\n\nBuild the Go-Blueprint binary:\n\n```sh\ngo build\n```\n\nInstall in your `$PATH` to make it accessible system-wide:\n\n```sh\ngo install\n```\n\nVerify the installation by running:\n\n```sh\ngo-blueprint version\n```\n\nThis should display the version information of the installed Go-Blueprint.\n\nNow you have successfully built and installed Go-Blueprint from the source code.\n"
  },
  {
    "path": "docs/mkdocs.yml",
    "content": "### Site metadata ###\n\nsite_name: Go-Blueprint Docs\nsite_description: Official documentation for Go-Blueprint project\nsite_url: https://docs.go-blueprint.dev/\n\nrepo_url: https://github.com/Melkeydev/go-blueprint\nedit_uri: edit/main/docs/docs\n\n### Build settings ###\n\ntheme:\n  name: material\n  custom_dir: custom_theme/\n  theme:\n  features:\n    - navigation.instant\n    - navigation.sections\n    - navigation.footer\n    - toc.flow\n  palette: \n    - scheme: default\n      toggle:\n        icon: material/brightness-7\n        name: Switch to dark mode\n    - scheme: slate\n      toggle:\n        icon: material/brightness-4\n        name: Switch to light mode\n\nnav:\n  - Home: index.md\n  - Installation: installation.md\n  - Blueprint UI: blueprint-ui.md\n  - Project creation & default config:\n    - Project init: creating-project/project-init.md\n    - Makefile: creating-project/makefile.md\n    - Air: creating-project/air.md\n  - Blueprint Core:\n    - Frameworks: blueprint-core/frameworks.md\n    - DB Drivers: blueprint-core/db-drivers.md\n  - Advanced Flag:\n    - AF Usage: advanced-flag/advanced-flag.md\n    - HTMX and Templ: advanced-flag/htmx-templ.md\n    - Tailwind CSS: advanced-flag/tailwind.md\n    - GoReleaser & GoTest CI: advanced-flag/goreleaser.md\n    - Websocket: advanced-flag/websocket.md\n    - Docker: advanced-flag/docker.md\n    - React & Vite (TypeScript): advanced-flag/react-vite.md\n  - Testing endpoints: \n    - Server: endpoints-test/server.md\n    - DB Health Endpoints: \n       - SQL DBs: endpoints-test/sql.md\n       - Redis: endpoints-test/redis.md\n       - MongoDB: endpoints-test/mongo.md\n       - ScyllaDB: endpoints-test/scylladb.md\n    - Websocket: endpoints-test/websocket.md\n    - Web Endpoint: endpoints-test/web.md\n\nextra:\n    social:\n      - icon: fontawesome/brands/discord\n        link: https://discord.com/invite/HHZMSCu\n        name: Discord\n      - icon: fontawesome/brands/twitch\n        link: https://www.twitch.tv/melkey\n        name: Twitch\n      - icon: fontawesome/brands/youtube\n        link: https://www.youtube.com/@MelkeyDev\n        name: YouTube\n      - icon: fontawesome/brands/twitter\n        link: https://x.com/MelkeyDev\n        name: Twitter\n    generator: false\n\ncopyright: Copyright &copy; 2025 Melkey\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "mkdocs==1.5.3\nmkdocs-material==9.5.15"
  },
  {
    "path": "go.mod",
    "content": "module github.com/melkeydev/go-blueprint\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/charmbracelet/bubbles v0.16.1\n\tgithub.com/charmbracelet/bubbletea v0.24.2\n\tgithub.com/charmbracelet/lipgloss v0.9.0\n\tgithub.com/spf13/cobra v1.7.0\n\tgithub.com/spf13/pflag v1.0.5\n)\n\nrequire (\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.18 // indirect\n\tgithub.com/mattn/go-localereader v0.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.15 // indirect\n\tgithub.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/reflow v0.3.0 // indirect\n\tgithub.com/muesli/termenv v0.15.2 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgolang.org/x/sync v0.1.0 // indirect\n\tgolang.org/x/sys v0.12.0 // indirect\n\tgolang.org/x/term v0.6.0 // indirect\n\tgolang.org/x/text v0.3.8 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=\ngithub.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=\ngithub.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=\ngithub.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=\ngithub.com/charmbracelet/lipgloss v0.9.0 h1:BHIM7U4vX77xGEld8GrTKspBMtSv7j0wxPCH73nrdxE=\ngithub.com/charmbracelet/lipgloss v0.9.0/go.mod h1:h8KDyaivONasw1Bhb4nWiKlk4P1wHPly+3+3v6EFMmA=\ngithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=\ngithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=\ngithub.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=\ngithub.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=\ngithub.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=\ngithub.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=\ngithub.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=\ngithub.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=\ngithub.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=\ngithub.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=\ngithub.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngolang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport \"github.com/melkeydev/go-blueprint/cmd\"\n\nfunc main() {\n\tcmd.Execute()\n}\n"
  },
  {
    "path": "scripts/completions.sh",
    "content": "#!/bin/sh\nset -e\nrm -rf completions\nmkdir completions\nfor sh in bash zsh fish; do\n  go run main.go completion \"$sh\" >\"completions/go-blueprint.$sh\"\ndone\n"
  },
  {
    "path": "scripts/create-npm-packages.sh",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nVERSION=\"$1\"\nPACKAGE_NAME=\"@melkeydev/go-blueprint\"\nMAIN_PACKAGE_DIR=\"npm-package\"\nPLATFORM_PACKAGES_DIR=\"platform-packages\"\n\nrm -rf \"$MAIN_PACKAGE_DIR\" \"$PLATFORM_PACKAGES_DIR\"\n\nmkdir -p \"$MAIN_PACKAGE_DIR/bin\" \"$PLATFORM_PACKAGES_DIR\"\n\ndeclare -A PLATFORM_MAP=(\n    [\"go-blueprint_${VERSION}_Darwin_all\"]=\"darwin-x64,darwin-arm64\"\n    [\"go-blueprint_${VERSION}_Linux_x86_64\"]=\"linux-x64\"\n    [\"go-blueprint_${VERSION}_Linux_arm64\"]=\"linux-arm64\"\n    [\"go-blueprint_${VERSION}_Windows_x86_64\"]=\"win32-x64\"\n    [\"go-blueprint_${VERSION}_Windows_arm64\"]=\"win32-arm64\"\n)\n\ndeclare -A OS_MAP=(\n    [\"darwin-x64\"]=\"darwin\"\n    [\"darwin-arm64\"]=\"darwin\"\n    [\"linux-x64\"]=\"linux\"\n    [\"linux-arm64\"]=\"linux\"\n    [\"win32-x64\"]=\"win32\"\n    [\"win32-arm64\"]=\"win32\"\n)\n\ndeclare -A CPU_MAP=(\n    [\"darwin-x64\"]=\"x64\"\n    [\"darwin-arm64\"]=\"arm64\"\n    [\"linux-x64\"]=\"x64\"\n    [\"linux-arm64\"]=\"arm64\"\n    [\"win32-x64\"]=\"x64\"\n    [\"win32-arm64\"]=\"arm64\"\n)\n\nOPTIONAL_DEPS=\"\"\nfor archive in dist/*.tar.gz dist/*.zip; do\n    if [ -f \"$archive\" ]; then\n        archive_name=$(basename \"$archive\")\n        archive_name=\"${archive_name%.tar.gz}\"\n        archive_name=\"${archive_name%.zip}\"\n        \n        platform_keys=\"${PLATFORM_MAP[$archive_name]:-}\"\n        \n        if [ -n \"$platform_keys\" ]; then\n            echo \"Processing $archive for platforms: $platform_keys\"\n            \n            IFS=',' read -ra PLATFORM_ARRAY <<< \"$platform_keys\"\n            for platform_key in \"${PLATFORM_ARRAY[@]}\"; do\n                platform_key=$(echo \"$platform_key\" | xargs)\n                \n                echo \"  Creating package for platform: $platform_key\"\n                \n                platform_package_dir=\"$PLATFORM_PACKAGES_DIR/go-blueprint-$platform_key\"\n                mkdir -p \"$platform_package_dir/bin\"\n                \n                if [[ \"$archive\" == *.tar.gz ]]; then\n                    tar -xzf \"$archive\" -C \"$platform_package_dir/bin\"\n                else\n                    unzip -j \"$archive\" -d \"$platform_package_dir/bin\"\n                fi\n                \n                for doc_file in README.md README README.txt LICENSE LICENSE.md LICENSE.txt; do\n                    if [ -f \"$platform_package_dir/bin/$doc_file\" ]; then\n                        mv \"$platform_package_dir/bin/$doc_file\" \"$platform_package_dir/\"\n                    fi\n                done\n                \n                ls -l \"$platform_package_dir/bin\"\n                chmod +x \"$platform_package_dir/bin/\"*\n                \n                os_value=\"${OS_MAP[$platform_key]}\"\n                cpu_value=\"${CPU_MAP[$platform_key]}\"\n                \n                files_array='[\"bin/\"]'\n                for doc_file in README.md README README.txt LICENSE LICENSE.md LICENSE.txt; do\n                    if [ -f \"$platform_package_dir/$doc_file\" ]; then\n                        files_array=\"${files_array%]}, \\\"$doc_file\\\"]\"\n                    fi\n                done\n                \n                binary_name=\"go-blueprint\"\n                if [[ \"$os_value\" == \"win32\" ]]; then\n                    binary_name=\"go-blueprint.exe\"\n                fi\n                \n                cat > \"$platform_package_dir/package.json\" << EOF\n{\n  \"name\": \"$PACKAGE_NAME-$platform_key\",\n  \"version\": \"$VERSION\",\n  \"description\": \"Platform-specific binary for $PACKAGE_NAME ($platform_key)\",\n  \"os\": [\"$os_value\"],\n  \"cpu\": [\"$cpu_value\"],\n  \"bin\": {\n    \"go-blueprint\": \"bin/$binary_name\"\n  },\n  \"files\": $files_array,\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Melkeydev/go-blueprint.git\"\n  },\n  \"author\": \"Melkeydev\",\n  \"license\": \"MIT\"\n}\nEOF\n                \n                if [ -n \"$OPTIONAL_DEPS\" ]; then\n                    OPTIONAL_DEPS=\"$OPTIONAL_DEPS,\"\n                fi\n                OPTIONAL_DEPS=\"$OPTIONAL_DEPS\\\"$PACKAGE_NAME-$platform_key\\\": \\\"$VERSION\\\"\"\n            done\n        fi\n    fi\ndone\n\ncat > \"$MAIN_PACKAGE_DIR/bin/go-blueprint\" << 'EOF'\n#!/usr/bin/env node\n\nconst { execFileSync } = require('child_process')\n\nconst packageName = '@melkeydev/go-blueprint'\n\nconst platformPackages = {\n  'darwin-x64': `${packageName}-darwin-x64`,\n  'darwin-arm64': `${packageName}-darwin-arm64`,\n  'linux-x64': `${packageName}-linux-x64`,\n  'linux-arm64': `${packageName}-linux-arm64`,\n  'win32-x64': `${packageName}-win32-x64`,\n  'win32-arm64': `${packageName}-win32-arm64`\n}\n\nfunction getBinaryPath() {\n  const platformKey = `${process.platform}-${process.arch}`\n  const platformPackageName = platformPackages[platformKey]\n\n  if (!platformPackageName) {\n    console.error(`Platform ${platformKey} is not supported!`)\n    process.exit(1)\n  }\n\n  try {\n    const binaryName = process.platform === 'win32' ? 'go-blueprint.exe' : 'go-blueprint'\n    const packagePath = platformPackageName.replace('@', '').replace('/', '-')\n    return require.resolve(`${platformPackageName}/bin/${binaryName}`)\n  } catch (e) {\n    process.exit(1)\n  }\n}\n\ntry {\n  const binaryPath = getBinaryPath()\n  execFileSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' })\n} catch (error) {\n  console.error('Failed to execute go-blueprint:', error.message)\n  process.exit(1)\n}\nEOF\n\nchmod +x \"$MAIN_PACKAGE_DIR/bin/go-blueprint\"\n\ncat > \"$MAIN_PACKAGE_DIR/package.json\" << EOF\n{\n  \"name\": \"$PACKAGE_NAME\",\n  \"version\": \"$VERSION\",\n  \"description\": \"A CLI for scaffolding Go projects with modern tooling\",\n  \"main\": \"index.js\",\n  \"bin\": {\n    \"go-blueprint\": \"bin/go-blueprint\"\n  },\n  \"optionalDependencies\": {\n    $OPTIONAL_DEPS\n  },\n  \"keywords\": [\"go\", \"golang\", \"cli\"],\n  \"author\": \"Melkeydev\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Melkeydev/go-blueprint.git\"\n  },\n  \"homepage\": \"https://github.com/Melkeydev/go-blueprint\",\n  \"engines\": {\n    \"node\": \">=14.0.0\"\n  },\n  \"files\": [\n    \"bin/\",\n    \"index.js\",\n    \"README.md\"\n  ]\n}\nEOF\n\ncat > \"$MAIN_PACKAGE_DIR/index.js\" << 'EOF'\nconst { execFileSync } = require('child_process')\nconst path = require('path')\n\nconst binaryName = process.platform === 'win32' ? 'go-blueprint.exe' : 'go-blueprint'\n\nconst packageName = '@melkeydev/go-blueprint'\n\nconst platformPackages = {\n  'darwin-x64': `${packageName}-darwin-x64`,\n  'darwin-arm64': `${packageName}-darwin-arm64`,\n  'linux-x64': `${packageName}-linux-x64`,\n  'linux-arm64': `${packageName}-linux-arm64`,\n  'win32-x64': `${packageName}-win32-x64`,\n  'win32-arm64': `${packageName}-win32-arm64`\n}\n\nfunction getBinaryPath() {\n  const platformKey = `${process.platform}-${process.arch}`\n  const platformPackageName = platformPackages[platformKey]\n\n  if (!platformPackageName) {\n    throw new Error(`Platform ${platformKey} is not supported!`)\n  }\n\n  try {\n    return require.resolve(`${platformPackageName}/bin/${binaryName}`)\n  } catch (e) {\n    throw new Error(`Platform-specific package ${platformPackageName} not found.`)\n  }\n}\n\nmodule.exports = {\n  getBinaryPath,\n  run: function(...args) {\n    const binaryPath = getBinaryPath()\n    return execFileSync(binaryPath, args, { stdio: 'inherit' })\n  }\n}\nEOF\n\nfirst_platform_dir=$(ls -1d \"$PLATFORM_PACKAGES_DIR\"/* | head -1 2>/dev/null || echo \"\")\nif [ -n \"$first_platform_dir\" ] && [ -f \"$first_platform_dir/README.md\" ]; then\n    cp \"$first_platform_dir/README.md\" \"$MAIN_PACKAGE_DIR/\"\nfi"
  }
]