add makefile

This commit is contained in:
Adem Baccara
2025-08-17 15:36:32 +01:00
parent bd658e9c7b
commit 59845a359f
16 changed files with 445 additions and 46 deletions
+1 -1
View File
@@ -32,4 +32,4 @@ go.work.sum
# .vscode/
brainstorm
.idea
lazyssh
bin
+105
View File
@@ -0,0 +1,105 @@
run:
timeout: 5m
allow-parallel-runners: true
issues:
# disable the default set of exclude rules
exclude-use-default: false
exclude-rules:
# disable some linters for api packages
- path: "api/*"
linters:
- lll
# disable some linters for internal packages
- path: "internal/*"
linters:
- dupl
- lll
# exclude some linters for the test directory and test files
- path: test/.*|.*_test\.go
linters:
- dupl
- errcheck
- goconst
- gocyclo
- gosec
linters:
disable-all: true
enable:
- asciicheck
- bodyclose
- copyloopvar
- dogsled
- dupl
- errcheck
- errorlint
- exhaustive
- ginkgolinter
- goconst
- gocritic
- gocyclo
- gofmt
- goheader
- goimports
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- nolintlint
- prealloc
- revive
- staticcheck
- typecheck
- unconvert
- unparam
- unused
linters-settings:
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- assignOp
- filepathJoin
- paramTypeCombine
- rangeValCopy
- unnamedResult
- whyNoLint
goimports:
local-prefixes: github.com/kubeflow/notebooks/workspaces/backend
goheader:
template: |-
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
misspell:
locale: US
revive:
rules:
- name: comment-spacings
+11 -4
View File
@@ -2,18 +2,25 @@ package main
import (
"fmt"
"os"
"time"
"github.com/Adembc/lazyssh/internal/adapters/data/memory"
"github.com/Adembc/lazyssh/internal/adapters/ui"
"github.com/Adembc/lazyssh/internal/core/services"
"github.com/spf13/cobra"
"os"
)
var (
version = "develop"
gitCommit = "unknown"
buildTime = time.Now().Format("2006-01-02 15:04:05")
)
func main() {
serverInMemoryRepo := memory.NewServerRepository()
serverService := services.NewServerService(serverInMemoryRepo)
tui := ui.NewTUI(serverService)
tui := ui.NewTUI(serverService, version, gitCommit, buildTime)
rootCmd := &cobra.Command{
Use: ui.AppName,
@@ -25,7 +32,7 @@ func main() {
rootCmd.SilenceUsage = true
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
+4 -4
View File
@@ -5,6 +5,7 @@ go 1.24.4
require (
github.com/gdamore/tcell/v2 v2.8.1
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb
github.com/spf13/cobra v1.9.1
)
require (
@@ -13,9 +14,8 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
)
+6 -3
View File
@@ -58,8 +58,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -68,8 +69,9 @@ 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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -78,8 +80,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+3 -4
View File
@@ -1,14 +1,14 @@
package memory
import (
"github.com/Adembc/lazyssh/internal/core/domain"
"strconv"
"strings"
"time"
"github.com/Adembc/lazyssh/internal/core/domain"
)
type serverRepository struct {
}
type serverRepository struct{}
var servers = []domain.Server{
{Alias: "web-01", Host: "192.168.1.10", User: "root", Port: 22, Key: "~/.ssh/id_rsa", Tags: []string{"prod", "web"}, Status: "online", LastSeen: time.Now().Add(-2 * time.Hour)},
@@ -58,7 +58,6 @@ func (r *serverRepository) ListServers(query string) ([]domain.Server, error) {
}
}
return filteredServers, nil
return servers, nil
}
// UpdateServer updates an existing server with new details.
-1
View File
@@ -4,7 +4,6 @@ import "time"
const (
AppName = "lazyssh"
AppVersion = "v0.0.1"
RepoURL = "github.com/adembc/lazyssh"
SplashScreenDuration = 1 * time.Second
Banner = `
+3 -2
View File
@@ -2,6 +2,7 @@ package ui
import (
"fmt"
"github.com/Adembc/lazyssh/internal/core/domain"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
@@ -87,10 +88,10 @@ func (t *tui) handleServerEdit() {
func (t *tui) handleServerSave(server domain.Server, original *domain.Server) {
if original != nil {
// Edit mode
t.serverService.UpdateServer(*original, server)
_ = t.serverService.UpdateServer(*original, server)
} else {
// Add mode
t.serverService.AddServer(server)
_ = t.serverService.AddServer(server)
}
t.refreshServerList()
+76 -9
View File
@@ -1,23 +1,28 @@
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strings"
"time"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type AppHeader struct {
*tview.Flex
version string
repoURL string
version string
buildTime string
gitCommit string
repoURL string
}
func NewAppHeader(version, repoURL string) *AppHeader {
func NewAppHeader(version, gitCommit, buildTime, repoURL string) *AppHeader {
header := &AppHeader{
Flex: tview.NewFlex(),
version: version,
repoURL: repoURL,
Flex: tview.NewFlex(),
version: version,
repoURL: repoURL,
buildTime: buildTime,
gitCommit: gitCommit,
}
header.build()
return header
@@ -57,7 +62,24 @@ func (h *AppHeader) buildCenterSection(bg tcell.Color) *tview.TextView {
SetDynamicColors(true).
SetTextAlign(tview.AlignCenter)
center.SetBackgroundColor(bg)
center.SetText("[black:#2ECC71::b] " + h.version + " [-] [black:#3B82F6::b] TUI [-]")
commit := shortCommit(h.gitCommit)
// Build tag-like chips for version, commit, and build time
versionTag := makeTag(h.version, "#22C55E") // green
commitTag := ""
if commit != "" {
commitTag = makeTag(commit, "#A78BFA") // violet
}
timeTag := makeTag(formatBuildTime(h.buildTime), "#3B82F6") // blue
text := versionTag
if commitTag != "" {
text += " " + commitTag
}
text += " " + timeTag
center.SetText(text)
return center
}
@@ -77,3 +99,48 @@ func (h *AppHeader) createSeparator() *tview.TextView {
separator.SetText("[#444444]" + strings.Repeat("─", 200) + "[-]")
return separator
}
// shortCommit returns first 7 chars of commit if it looks valid; otherwise empty string.
func shortCommit(c string) string {
c = strings.TrimSpace(c)
if c == "" || c == "unknown" || c == "(devel)" {
return ""
}
if len(c) > 7 {
return c[:7]
}
return c
}
// formatBuildTime tries to parse common time formats and returns a concise human-readable string.
func formatBuildTime(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return "unknown"
}
layouts := []string{
"2006-01-02 15:04:05",
time.RFC3339,
time.RFC1123,
time.RFC1123Z,
}
var t time.Time
var err error
for _, l := range layouts {
t, err = time.Parse(l, s)
if err == nil {
return t.Format("Mon, 02 Jan 2006 15:04")
}
}
return s
}
// makeTag returns a rectangular-looking colored chip for the given text.
func makeTag(text, bg string) string {
text = strings.TrimSpace(text)
if text == "" {
return ""
}
return "[black:" + bg + "::b] " + text + " [-]"
}
+1
View File
@@ -2,6 +2,7 @@ package ui
import (
"fmt"
"github.com/Adembc/lazyssh/internal/core/domain"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
+4 -3
View File
@@ -2,12 +2,13 @@ package ui
import (
"fmt"
"github.com/Adembc/lazyssh/internal/core/domain"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strconv"
"strings"
"time"
"github.com/Adembc/lazyssh/internal/core/domain"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type ServerFormMode int
+3 -2
View File
@@ -1,10 +1,11 @@
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strings"
"time"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// buildSplash creates a splash screen with a spinner; returns primitive and a stop function
+16 -7
View File
@@ -1,13 +1,18 @@
package ui
import (
"time"
"github.com/Adembc/lazyssh/internal/core/ports"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"time"
)
type tui struct {
version string
commit string
buildDate string
app *tview.Application
serverService ports.ServerService
@@ -25,21 +30,23 @@ type tui struct {
searchVisible bool
}
func NewTUI(ss ports.ServerService) *tui {
func NewTUI(ss ports.ServerService, version, commit, buildDate string) *tui {
return &tui{
app: tview.NewApplication(),
serverService: ss,
version: version,
commit: commit,
buildDate: buildDate,
}
}
func (t *tui) Run() error {
t.app.EnableMouse(true)
t.initializeTheme().buildComponents().buildLayout().bindEvents().loadInitialData().loadSplashScreen()
return t.app.Run()
}
func (t *tui) initializeTheme() *tui {
tview.Styles.PrimitiveBackgroundColor = tcell.Color232
tview.Styles.ContrastBackgroundColor = tcell.Color235
@@ -51,8 +58,9 @@ func (t *tui) initializeTheme() *tui {
tview.Styles.GraphicsColor = tcell.Color238
return t
}
func (t *tui) buildComponents() *tui {
t.header = NewAppHeader(AppVersion, RepoURL)
t.header = NewAppHeader(t.version, t.commit, t.buildDate, RepoURL)
t.searchBar = NewSearchBar().
OnSearch(t.handleSearchInput).
OnEscape(t.hideSearchBar)
@@ -63,8 +71,8 @@ func (t *tui) buildComponents() *tui {
t.statusBar = NewStatusBar()
return t
}
func (t *tui) buildLayout() *tui {
func (t *tui) buildLayout() *tui {
t.left = tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(t.hintBar, 1, 0, false).
AddItem(t.serverList, 0, 1, true)
@@ -82,10 +90,12 @@ func (t *tui) buildLayout() *tui {
AddItem(t.statusBar, 1, 0, false)
return t
}
func (t *tui) bindEvents() *tui {
t.root.SetInputCapture(t.handleGlobalKeys)
return t
}
func (t *tui) loadInitialData() *tui {
servers, _ := t.serverService.ListServers("")
t.serverList.UpdateServers(servers)
@@ -103,5 +113,4 @@ func (t *tui) loadSplashScreen() *tui {
})
})
return t
}
+2 -1
View File
@@ -2,9 +2,10 @@ package ui
import (
"fmt"
"github.com/Adembc/lazyssh/internal/core/domain"
"strings"
"time"
"github.com/Adembc/lazyssh/internal/core/domain"
)
func statusIcon(s string) string {
-1
View File
@@ -18,7 +18,6 @@ func NewServerService(sr ports.ServerRepository) *serverService {
// ListServers returns a list of servers.
func (s *serverService) ListServers(query string) ([]domain.Server, error) {
// do any relevant business logic here if needed
servers, err := s.serverRepository.ListServers(query)
if err != nil {
+210 -4
View File
@@ -1,4 +1,210 @@
build:
go build -o lazyssh ./cmd
run:
go run ./cmd
# Makefile for LazySSH project
##@ General
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
# Default target
.DEFAULT_GOAL := help
# Project variables
PROJECT_NAME ?= $(shell basename $(CURDIR))
VERSION ?= v0.0.1
BUILD_TIME ?= $(shell date -u '+%Y-%m-%d_%H:%M:%S')
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
# Build variables
BINARY_NAME ?= lazyssh
OUTPUT_DIR ?= ./bin
CMD_DIR ?= ./cmd
PKG_LIST := $(shell go list ./...)
# LDFLAGS for version information
LDFLAGS = -ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.gitCommit=$(GIT_COMMIT)"
##@ Dependencies
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
# Tool versions
GOLANGCI_LINT_VERSION ?= v1.61.0
GOFUMPT_VERSION ?= v0.7.0
STATICCHECK_VERSION ?= 2024.1.1
# Tool binaries
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
GOFUMPT = $(LOCALBIN)/gofumpt
STATICCHECK = $(LOCALBIN)/staticcheck
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f $(1) || true ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
endef
.PHONY: tools
tools: golangci-lint gofumpt staticcheck ## Install all development tools
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
.PHONY: gofumpt
gofumpt: $(GOFUMPT) ## Download gofumpt locally if necessary
$(GOFUMPT): $(LOCALBIN)
$(call go-install-tool,$(GOFUMPT),mvdan.cc/gofumpt,$(GOFUMPT_VERSION))
.PHONY: staticcheck
staticcheck: $(STATICCHECK) ## Download staticcheck locally if necessary
$(STATICCHECK): $(LOCALBIN)
$(call go-install-tool,$(STATICCHECK),honnef.co/go/tools/cmd/staticcheck,$(STATICCHECK_VERSION))
##@ Development
.PHONY: fmt
fmt: gofumpt ## Format Go code
$(GOFUMPT) -l -w .
go fmt ./...
.PHONY: vet
vet: ## Run go vet against code
go vet ./...
.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT) run
.PHONY: lint-fix
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
$(GOLANGCI_LINT) run --fix
.PHONY: check
check: staticcheck ## Run staticcheck analyzer
$(STATICCHECK) ./...
.PHONY: quality
quality: fmt vet ## Run all code quality checks (i will add lint and check)
##@ Testing
.PHONY: test
test: ## Run unit tests
go test -race -coverprofile=coverage.out ./...
.PHONY: test-verbose
test-verbose: ## Run unit tests with verbose output
go test -race -v -coverprofile=coverage.out ./...
.PHONY: test-short
test-short: ## Run unit tests (short mode)
go test -race -short ./...
.PHONY: coverage
coverage: test ## Run tests and show coverage
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report generated: coverage.html"
.PHONY: benchmark
benchmark: ## Run benchmarks
go test -bench=. -benchmem ./...
##@ Building
.PHONY: deps
deps: ## Download dependencies
go mod download
go mod verify
.PHONY: tidy
tidy: ## Tidy up dependencies
go mod tidy
.PHONY: build
build: quality $(OUTPUT_DIR) ## Build binary
go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME) $(CMD_DIR)
.PHONY: build-all
build-all: quality $(OUTPUT_DIR) ## Build binaries for all platforms
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-linux-amd64 $(CMD_DIR)
GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-linux-arm64 $(CMD_DIR)
GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-darwin-amd64 $(CMD_DIR)
GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-darwin-arm64 $(CMD_DIR)
GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-windows-amd64.exe $(CMD_DIR)
.PHONY: install
install: build ## Install binary to GOBIN
cp $(OUTPUT_DIR)/$(BINARY_NAME) $(GOBIN)/
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
##@ Running
.PHONY: run
run: ## Run application from source
go run $(CMD_DIR)/main.go
.PHONY: run-race
run-race: ## Run application from source with race detector
go run -race $(CMD_DIR)/main.go
##@ Maintenance
.PHONY: clean
clean: ## Clean build artifacts and caches
go clean -cache -testcache -modcache
rm -rf $(OUTPUT_DIR)
rm -rf $(LOCALBIN)
rm -f coverage.out coverage.html
.PHONY: clean-build
clean-build: ## Clean only build artifacts
rm -rf $(OUTPUT_DIR)
rm -f coverage.out coverage.html
.PHONY: update-deps
update-deps: ## Update all dependencies
go get -u ./...
go mod tidy
.PHONY: security
security: ## Run security checks
go list -json -deps ./... | grep -v "$$GOROOT" | jq -r '.Module | select(.Path != null) | .Path' | sort -u | xargs go list -json -m | jq -r 'select(.Replace == null) | "\(.Path)@\(.Version)"' | xargs -I {} sh -c 'echo "Checking {}" && go list -json -m {} | jq -r .Dir' >/dev/null
.PHONY: version
version: ## Display version information
@echo "Project: $(PROJECT_NAME)"
@echo "Version: $(VERSION)"
@echo "Build Time: $(BUILD_TIME)"
@echo "Git Commit: $(GIT_COMMIT)"
@echo "Go Version: $(shell go version)"
##@ Help
.PHONY: help
help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)