trim fat, use Gi philosophy

This commit is contained in:
djmil 2026-03-29 19:24:55 +00:00
parent f4ce4599aa
commit 0f75b279c3
19 changed files with 320 additions and 469 deletions

View File

@ -1,37 +1,33 @@
#!/usr/bin/env bash
# pre-commit hook: runs on every `git commit`
# pre-push hook: runs quality checks before every `git push`.
# Install with: make setup (sets core.hooksPath = .githooks)
set -euo pipefail
# Collect staged Go files only — avoids re-checking untouched code.
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.go$' || true)
if [ -z "$STAGED" ]; then
echo "pre-commit: no Go files staged — skipping."
exit 0
fi
echo "pre-commit: running checks on staged Go files..."
echo "pre-push: running checks..."
# ── 1. gofmt ──────────────────────────────────────────────────────────────────
echo " → gofmt"
UNFORMATTED=$(gofmt -l $STAGED)
UNFORMATTED=$(gofmt -l $(git ls-files '*.go'))
if [ -n "$UNFORMATTED" ]; then
echo " FAIL: the following files are not gofmt-formatted:"
echo "$UNFORMATTED" | sed 's/^/ /'
echo " Fix with: gofmt -w <file> or make lint-fix"
echo " Fix with: make lint-fix"
exit 1
fi
# ── 2. golangci-lint ──────────────────────────────────────────────────────────
# ── 2. go vet ─────────────────────────────────────────────────────────────────
echo " → go vet"
go vet ./...
# ── 3. golangci-lint ──────────────────────────────────────────────────────────
echo " → golangci-lint"
golangci-lint run ./...
# ── 3. gosec (security scan) ──────────────────────────────────────────────────
# ── 4. gosec (security scan) ──────────────────────────────────────────────────
echo " → gosec"
gosec -quiet ./...
# govulncheck is intentionally omitted from pre-commit (network + slow).
# govulncheck is intentionally omitted (network + slow).
# Run it manually with: make security
echo "pre-commit: all checks passed."
echo "pre-push: all checks passed."

View File

@ -4,8 +4,7 @@
run:
timeout: 5m
# Align with go.mod
# go: '1.24'
go: '1.25'
# Enable specific linters on top of the default set.
# Default set: errcheck, gosimple, govet, ineffassign, staticcheck, unused
@ -39,7 +38,7 @@ linters-settings:
goimports:
# Put local module imports in their own group (after stdlib and external).
local-prefixes: github.com/your-org/go-template
local-prefixes: gitea.djmil.dev/djmil/go-template
gocritic:
disabled-checks:
@ -65,13 +64,5 @@ issues:
- unparam
- gocritic
- gosec
# Don't fail on generated files.
- path: mock_.*\.go
linters:
- all
- path: mocks/
linters:
- all
max-issues-per-linter: 50
max-same-issues: 5

View File

@ -1,18 +0,0 @@
# mockery v2 configuration
# Docs: https://vektra.github.io/mockery/latest/configuration/
#
# Mocks are placed in mocks/<package>/ next to their source package.
# Regenerate with: make mocks
with-expecter: true # generate type-safe EXPECT() call chains
mockname: "Mock{{.InterfaceName}}"
filename: "mock_{{.InterfaceName | snakecase}}.go"
outpkg: "mocks"
dir: "mocks/{{.PackageName}}"
# Packages whose interfaces should be mocked.
# Add entries here whenever you define an interface that other packages depend on.
packages:
gitea.djmil.dev/djmil/go-template/internal/greeter:
interfaces:
Greeter:

9
.vscode/launch.json vendored
View File

@ -8,10 +8,7 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/app",
"env": {
"CONFIG_PATH": "${workspaceFolder}/config/dev.yaml"
},
"args": []
"args": ["-env", "dev", "-log-level", "debug"]
},
{
// Debug: same as above but with delve attached
@ -20,9 +17,7 @@
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/cmd/app",
"env": {
"CONFIG_PATH": "${workspaceFolder}/config/dev.yaml"
},
"args": ["-env", "dev", "-log-level", "debug"],
"showLog": true,
"trace": "verbose"
},

View File

@ -33,5 +33,9 @@
// Test explorer
"go.testExplorer.enable": true,
"makefile.configureOnOpen": false
"makefile.configureOnOpen": false,
"cSpell.words": [
"djmil",
"gitea"
]
}

View File

@ -7,8 +7,8 @@ Keep it concise — the agent needs signal, not essays.
## Project overview
Go 1.24 template / PoC starter. Demonstrates: structured logging (zap),
config (viper), interfaces + mocks (mockery), linting (golangci-lint),
Go 1.25 template / PoC starter. Demonstrates: structured logging (slog),
config (flag), interfaces + manual fakes, linting (golangci-lint),
security scanning (gosec, govulncheck), git hooks, devcontainer, VSCode tasks.
Module: `gitea.djmil.dev/djmil/go-template` — update this when you fork.
@ -19,15 +19,12 @@ Module: `gitea.djmil.dev/djmil/go-template` — update this when you fork.
```
cmd/app/main.go composition root — wires deps, no logic here
internal/config/ Viper config loader (config.Load)
internal/logger/ Zap wrapper with WithField / WithFields
internal/config/ flag-based config loader (config.Load)
internal/logger/ slog wrapper with WithField / WithFields
internal/greeter/ Example domain package (delete or repurpose)
mocks/greeter/ Generated mocks — regenerate with `make mocks`
config/dev.yaml Local dev config (committed, no secrets)
tools.go Tool version pinning (build tag: tools)
.golangci.yml Linter rules
.mockery.yaml Mockery code-gen config
.githooks/pre-commit Runs gofmt + golangci-lint + gosec before commit
.githooks/pre-push Runs gofmt + go vet + golangci-lint + gosec before push
```
---
@ -38,9 +35,8 @@ tools.go Tool version pinning (build tag: tools)
- **Packages** — keep `cmd/` thin (wiring only); business logic belongs in `internal/`
- **Interfaces** — define interfaces where they are *used*, not where they are *implemented*
- **Errors** — wrap with `fmt.Errorf("context: %w", err)`; never swallow errors silently
- **Logging** — use `log.WithField("key", val)` for structured context, never `fmt.Sprintf` in log messages
- **Config** — all configuration through `internal/config`; no hard-coded values in logic packages
- **Secrets** — never commit `.env` files or credentials; use env var overrides of config keys
- **Logging** — use `log.WithField("key", val)` for structured context; never `fmt.Sprintf` in log messages; `log/slog` is the backend
- **Config** — all configuration through `internal/config` (flag-parsed); no hard-coded values in logic packages
---
@ -58,13 +54,13 @@ tools.go Tool version pinning (build tag: tools)
## Testing rules
- All tests use **testify** (`assert` for soft checks, `require` for stop-on-fail)
- Tests use only the standard `testing` package — no third-party assertion libraries
- Test files: `package foo_test` (black-box) unless white-box access is needed
- Mock dependencies via **mockery**-generated mocks with `EXPECT()` chains
- Fake dependencies with **manual fakes** (implement the interface inline in `_test.go`)
- Use `logger.NewNop()` when the test doesn't care about log output
- Table-driven tests with `t.Run("description", ...)` for multiple cases
- The race detector is enabled in CI (`make test-race`); don't introduce data races
- Never use `time.Sleep` in tests; use channels or `require.Eventually`
- Never use `time.Sleep` in tests; use channels or `t.Cleanup`
---
@ -73,15 +69,13 @@ tools.go Tool version pinning (build tag: tools)
```bash
make init # first-time setup: fetch deps, install tools, git hooks
make build # compile to ./bin/app
make run # go run with config/dev.yaml
make run # go run with default flags
make test # run all tests
make test-race # tests + race detector
make lint # golangci-lint
make lint-fix # auto-fix lint issues
make lint # go vet + golangci-lint
make lint-fix # go fix + golangci-lint auto-fix
make security # gosec + govulncheck
make mocks # regenerate all mocks
make generate # run all //go:generate directives
make clean # remove bin/ and mocks/
make clean # remove bin/
```
VSCode: `Ctrl+Shift+B` = build, `Ctrl+Shift+T` = test.
@ -92,19 +86,17 @@ Debug: use launch config "Debug: app" (F5).
## Adding new features (checklist)
1. Define the interface in `internal/<domain>/`
2. Write the implementation and its unit tests
3. Add the interface to `.mockery.yaml` and run `make mocks`
4. Wire it in `cmd/app/main.go`
5. Run `make lint test` before committing
2. Write the implementation and its unit tests (using a manual fake for the interface)
3. Wire it in `cmd/app/main.go`
4. Run `make lint test` before committing
---
## Known pitfalls
- `mocks/` are committed intentionally — regenerate after interface changes
- `govulncheck` makes network calls; excluded from pre-commit hook (run manually)
- `config/dev.yaml` is committed but never add real credentials here
- `govulncheck` makes network calls; excluded from pre-push hook (run manually)
- `tools.go` has `//go:build tools` — it won't compile into the binary
- `go fix` rewrites source files; run `make lint-fix` before committing after a Go version bump
---
@ -113,5 +105,6 @@ Debug: use launch config "Debug: app" (F5).
Keep this section to ~10 entries; remove stale items.
Format: YYYY-MM-DD — what was done and why. -->
- 2026-03-05 — Initial template scaffolded: config, logger, greeter example,
mocks, git hooks, devcontainer, VSCode tasks, golangci-lint, Makefile.
- 2026-03-29 — Stripped to stdlib-only: removed testify/mockery→manual fakes, zap→slog, viper→flag.
- 2026-03-29 — Pre-commit hook moved to pre-push; go vet + go fix added to lint pipeline.
- 2026-03-29 — Fixed stale docs and .golangci.yml local-prefixes; .vscode launch configs use CLI flags.

View File

@ -1,11 +1,9 @@
.PHONY: help init setup build run test test-race lint security generate mocks clean
.PHONY: help init setup build run test test-race lint lint-fix security clean
# ── Variables ──────────────────────────────────────────────────────────────────
BINARY_NAME := app
BINARY_PATH := ./bin/$(BINARY_NAME)
CMD_PATH := ./cmd/app
CONFIG_PATH := ./config/dev.yaml
GO_FILES := $(shell find . -type f -name '*.go' -not -path './vendor/*')
# ── Default target ─────────────────────────────────────────────────────────────
help: ## Show this help message
@ -14,13 +12,7 @@ help: ## Show this help message
# ── First-time setup ───────────────────────────────────────────────────────────
init: ## First-time project init: fetch deps, install tools, configure git hooks
@echo "--- Fetching application dependencies ---"
go get go.uber.org/zap@latest
go get github.com/spf13/viper@latest
go get github.com/stretchr/testify@latest
go get github.com/stretchr/objx@latest
@echo "--- Fetching tool dependencies (pinned in tools.go) ---"
go get github.com/vektra/mockery/v2@latest
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go get github.com/securego/gosec/v2/cmd/gosec@latest
go get golang.org/x/vuln/cmd/govulncheck@latest
@ -40,7 +32,6 @@ setup: ## Configure git to use .githooks directory
tools: ## Install tool binaries to GOPATH/bin (versions from go.mod)
@echo "Installing tools..."
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/vektra/mockery/v2@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/securego/gosec/v2/cmd/gosec@latest
go install golang.org/x/vuln/cmd/govulncheck@latest
@ -50,8 +41,8 @@ build: ## Compile the binary to ./bin/
go build -o $(BINARY_PATH) $(CMD_PATH)
# ── Run ────────────────────────────────────────────────────────────────────────
run: ## Run the application with dev config
CONFIG_PATH=$(CONFIG_PATH) go run $(CMD_PATH)/main.go
run: ## Run the application with default flags
go run $(CMD_PATH)/main.go
# ── Test ───────────────────────────────────────────────────────────────────────
test: ## Run all tests
@ -64,10 +55,12 @@ test-verbose: ## Run all tests with verbose output
go test ./... -race -cover -v
# ── Lint & Security ────────────────────────────────────────────────────────────
lint: ## Run golangci-lint
lint: ## Run go vet + golangci-lint
go vet ./...
golangci-lint run ./...
lint-fix: ## Run golangci-lint with auto-fix
lint-fix: ## Apply go fix + golangci-lint auto-fix
go fix ./...
golangci-lint run --fix ./...
security: ## Run gosec + govulncheck
@ -76,14 +69,6 @@ security: ## Run gosec + govulncheck
@echo "--- govulncheck ---"
govulncheck ./...
# ── Code generation ────────────────────────────────────────────────────────────
generate: ## Run go generate across all packages
go generate ./...
mocks: ## Regenerate all mocks via mockery (config: .mockery.yaml)
mockery
# ── Cleanup ────────────────────────────────────────────────────────────────────
clean: ## Remove build artifacts and generated mocks
clean: ## Remove build artifacts
rm -rf ./bin
rm -rf ./mocks

109
README.md
View File

@ -9,14 +9,13 @@ Clone it, rename the module, run `make init`, and you're coding.
| Area | Tool | Purpose |
|---|---|---|
| Language | Go 1.24 | Modules, toolchain directive |
| Logging | [zap](https://github.com/uber-go/zap) | Structured JSON logging + `WithField` extension |
| Config | [viper](https://github.com/spf13/viper) | YAML file + env var overlay |
| Language | Go 1.25 | Modules, toolchain directive |
| Logging | standard `log/slog` | Structured JSON/text logging + `WithField` extension |
| Config | standard `flag` | CLI flags with defaults, no config files |
| Linting | [golangci-lint](https://golangci-lint.run) | Aggregated linters, one config file |
| Security | [gosec](https://github.com/securego/gosec) + [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) | SAST + dependency vulnerability scan |
| Mocks | [mockery v2](https://vektra.github.io/mockery/latest/) | Type-safe, EXPECT()-style generated mocks |
| Tests | [testify](https://github.com/stretchr/testify) | Assertions (`assert`/`require`) + mock framework |
| Git hooks | custom pre-commit | gofmt + golangci-lint + gosec on every commit |
| Tests | standard `testing` | Table-driven tests, manual fakes, no third-party test framework |
| Git hooks | custom pre-push | gofmt + go vet + golangci-lint + gosec on every push |
| Tool versions | `tools.go` + `go.mod` | Single source of truth for tool versions |
| Dev environment | devcontainer | Reproducible VSCode / GitHub Codespaces setup |
| IDE | VSCode | Launch configs, tasks, recommended settings |
@ -27,7 +26,7 @@ Clone it, rename the module, run `make init`, and you're coding.
### Prerequisites
- Go 1.24+
- Go 1.25+
- Git
- (Optional) [VSCode](https://code.visualstudio.com/) + [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go)
- (Optional) Docker for devcontainer
@ -52,7 +51,7 @@ make init # fetches deps, installs tools, configures git hooks
```bash
make build # compiles to ./bin/app
make run # go run with config/dev.yaml
make run # go run with default flags
```
---
@ -62,11 +61,9 @@ make run # go run with config/dev.yaml
```bash
make test # run all tests
make test-race # … with race detector
make lint # golangci-lint
make lint-fix # auto-fix what can be fixed
make lint # go vet + golangci-lint
make lint-fix # go fix + golangci-lint auto-fix
make security # gosec + govulncheck
make mocks # regenerate mocks after interface changes
make generate # run all //go:generate directives
```
> **Keyboard shortcut (VSCode):** `Ctrl+Shift+B` → build, `Ctrl+Shift+T` → test.
@ -80,20 +77,15 @@ make generate # run all //go:generate directives
├── cmd/
│ └── app/
│ └── main.go # Composition root (thin — just wiring)
├── config/
│ └── dev.yaml # Local dev config (safe to commit)
├── internal/
│ ├── config/ # Viper config loader
│ ├── logger/ # Zap wrapper with WithField
│ ├── config/ # flag-based config (config.Load)
│ ├── logger/ # slog wrapper with WithField / WithFields
│ └── greeter/ # Example domain package (replace with yours)
├── mocks/
│ └── greeter/ # Generated by `make mocks` — commit these
├── .devcontainer/ # VSCode / Codespaces container definition
├── .githooks/ # pre-commit hook (installed by `make setup`)
├── .githooks/ # pre-push hook (installed by `make setup`)
├── .vscode/ # launch, tasks, editor settings
├── tools.go # Tool version pinning (build tag: tools)
├── .golangci.yml # Linter configuration
├── .mockery.yaml # Mockery configuration
└── Makefile # All development commands
```
@ -101,35 +93,28 @@ make generate # run all //go:generate directives
## Configuration
Config is loaded from a YAML file (default `config/dev.yaml`) with env var overrides.
```yaml
app:
name: "go-template"
port: 8080
env: "dev" # dev | staging | prod
logger:
level: "debug" # debug | info | warn | error
```
Override any key at runtime:
All configuration is via CLI flags with sensible defaults:
```bash
APP_PORT=9090 LOGGER_LEVEL=info make run
./bin/app -help
-env string environment: dev | staging | prod (default "dev")
-log-level string log level: debug | info | warn | error (default "info")
-name string application name (default "go-template")
-port int listen port (default 8080)
```
The `CONFIG_PATH` env var points to the config file:
Override at runtime:
```bash
CONFIG_PATH=config/staging.yaml ./bin/app
./bin/app -port 9090 -env prod -log-level warn
```
---
## Logging
The logger wrapper adds `WithField` and `WithFields` for ergonomic context chaining:
The logger wrapper adds `WithField` and `WithFields` for ergonomic context chaining
on top of the standard `log/slog` package:
```go
log.WithField("request_id", rid).
@ -142,29 +127,32 @@ log.WithFields(map[string]any{
}).Debug("generating greeting")
```
In production (`app.env != dev`) the output is JSON. In development, use
`logger.NewDevelopment()` for coloured console output.
In production (`app.env != dev`) the output is JSON (`slog.NewJSONHandler`).
In development, `logger.NewDevelopment()` uses the human-friendly text handler.
---
## Mocking
## Testing
1. Define your interface in an `internal/` package.
2. Add a `//go:generate mockery --name=YourInterface` directive.
3. Register the interface in `.mockery.yaml` under `packages:`.
4. Run `make mocks`.
Generated mocks support type-safe EXPECT() call chains:
Tests use the standard `testing` package. For dependencies on interfaces, write
a manual fake inline in the test file — no code generation required:
```go
import mocks "gitea.djmil.dev/djmil/go-template/mocks/greeter"
type fakeGreeter struct {
greetFn func(name string) (string, error)
}
func (f *fakeGreeter) Greet(name string) (string, error) {
return f.greetFn(name)
}
func TestSomething(t *testing.T) {
g := mocks.NewMockGreeter(t)
g.EXPECT().Greet("Alice").Return("Hello, Alice!", nil)
result, err := myConsumer(g)
require.NoError(t, err)
fake := &fakeGreeter{
greetFn: func(name string) (string, error) {
return "Hello, " + name + "!", nil
},
}
// pass fake to the system under test
}
```
@ -172,16 +160,17 @@ func TestSomething(t *testing.T) {
## Git hooks
The pre-commit hook runs **gofmt + golangci-lint + gosec** before every commit.
It checks only staged Go files to keep it fast.
The pre-push hook runs **gofmt + go vet + golangci-lint + gosec** against the
full codebase before every `git push`. Running on push (not commit) keeps the
inner commit loop fast while still blocking bad code from reaching the remote.
`govulncheck` is intentionally excluded from the hook (it makes network calls
and can be slow). Run it manually with `make security`.
`govulncheck` is intentionally excluded (it makes network calls and can be
slow). Run it manually with `make security`.
To skip the hook in an emergency:
```bash
git commit --no-verify -m "wip: skip hooks"
git push --no-verify
```
---
@ -195,7 +184,7 @@ same versions.
To update a tool:
```bash
go get github.com/vektra/mockery/v2@latest
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go mod tidy
make tools # reinstall binaries
```
@ -204,8 +193,8 @@ make tools # reinstall binaries
## Devcontainer
Open this repo in VSCode and choose **"Reopen in Container"** — everything
(Go toolchain, golangci-lint, mockery, gosec, govulncheck) is installed
Open this repo in VSCode and choose **"Reopen in Container"** — the Go
toolchain and all tools (golangci-lint, gosec, govulncheck) are installed
automatically via `make init`.
Works with GitHub Codespaces out of the box.

View File

@ -21,27 +21,21 @@ func main() {
func run() error {
// ── Config ────────────────────────────────────────────────────────────────
cfgPath := os.Getenv("CONFIG_PATH")
if cfgPath == "" {
cfgPath = "config/dev.yaml"
}
cfg, err := config.Load(cfgPath)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
cfg := config.Load()
// ── Logger ────────────────────────────────────────────────────────────────
var log *logger.Logger
var (
log *logger.Logger
err error
)
if cfg.App.Env == "dev" {
log, err = logger.NewDevelopment()
log = logger.NewDevelopment()
} else {
log, err = logger.New(cfg.Logger.Level)
if err != nil {
return fmt.Errorf("creating logger: %w", err)
}
}
if err != nil {
return fmt.Errorf("creating logger: %w", err)
}
defer log.Sync()
log.WithFields(map[string]any{
"app": cfg.App.Name,

View File

@ -1,12 +0,0 @@
# Development configuration.
# Values here are safe for local use only — never commit production secrets.
# Override any key with an environment variable: APP_PORT=9090, LOGGER_LEVEL=debug
app:
name: "go-template"
port: 8080
env: "dev"
logger:
# debug | info | warn | error
level: "debug"

31
go.mod
View File

@ -6,12 +6,9 @@ go 1.25.0
// After cloning, rename the module path above to match your project.
require (
github.com/go-delve/delve v1.26.1
github.com/golangci/golangci-lint v1.64.8
github.com/securego/gosec/v2 v2.24.7
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/vektra/mockery/v2 v2.53.6
go.uber.org/zap v1.27.1
golang.org/x/vuln v1.1.4
)
@ -53,12 +50,15 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/chigopher/pathlib v0.19.1 // indirect
github.com/cilium/ebpf v0.11.0 // indirect
github.com/ckaznocha/intrange v0.3.0 // indirect
github.com/cosiner/argv v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/derekparker/trie/v3 v3.2.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
@ -68,6 +68,7 @@ require (
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.9 // indirect
github.com/go-critic/go-critic v0.12.0 // indirect
github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
@ -89,6 +90,7 @@ require (
github.com/golangci/revgrep v0.8.0 // indirect
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-dap v0.12.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@ -103,13 +105,11 @@ require (
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jgautheron/goconst v1.7.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/jjti/go-spancheck v0.6.4 // indirect
github.com/julz/importas v0.2.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
@ -125,6 +125,7 @@ require (
github.com/ldez/usetesting v0.4.2 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v1.1.0 // indirect
@ -144,6 +145,7 @@ require (
github.com/olekukonko/ll v0.0.8 // indirect
github.com/olekukonko/tablewriter v1.0.7 // indirect
github.com/openai/openai-go/v3 v3.23.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.7.1 // indirect
@ -159,10 +161,9 @@ require (
github.com/raeperd/recvcheck v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.5 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
@ -175,10 +176,13 @@ require (
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tdakkota/asciicheck v0.4.1 // indirect
github.com/tetafro/godot v1.5.0 // indirect
@ -209,9 +213,14 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.starlark.net v0.0.0-20231101134539-556fd59b42f6 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.11.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
@ -220,7 +229,6 @@ require (
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
@ -229,6 +237,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect

69
go.sum
View File

@ -50,6 +50,8 @@ github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8ger
github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU=
github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
@ -76,12 +78,16 @@ github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iy
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
github.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A=
github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY=
github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4=
github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c=
@ -92,6 +98,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
github.com/derekparker/trie/v3 v3.2.0 h1:fET3Qbp9xSB7yc7tz6Y2GKMNl0SycYFo3cmiRI3Gpf0=
github.com/derekparker/trie/v3 v3.2.0/go.mod h1:P94lW0LPgiaMgKAEQD59IDZD2jMK9paKok8Nli/nQbE=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
@ -116,6 +124,10 @@ github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYF
github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w=
github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w=
github.com/go-delve/delve v1.26.1 h1:V1F0hzAjXCpsBP+I/E6fVUTLC/ZBSs1YWUb8cTtIWFE=
github.com/go-delve/delve v1.26.1/go.mod h1:Ua/k2AAu4cLrUXGSRVH1b2Nzq2aCK188b9EYlAojlz4=
github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 h1:IGtvsNyIuRjl04XAOFGACozgUD7A82UffYxZt4DWbvA=
github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -150,7 +162,6 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@ -180,6 +191,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-dap v0.12.0 h1:rVcjv3SyMIrpaOoTAdFDyHs99CwVOItIJGKLQFQhNeM=
github.com/google/go-dap v0.12.0/go.mod h1:tNjCASCm5cqePi/RVXXWEVqtnNLV1KTWtYOqu6rZNzc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
@ -222,20 +235,16 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk=
github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc=
github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk=
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
@ -270,6 +279,8 @@ github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84Yrj
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk=
github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
@ -278,13 +289,11 @@ github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY=
@ -324,8 +333,11 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
@ -359,16 +371,12 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU=
github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
@ -397,12 +405,14 @@ github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4=
@ -412,6 +422,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -457,8 +468,6 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
github.com/vektra/mockery/v2 v2.53.6 h1:qfUB6saauu652ZlMF/mEdlj7B/A0fw2XR0XBACBrf7Y=
github.com/vektra/mockery/v2 v2.53.6/go.mod h1:fjxC+mskIZqf67+z34pHxRRyyZnPnWNA36Cirf01Pkg=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
@ -497,16 +506,22 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.starlark.net v0.0.0-20231101134539-556fd59b42f6 h1:+eC0F/k4aBLC4szgOcjd7bDTEnpxADJyWJE0yowgM3E=
go.starlark.net v0.0.0-20231101134539-556fd59b42f6/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -574,10 +589,10 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -596,8 +611,6 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -653,6 +666,8 @@ google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -1,61 +1,54 @@
// Package config loads application configuration from a YAML file + env vars.
// Priority (highest → lowest): env vars → config file → defaults.
// Package config parses application configuration from command-line flags.
// Defaults are defined here; override at runtime with flags:
//
// ./app -port 9090 -env prod -log-level warn
//
// Usage:
//
// cfg, err := config.Load("config/dev.yaml")
// cfg := config.Load()
// fmt.Println(cfg.App.Port)
package config
import (
"fmt"
"github.com/spf13/viper"
"flag"
)
// Config is the root configuration object. Add sub-structs as the app grows.
type Config struct {
App AppConfig `mapstructure:"app"`
Logger LoggerConfig `mapstructure:"logger"`
App AppConfig
Logger LoggerConfig
}
// AppConfig holds generic application settings.
type AppConfig struct {
Name string `mapstructure:"name"`
Port int `mapstructure:"port"`
Env string `mapstructure:"env"` // dev | staging | prod
Name string
Port int
Env string // dev | staging | prod
}
// LoggerConfig controls logging behavior.
type LoggerConfig struct {
Level string `mapstructure:"level"` // debug | info | warn | error
Level string // debug | info | warn | error
}
// Load reads the YAML file at path and overlays any matching environment
// variables (e.g. APP_PORT overrides app.port).
func Load(path string) (*Config, error) {
v := viper.New()
// Load parses command-line flags and returns a Config.
// Call this once at startup before any other flag parsing.
func Load() *Config {
name := flag.String("name", "go-template", "application name")
port := flag.Int("port", 8080, "listen port")
env := flag.String("env", "dev", "environment: dev | staging | prod")
level := flag.String("log-level", "info", "log level: debug | info | warn | error")
v.SetConfigFile(path)
flag.Parse()
// Allow every config key to be overridden by an env var.
// e.g. APP_PORT=9090 overrides app.port
v.AutomaticEnv()
// Sensible defaults so the binary works without a config file.
v.SetDefault("app.name", "go-template")
v.SetDefault("app.port", 8080)
v.SetDefault("app.env", "dev")
v.SetDefault("logger.level", "info")
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("config: reading %q: %w", path, err)
return &Config{
App: AppConfig{
Name: *name,
Port: *port,
Env: *env,
},
Logger: LoggerConfig{
Level: *level,
},
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("config: unmarshalling: %w", err)
}
return &cfg, nil
}

View File

@ -1,6 +1,6 @@
// Package greeter is a minimal example domain package.
// It demonstrates how to:
// - define an interface (mockable via mockery)
// - define an interface (satisfied by manual fakes in tests)
// - inject dependencies (logger) through a constructor
// - use the logger.WithField pattern
//
@ -13,8 +13,6 @@ import (
"gitea.djmil.dev/djmil/go-template/internal/logger"
)
//go:generate mockery --name=Greeter
// Greeter produces a greeting for a given name.
// The interface is what other packages should depend on — never the concrete type.
type Greeter interface {

View File

@ -1,11 +1,9 @@
package greeter_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitea.djmil.dev/djmil/go-template/internal/greeter"
"gitea.djmil.dev/djmil/go-template/internal/logger"
)
@ -15,37 +13,51 @@ import (
func TestGreet(t *testing.T) {
svc := greeter.New(logger.NewNop())
t.Run("returns personalised greeting", func(t *testing.T) {
t.Run("returns personalized greeting", func(t *testing.T) {
msg, err := svc.Greet("World")
require.NoError(t, err)
assert.Equal(t, "Hello, World!", msg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if msg != "Hello, World!" {
t.Errorf("got %q, want %q", msg, "Hello, World!")
}
})
t.Run("rejects empty name", func(t *testing.T) {
_, err := svc.Greet("")
require.Error(t, err)
assert.Contains(t, err.Error(), "name must not be empty")
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "name must not be empty") {
t.Errorf("error %q does not contain %q", err.Error(), "name must not be empty")
}
})
}
// ── Mock usage example ────────────────────────────────────────────────────────
// The mock lives in mocks/greeter/mock_greeter.go and is generated by:
//
// make mocks
//
// This test shows how a *consumer* of the Greeter interface would use the mock.
// It lives here only for proximity to the interface definition — in a real
// project, consumer packages write their own tests with the mock.
// ── Manual fake example ───────────────────────────────────────────────────────
// For consumers that depend on the Greeter interface, write a manual fake.
// No code generation required — just implement the interface directly.
func TestMockUsageExample(t *testing.T) {
// Import path when used in another package:
// mocks "gitea.djmil.dev/djmil/go-template/mocks/greeter"
//
// mock := mocks.NewMockGreeter(t)
// mock.EXPECT().Greet("Alice").Return("Hello, Alice!", nil)
//
// result, err := someConsumer(mock)
// require.NoError(t, err)
// assert.Equal(t, "Hello, Alice!", result)
t.Skip("mock usage — see comment above for pattern")
type fakeGreeter struct {
greetFn func(name string) (string, error)
}
func (f *fakeGreeter) Greet(name string) (string, error) {
return f.greetFn(name)
}
func TestFakeUsageExample(t *testing.T) {
fake := &fakeGreeter{
greetFn: func(name string) (string, error) {
return "Hello, " + name + "!", nil
},
}
msg, err := fake.Greet("Alice")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if msg != "Hello, Alice!" {
t.Errorf("got %q, want %q", msg, "Hello, Alice!")
}
}

View File

@ -1,8 +1,8 @@
// Package logger wraps go.uber.org/zap with a thin, ergonomic API.
// Package logger wraps log/slog with a thin, ergonomic API.
//
// The key addition over raw zap is the WithField / WithFields helpers that
// return a *Logger (not a *zap.Logger), so callers stay in the typed world and
// don't need to import zap just to attach context fields.
// The key addition over raw slog is the WithField / WithFields helpers that
// return a *Logger (not a *slog.Logger), so callers stay in the typed world
// and can chain field attachments without importing slog directly.
//
// Usage:
//
@ -15,18 +15,18 @@ package logger
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"log/slog"
"os"
)
// Logger is a thin wrapper around *zap.Logger.
// All zap methods (Info, Error, Debug, …) are available via embedding.
// Logger is a thin wrapper around *slog.Logger.
// All slog methods (Info, Error, Debug, Warn, …) are available via embedding.
type Logger struct {
*zap.Logger
*slog.Logger
}
// New creates a production-style JSON logger for the given level string.
// New creates a JSON logger writing to stderr for the given level string.
// Valid levels: debug, info, warn, error.
func New(level string) (*Logger, error) {
lvl, err := parseLevel(level)
@ -34,65 +34,45 @@ func New(level string) (*Logger, error) {
return nil, err
}
cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(lvl)
// ISO8601 timestamps are human-readable and grep-friendly.
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: lvl})
z, err := cfg.Build(zap.AddCallerSkip(0))
if err != nil {
return nil, fmt.Errorf("logger: build: %w", err)
}
return &Logger{z}, nil
return &Logger{slog.New(h)}, nil
}
// NewDevelopment creates a colourised, human-friendly console logger.
// NewDevelopment creates a human-friendly text logger writing to stderr.
// Use this in local dev; prefer New() in any deployed environment.
func NewDevelopment() (*Logger, error) {
z, err := zap.NewDevelopment()
if err != nil {
return nil, fmt.Errorf("logger: build dev: %w", err)
}
func NewDevelopment() *Logger {
h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
return &Logger{z}, nil
return &Logger{slog.New(h)}
}
// NewNop returns a no-op logger. Useful in tests that don't care about logs.
func NewNop() *Logger {
return &Logger{zap.NewNop()}
return &Logger{slog.New(slog.NewTextHandler(io.Discard, nil))}
}
// WithField returns a child logger that always includes key=value in every log
// line. value can be any type; zap.Any is used internally.
// WithField returns a child logger that always includes key=value in every log line.
func (l *Logger) WithField(key string, value any) *Logger {
return &Logger{l.Logger.With(zap.Any(key, value))}
return &Logger{l.Logger.With(key, value)}
}
// WithFields returns a child logger enriched with every key/value in fields.
// Prefer WithField for one or two fields; use WithFields for structured context
// objects (e.g. attaching a request span).
func (l *Logger) WithFields(fields map[string]any) *Logger {
zapFields := make([]zap.Field, 0, len(fields))
args := make([]any, 0, len(fields)*2)
for k, v := range fields {
zapFields = append(zapFields, zap.Any(k, v))
args = append(args, k, v)
}
return &Logger{l.Logger.With(zapFields...)}
}
// Sync flushes any buffered log entries. Call this on shutdown:
//
// defer log.Sync()
func (l *Logger) Sync() {
// Intentionally ignore the error — os.Stderr sync often fails on some OSes.
_ = l.Logger.Sync()
return &Logger{l.Logger.With(args...)}
}
// ── helpers ───────────────────────────────────────────────────────────────────
func parseLevel(level string) (zapcore.Level, error) {
var lvl zapcore.Level
func parseLevel(level string) (slog.Level, error) {
var lvl slog.Level
if err := lvl.UnmarshalText([]byte(level)); err != nil {
return lvl, fmt.Errorf("logger: unknown level %q (use debug|info|warn|error)", level)
}

View File

@ -1,92 +0,0 @@
// Code generated by mockery v2.46.0. DO NOT EDIT.
// Regenerate with: make mocks
package mocks
import mock "github.com/stretchr/testify/mock"
// MockGreeter is an autogenerated mock type for the Greeter interface.
type MockGreeter struct {
mock.Mock
}
// MockGreeter_Expecter provides a type-safe wrapper around mock.Call.
type MockGreeter_Expecter struct {
mock *mock.Mock
}
// EXPECT returns a typed expecter for this mock.
func (_m *MockGreeter) EXPECT() *MockGreeter_Expecter {
return &MockGreeter_Expecter{mock: &_m.Mock}
}
// Greet provides a mock function for the Greeter.Greet method.
func (_m *MockGreeter) Greet(name string) (string, error) {
ret := _m.Called(name)
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(name)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGreeter_Greet_Call is a *mock.Call with a type-safe Run method.
type MockGreeter_Greet_Call struct {
*mock.Call
}
// Greet sets up an expectation on the Greet method.
//
// Example:
//
// mock.EXPECT().Greet("Alice").Return("Hello, Alice!", nil)
func (_e *MockGreeter_Expecter) Greet(name any) *MockGreeter_Greet_Call {
return &MockGreeter_Greet_Call{Call: _e.mock.On("Greet", name)}
}
// Run sets a side-effect function that is called when Greet is invoked.
func (_c *MockGreeter_Greet_Call) Run(run func(name string)) *MockGreeter_Greet_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
// Return sets the return values for the Greet mock call.
func (_c *MockGreeter_Greet_Call) Return(greeting string, err error) *MockGreeter_Greet_Call {
_c.Call.Return(greeting, err)
return _c
}
// RunAndReturn combines Run and Return into a single call.
func (_c *MockGreeter_Greet_Call) RunAndReturn(run func(string) (string, error)) *MockGreeter_Greet_Call {
_c.Call.Return(run)
return _c
}
// NewMockGreeter creates a new MockGreeter and registers a cleanup function
// that asserts all expectations were met when the test ends.
func NewMockGreeter(t interface {
mock.TestingT
Cleanup(func())
}) *MockGreeter {
m := &MockGreeter{}
m.Mock.Test(t)
t.Cleanup(func() { m.AssertExpectations(t) })
return m
}

View File

@ -6,13 +6,13 @@
# ./rename.sh acme-corp my-svc # non-interactive (org, project)
#
# What it changes:
# go.mod module path
# **/*.go import paths
# .mockery.yaml package path
# config/dev.yaml app.name
# go.mod module path
# **/*.go import paths
# .devcontainer/devcontainer.json name field
# README.md heading + module path references
# CLAUDE.md Module line
# README.md heading + module path references
# CLAUDE.md Module line
# .golangci.yml goimports local-prefixes
# git history squashed into one INIT commit
set -euo pipefail
@ -66,14 +66,19 @@ if [[ "$CURRENT_MODULE" != "$TEMPLATE_MODULE" ]]; then
echo
fi
# ── Capture template version before touching anything ─────────────────────────
TEMPLATE_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "untagged")
# ── Gather inputs ─────────────────────────────────────────────────────────────
heading "Go Template — Project Renamer"
echo "This script rewrites the module path and project name throughout the codebase."
echo
INTERACTIVE=true
if [[ $# -ge 2 ]]; then
NEW_ORG="$1"
NEW_PROJECT="$2"
INTERACTIVE=false
else
while true; do
read -rp "Org / username (e.g. djmil): " NEW_ORG
@ -96,12 +101,13 @@ OLD_DISPLAY=$(to_title "$OLD_PROJECT") # e.g. Go Template
# ── Preview ───────────────────────────────────────────────────────────────────
heading "Changes to be applied"
printf " %-22s %s → %s\n" "Module path:" "$OLD_MODULE" "$NEW_MODULE"
printf " %-22s %s → %s\n" "Project name:" "$OLD_PROJECT" "$NEW_PROJECT"
printf " %-22s %s → %s\n" "Display name:" "$OLD_DISPLAY" "$NEW_DISPLAY"
printf " %-22s %s → %s\n" "Module path:" "$OLD_MODULE" "$NEW_MODULE"
printf " %-22s %s → %s\n" "Project name:" "$OLD_PROJECT" "$NEW_PROJECT"
printf " %-22s %s → %s\n" "Display name:" "$OLD_DISPLAY" "$NEW_DISPLAY"
printf " %-22s %s\n" "Template version:" "$TEMPLATE_TAG"
echo
if [[ $# -lt 2 ]]; then
if $INTERACTIVE; then
read -rp "Apply these changes? [y/N] " CONFIRM
case "$CONFIRM" in
[yY][eE][sS]|[yY]) ;;
@ -113,10 +119,8 @@ fi
# macOS sed requires an extension argument for -i; GNU sed does not.
sedi() {
if sed --version &>/dev/null 2>&1; then
# GNU sed
sed -i "$@"
else
# BSD / macOS sed
sed -i '' "$@"
fi
}
@ -144,43 +148,35 @@ for f in $GO_FILES; do
done
info "${CHANGED_GO} Go source file(s)"
# 3. .mockery.yaml — package path
if [[ -f .mockery.yaml ]]; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" .mockery.yaml
info ".mockery.yaml"
fi
# 4. config/dev.yaml — app.name
if [[ -f config/dev.yaml ]]; then
sedi "s|name: \"${OLD_PROJECT}\"|name: \"${NEW_PROJECT}\"|g" config/dev.yaml
info "config/dev.yaml"
fi
# 5. .devcontainer/devcontainer.json — "name" field
# 3. .devcontainer/devcontainer.json — "name" field
if [[ -f .devcontainer/devcontainer.json ]]; then
sedi "s|\"name\": \"${OLD_DISPLAY}\"|\"name\": \"${NEW_DISPLAY}\"|g" \
.devcontainer/devcontainer.json
info ".devcontainer/devcontainer.json"
fi
# 6. README.md — heading + all module path occurrences
# 4. README.md — heading + all module path occurrences
if [[ -f README.md ]]; then
sedi "s|^# ${OLD_PROJECT}$|# ${NEW_PROJECT}|g" README.md
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" README.md
sedi "s|name: \"${OLD_PROJECT}\"|name: \"${NEW_PROJECT}\"|g" README.md
info "README.md"
fi
# 7. CLAUDE.md — Module line
# 5. CLAUDE.md — Module line
if [[ -f CLAUDE.md ]]; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" CLAUDE.md
info "CLAUDE.md"
fi
# 8. git remote origin — rewrite URL preserving scheme (https or ssh)
# 6. .golangci.yml — goimports local-prefixes
if [[ -f .golangci.yml ]]; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" .golangci.yml
info ".golangci.yml"
fi
# 6. git remote origin — rewrite URL preserving scheme (https or ssh)
if git remote get-url origin &>/dev/null 2>&1; then
OLD_REMOTE=$(git remote get-url origin)
# Detect scheme and build new URL accordingly
if [[ "$OLD_REMOTE" == https://* ]]; then
NEW_REMOTE="https://${DEFAULT_HOST}/${NEW_ORG}/${NEW_PROJECT}.git"
else
@ -193,13 +189,37 @@ else
warn "No 'origin' remote found — skipping remote update."
fi
# ── Squash git history into a single INIT commit ──────────────────────────────
heading "Git history"
DO_SQUASH=true
if $INTERACTIVE; then
read -rp "Squash all template history into one INIT commit? [Y/n] " SQUASH_CONFIRM
case "$SQUASH_CONFIRM" in
[nN][oO]|[nN]) DO_SQUASH=false ;;
esac
fi
if $DO_SQUASH; then
# Stage rename changes, fold all commits back to root, rewrite as single commit.
git add -A
ROOT_COMMIT=$(git rev-list --max-parents=0 HEAD)
git reset --soft "$ROOT_COMMIT"
git commit --amend -m "init: bootstrap ${NEW_PROJECT} from go-template ${TEMPLATE_TAG}
Initialized from ${OLD_MODULE} @ ${TEMPLATE_TAG}.
Module renamed to ${NEW_MODULE}."
info "History squashed → single INIT commit (template: ${TEMPLATE_TAG})"
else
warn "Skipping squash — template history preserved."
fi
# ── Post-rename suggestions ───────────────────────────────────────────────────
heading "Done"
echo "Module is now: ${BOLD}${NEW_MODULE}${RESET}"
echo
echo "Recommended next steps:"
echo " go mod tidy # sync go.sum after path change"
echo " make mocks # regenerate mocks with new import paths"
echo " make build # verify it compiles"
echo " make test # verify tests pass"
echo " git push -u origin main"
echo " go mod tidy # sync go.sum after path change"
echo " make build # verify it compiles"
echo " make test # verify tests pass"
echo " git push -u origin main --force-with-lease"

View File

@ -9,6 +9,5 @@ import (
_ "github.com/go-delve/delve/cmd/dlv"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/securego/gosec/v2/cmd/gosec"
_ "github.com/vektra/mockery/v2"
_ "golang.org/x/vuln/cmd/govulncheck"
)