refactor: enable golangci-lint on magefile, fix errors

This commit is contained in:
John Starich
2026-03-06 00:56:27 -06:00
committed by kolaente
parent c49d8e129a
commit cea8c7807d
2 changed files with 141 additions and 99 deletions

View File

@@ -1,6 +1,8 @@
version: "2" version: "2"
run: run:
tests: true tests: true
build-tags:
- mage
linters: linters:
enable: enable:
- asasalint - asasalint
@@ -146,6 +148,10 @@ linters:
- linters: - linters:
- revive - revive
text: 'var-naming: avoid package names that conflict with Go standard library package names' text: 'var-naming: avoid package names that conflict with Go standard library package names'
- linters:
- err113
path: magefile.go
text: 'do not define dynamic errors, use wrapped static errors instead:'
paths: paths:
- third_party$ - third_party$
- builtin$ - builtin$

View File

@@ -24,6 +24,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -60,7 +61,7 @@ var (
PkgVersion = "unstable" PkgVersion = "unstable"
// Aliases are mage aliases of targets // Aliases are mage aliases of targets
Aliases = map[string]interface{}{ Aliases = map[string]any{
"build": Build.Build, "build": Build.Build,
"check:got-swag": Check.GotSwag, "check:got-swag": Check.GotSwag,
"release": Release.Release, "release": Release.Release,
@@ -86,14 +87,15 @@ func goDetectVerboseFlag() string {
return fmt.Sprintf("-v=%t", mg.Verbose()) return fmt.Sprintf("-v=%t", mg.Verbose())
} }
func runCmdWithOutput(name string, arg ...string) (output []byte, err error) { func runGitCommandWithOutput(arg ...string) (output []byte, err error) {
cmd := exec.Command(name, arg...) cmd := exec.Command("git", arg...)
output, err = cmd.Output() output, err = cmd.Output()
if err != nil { if err != nil {
if ee, is := err.(*exec.ExitError); is { var ee *exec.ExitError
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err) if errors.As(err, &ee) {
return nil, fmt.Errorf("error running command: %s, %w", string(ee.Stderr), err)
} }
return nil, fmt.Errorf("error running command: %s", err) return nil, fmt.Errorf("error running command: %w", err)
} }
return output, nil return output, nil
@@ -130,7 +132,7 @@ func getRawVersionNumber() (version string, err error) {
return strings.Replace(os.Getenv("DRONE_BRANCH"), "release/v", "", 1), nil return strings.Replace(os.Getenv("DRONE_BRANCH"), "release/v", "", 1), nil
} }
versionBytes, err := runCmdWithOutput("git", "describe", "--tags", "--always", "--abbrev=10") versionBytes, err := runGitCommandWithOutput("describe", "--tags", "--always", "--abbrev=10")
return string(versionBytes), err return string(versionBytes), err
} }
@@ -204,15 +206,15 @@ func runAndStreamOutput(cmd string, args ...string) error {
// Will check if the tool exists and if not install it from the provided import path // Will check if the tool exists and if not install it from the provided import path
// If any errors occur, it will exit with a status code of 1. // If any errors occur, it will exit with a status code of 1.
func checkAndInstallGoTool(tool, importPath string) { func checkAndInstallGoTool(tool, importPath string) error {
if err := exec.Command(tool).Run(); err != nil && strings.Contains(err.Error(), "executable file not found") { if err := exec.Command(tool).Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Printf("%s not installed, installing %s...\n", tool, importPath) fmt.Printf("%s not installed, installing %s...\n", tool, importPath)
if err := exec.Command("go", "install", goDetectVerboseFlag(), importPath).Run(); err != nil { if err := exec.Command("go", "install", goDetectVerboseFlag(), importPath).Run(); err != nil { //nolint:gosec // Every caller to checkAndInstallGoTool is hard-coded at time of writing, so no injection possible.
fmt.Printf("Error installing %s\n", tool) return fmt.Errorf("error installing %s: %w", tool, err)
os.Exit(1)
} }
fmt.Println("Installed.") fmt.Println("Installed.")
} }
return nil
} }
// Calculates a hash of a file // Calculates a hash of a file
@@ -269,19 +271,19 @@ func copyFile(src, dst string) error {
func moveFile(src, dst string) error { func moveFile(src, dst string) error {
inputFile, err := os.Open(src) inputFile, err := os.Open(src)
if err != nil { if err != nil {
return fmt.Errorf("couldn't open source file: %s", err) return fmt.Errorf("couldn't open source file: %w", err)
} }
defer inputFile.Close() defer inputFile.Close()
outputFile, err := os.Create(dst) outputFile, err := os.Create(dst)
if err != nil { if err != nil {
return fmt.Errorf("couldn't open dest file: %s", err) return fmt.Errorf("couldn't open dest file: %w", err)
} }
defer outputFile.Close() defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile) _, err = io.Copy(outputFile, inputFile)
if err != nil { if err != nil {
return fmt.Errorf("writing to output file failed: %s", err) return fmt.Errorf("writing to output file failed: %w", err)
} }
// Make sure to copy copy the permissions of the original file as well // Make sure to copy copy the permissions of the original file as well
@@ -297,7 +299,7 @@ func moveFile(src, dst string) error {
// The copy was successful, so now delete the original file // The copy was successful, so now delete the original file
err = os.Remove(src) err = os.Remove(src)
if err != nil { if err != nil {
return fmt.Errorf("failed removing original file: %s", err) return fmt.Errorf("failed removing original file: %w", err)
} }
return nil return nil
} }
@@ -316,7 +318,7 @@ func appendToFile(filename, content string) error {
const InfoColor = "\033[1;32m%s\033[0m" const InfoColor = "\033[1;32m%s\033[0m"
func printSuccess(text string, args ...interface{}) { func printSuccess(text string, args ...any) {
text = fmt.Sprintf(text, args...) text = fmt.Sprintf(text, args...)
fmt.Printf(InfoColor+"\n", text) fmt.Printf(InfoColor+"\n", text)
} }
@@ -346,15 +348,18 @@ func setProcessGroup(cmd *exec.Cmd) {
} }
// killProcessGroup sends a signal to the entire process group of the given command. // killProcessGroup sends a signal to the entire process group of the given command.
func killProcessGroup(cmd *exec.Cmd) { func killProcessGroup(cmd *exec.Cmd) error {
if cmd.Process != nil { if cmd.Process == nil {
pgid, err := syscall.Getpgid(cmd.Process.Pid) return nil
if err == nil {
syscall.Kill(-pgid, syscall.SIGTERM)
} }
cmd.Wait() if pgid, err := syscall.Getpgid(cmd.Process.Pid); err == nil { // use best-effort to kill full process group
err = syscall.Kill(-pgid, syscall.SIGTERM)
if err != nil {
return err
} }
} }
return cmd.Wait()
}
// waitForHTTP polls a URL until it returns a 200 status or the timeout expires. // waitForHTTP polls a URL until it returns a 200 status or the timeout expires.
func waitForHTTP(url string, timeout time.Duration) error { func waitForHTTP(url string, timeout time.Duration) error {
@@ -525,7 +530,9 @@ func (Test) E2E(args string) error {
} }
defer func() { defer func() {
fmt.Println("\n--- Stopping API server ---") fmt.Println("\n--- Stopping API server ---")
killProcessGroup(apiCmd) if err := killProcessGroup(apiCmd); err != nil {
fmt.Println("Failed to stop API server:", err)
}
}() }()
// Wait for API to be ready // Wait for API to be ready
@@ -549,7 +556,7 @@ func (Test) E2E(args string) error {
// Serve the built frontend with vite preview (static, no file watchers) // Serve the built frontend with vite preview (static, no file watchers)
fmt.Println("\n--- Starting frontend preview server ---") fmt.Println("\n--- Starting frontend preview server ---")
frontendCmd := exec.Command("pnpm", "preview:dev", "--port", strconv.Itoa(frontendPort)) frontendCmd := exec.Command("pnpm", "preview:dev", "--port", strconv.Itoa(frontendPort)) //nolint:gosec // This mage task runs end to end tests with environment-based configuration, it must use the port environment variable to suit its current environment.
frontendCmd.Dir = "frontend" frontendCmd.Dir = "frontend"
frontendCmd.Stdout = os.Stdout frontendCmd.Stdout = os.Stdout
frontendCmd.Stderr = os.Stderr frontendCmd.Stderr = os.Stderr
@@ -559,7 +566,9 @@ func (Test) E2E(args string) error {
} }
defer func() { defer func() {
fmt.Println("\n--- Stopping frontend preview server ---") fmt.Println("\n--- Stopping frontend preview server ---")
killProcessGroup(frontendCmd) if err := killProcessGroup(frontendCmd); err != nil {
fmt.Println("Failed to stop API server:", err)
}
}() }()
// Wait for frontend to be ready // Wait for frontend to be ready
@@ -600,7 +609,7 @@ func (Test) E2E(args string) error {
type Check mg.Namespace type Check mg.Namespace
// GotSwag checks if the swagger docs need to be re-generated from the code annotations // GotSwag checks if the swagger docs need to be re-generated from the code annotations
func (Check) GotSwag() { func (Check) GotSwag() error {
mg.Deps(initVars) mg.Deps(initVars)
// The check is pretty cheaply done: We take the hash of the swagger.json file, generate the docs, // The check is pretty cheaply done: We take the hash of the swagger.json file, generate the docs,
// hash the file again and compare the two hashes to see if anything changed. If that's the case, // hash the file again and compare the two hashes to see if anything changed. If that's the case,
@@ -610,27 +619,26 @@ func (Check) GotSwag() {
// docs after the check. This behaviour is good enough for ci though. // docs after the check. This behaviour is good enough for ci though.
oldHash, err := calculateSha256FileHash("./pkg/swagger/swagger.json") oldHash, err := calculateSha256FileHash("./pkg/swagger/swagger.json")
if err != nil { if err != nil {
fmt.Printf("Error getting old hash of the swagger docs: %s", err) return fmt.Errorf("error getting old hash of the swagger docs: %w", err)
os.Exit(1)
} }
(Generate{}).SwaggerDocs() if generateErr := (Generate{}).SwaggerDocs(); generateErr != nil {
return generateErr
}
newHash, err := calculateSha256FileHash("./pkg/swagger/swagger.json") newHash, err := calculateSha256FileHash("./pkg/swagger/swagger.json")
if err != nil { if err != nil {
fmt.Printf("Error getting new hash of the swagger docs: %s", err) return fmt.Errorf("error getting new hash of the swagger docs: %w", err)
os.Exit(1)
} }
if oldHash != newHash { if oldHash != newHash {
fmt.Println("Swagger docs are not up to date.") return fmt.Errorf("swagger docs are not up to date: run 'mage generate:swagger-docs' and commit the result")
fmt.Println("Please run 'mage generate:swagger-docs' and commit the result.")
os.Exit(1)
} }
return nil
} }
// Translations checks if all translation keys used in the code exist in the English translation file // Translations checks if all translation keys used in the code exist in the English translation file
func (Check) Translations() { func (Check) Translations() error {
mg.Deps(initVars) mg.Deps(initVars)
fmt.Println("Checking for missing translation keys...") fmt.Println("Checking for missing translation keys...")
@@ -638,8 +646,7 @@ func (Check) Translations() {
translationFile := "./pkg/i18n/lang/en.json" translationFile := "./pkg/i18n/lang/en.json"
translations, err := loadTranslations(translationFile) translations, err := loadTranslations(translationFile)
if err != nil { if err != nil {
fmt.Printf("Error loading translations: %v\n", err) return fmt.Errorf("error loading translations: %w", err)
os.Exit(1)
} }
fmt.Printf("Loaded %d translation keys from %s\n", len(translations), translationFile) fmt.Printf("Loaded %d translation keys from %s\n", len(translations), translationFile)
@@ -647,8 +654,7 @@ func (Check) Translations() {
// Extract keys from codebase // Extract keys from codebase
keys, err := walkCodebaseForTranslationKeys(".") keys, err := walkCodebaseForTranslationKeys(".")
if err != nil { if err != nil {
fmt.Printf("Error walking codebase: %v\n", err) return fmt.Errorf("error walking codebase: %w", err)
os.Exit(1)
} }
fmt.Printf("Found %d translation keys in the codebase\n", len(keys)) fmt.Printf("Found %d translation keys in the codebase\n", len(keys))
@@ -663,17 +669,18 @@ func (Check) Translations() {
// Print results // Print results
if len(missingKeys) > 0 { if len(missingKeys) > 0 {
fmt.Printf("\nFound %d missing translation keys:\n", len(missingKeys)) var errs []error
for key, occurrences := range missingKeys { for key, occurrences := range missingKeys {
fmt.Printf("\nKey: %s\n", key) var keyErrs []error
for _, occurrence := range occurrences { for _, occurrence := range occurrences {
fmt.Printf(" - %s:%d\n", occurrence.FilePath, occurrence.Line) keyErrs = append(keyErrs, fmt.Errorf("- %s:%d", occurrence.FilePath, occurrence.Line))
} }
errs = append(errs, fmt.Errorf("missing key %s in files:\n%w", key, errors.Join(keyErrs...)))
}
return fmt.Errorf("found %d missing translation keys:\n%w", len(missingKeys), errors.Join(errs...))
} }
os.Exit(1)
} else {
printSuccess("All translation keys are present in the translation file!") printSuccess("All translation keys are present in the translation file!")
} return nil
} }
// TranslationKey represents a translation key found in the code // TranslationKey represents a translation key found in the code
@@ -687,12 +694,12 @@ type TranslationKey struct {
func loadTranslations(filePath string) (map[string]bool, error) { func loadTranslations(filePath string) (map[string]bool, error) {
data, err := os.ReadFile(filePath) data, err := os.ReadFile(filePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading translation file: %v", err) return nil, fmt.Errorf("error reading translation file: %w", err)
} }
var translationsMap map[string]interface{} var translationsMap map[string]any
if err := json.Unmarshal(data, &translationsMap); err != nil { if err := json.Unmarshal(data, &translationsMap); err != nil {
return nil, fmt.Errorf("error parsing JSON: %v", err) return nil, fmt.Errorf("error parsing JSON: %w", err)
} }
// Flatten the nested structure // Flatten the nested structure
@@ -703,7 +710,7 @@ func loadTranslations(filePath string) (map[string]bool, error) {
} }
// flattenTranslations recursively flattens a nested map structure into a flat map with dot-separated keys // flattenTranslations recursively flattens a nested map structure into a flat map with dot-separated keys
func flattenTranslations(prefix string, src map[string]interface{}, dest map[string]bool) { func flattenTranslations(prefix string, src map[string]any, dest map[string]bool) {
for k, v := range src { for k, v := range src {
key := k key := k
if prefix != "" { if prefix != "" {
@@ -713,7 +720,7 @@ func flattenTranslations(prefix string, src map[string]interface{}, dest map[str
switch vv := v.(type) { switch vv := v.(type) {
case string: case string:
dest[key] = true dest[key] = true
case map[string]interface{}: case map[string]any:
flattenTranslations(key, vv, dest) flattenTranslations(key, vv, dest)
} }
} }
@@ -756,7 +763,7 @@ func extractTranslationKeysFromFile(filePath string) ([]TranslationKey, error) {
// Read the file content // Read the file content
content, err := os.ReadFile(filePath) content, err := os.ReadFile(filePath)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading file %s: %v", filePath, err) return nil, fmt.Errorf("error reading file %s: %w", filePath, err)
} }
var keys []TranslationKey var keys []TranslationKey
@@ -786,22 +793,25 @@ func extractTranslationKeysFromFile(filePath string) ([]TranslationKey, error) {
return keys, nil return keys, nil
} }
func checkGolangCiLintInstalled() { func checkGolangCiLintInstalled() error {
mg.Deps(initVars) mg.Deps(initVars)
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") { if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Println("Please manually install golangci-lint by running") return fmt.Errorf("golangci-lint executable failed to run, please manually install golangci-lint by running the command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0")
os.Exit(1)
} }
return nil
} }
func (Check) Golangci() error { func (Check) Golangci() error {
checkGolangCiLintInstalled() if err := checkGolangCiLintInstalled(); err != nil {
return err
}
return runAndStreamOutput("golangci-lint", "run") return runAndStreamOutput("golangci-lint", "run")
} }
func (Check) GolangciFix() error { func (Check) GolangciFix() error {
checkGolangCiLintInstalled() if err := checkGolangCiLintInstalled(); err != nil {
return err
}
return runAndStreamOutput("golangci-lint", "run", "--fix") return runAndStreamOutput("golangci-lint", "run", "--fix")
} }
@@ -842,8 +852,7 @@ func (Build) Build() error {
distPath := filepath.Join("frontend", "dist") distPath := filepath.Join("frontend", "dist")
if _, err := os.Stat(distPath); os.IsNotExist(err) { if _, err := os.Stat(distPath); os.IsNotExist(err) {
if err := os.MkdirAll(distPath, 0o755); err != nil { if err := os.MkdirAll(distPath, 0o755); err != nil {
fmt.Printf("Error creating %s: %s\n", distPath, err) return fmt.Errorf("error creating %s: %w", distPath, err)
os.Exit(1)
} }
} }
@@ -851,8 +860,7 @@ func (Build) Build() error {
if _, err := os.Stat(indexFile); os.IsNotExist(err) { if _, err := os.Stat(indexFile); os.IsNotExist(err) {
f, err := os.Create(indexFile) f, err := os.Create(indexFile)
if err != nil { if err != nil {
fmt.Printf("Error creating %s: %s\n", indexFile, err) return fmt.Errorf("error creating %s: %w", indexFile, err)
os.Exit(1)
} }
f.Close() f.Close()
fmt.Printf("Warning: %s not found, created empty file\n", indexFile) fmt.Printf("Warning: %s not found, created empty file\n", indexFile)
@@ -928,7 +936,9 @@ func (Release) Dirs() error {
func prepareXgo() error { func prepareXgo() error {
mg.Deps(initVars) mg.Deps(initVars)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo") if err := checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo"); err != nil {
return err
}
fmt.Println("Pulling latest xgo docker image...") fmt.Println("Pulling latest xgo docker image...")
return runAndStreamOutput("docker", "pull", "ghcr.io/techknowlogick/xgo:latest") return runAndStreamOutput("docker", "pull", "ghcr.io/techknowlogick/xgo:latest")
@@ -936,7 +946,9 @@ func prepareXgo() error {
func runXgo(targets string) error { func runXgo(targets string) error {
mg.Deps(initVars) mg.Deps(initVars)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo") if err := checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo"); err != nil {
return err
}
extraLdflags := `-linkmode external -extldflags "-static" ` extraLdflags := `-linkmode external -extldflags "-static" `
@@ -960,6 +972,9 @@ func runXgo(targets string) error {
} }
if os.Getenv("DRONE_WORKSPACE") != "" { if os.Getenv("DRONE_WORKSPACE") != "" {
return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error { return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip directories // Skip directories
if info.IsDir() { if info.IsDir() {
return nil return nil
@@ -1018,7 +1033,10 @@ func (Release) Compress(ctx context.Context) error {
errs, _ := errgroup.WithContext(ctx) errs, _ := errgroup.WithContext(ctx)
filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error { walkErr := filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Only executable files // Only executable files
if !strings.Contains(info.Name(), Executable) { if !strings.Contains(info.Name(), Executable) {
return nil return nil
@@ -1042,13 +1060,18 @@ func (Release) Compress(ctx context.Context) error {
return nil return nil
}) })
if walkErr != nil {
return walkErr
}
return errs.Wait() return errs.Wait()
} }
// Copy copies all built binaries to dist/release/ in preparation for creating the os packages // Copy copies all built binaries to dist/release/ in preparation for creating the os packages
func (Release) Copy() error { func (Release) Copy() error {
return filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error { return filepath.Walk("./"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Only executable files // Only executable files
if !strings.Contains(info.Name(), Executable) { if !strings.Contains(info.Name(), Executable) {
return nil return nil
@@ -1096,6 +1119,9 @@ func (Release) OsPackage() error {
// over the newly created files, creating some kind of endless loop. // over the newly created files, creating some kind of endless loop.
bins := make(map[string]os.FileInfo) bins := make(map[string]os.FileInfo)
if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error { if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.Contains(info.Name(), ".sha256") || info.IsDir() { if strings.Contains(info.Name(), ".sha256") || info.IsDir() {
return nil return nil
} }
@@ -1147,7 +1173,7 @@ func (Release) Zip() error {
fmt.Printf("Zipping %s...\n", info.Name()) fmt.Printf("Zipping %s...\n", info.Name())
zipFile := filepath.Join(rootDir, DIST, "zip", info.Name()+".zip") zipFile := filepath.Join(rootDir, DIST, "zip", info.Name()+".zip")
c := exec.Command("zip", "-r", zipFile, ".", "-i", "*") c := exec.Command("zip", "-r", zipFile, ".", "-i", "*") //nolint:gosec // This mage task creates zips of every directory recursively, it must use the directory name in the resulting file path to distinguish output files.
c.Dir = path c.Dir = path
out, err := c.Output() out, err := c.Output()
fmt.Print(string(out)) fmt.Print(string(out))
@@ -1203,9 +1229,7 @@ func (Release) Packages() error {
err = exec.Command(binpath).Run() err = exec.Command(binpath).Run()
} }
if err != nil && strings.Contains(err.Error(), "executable file not found") { if err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Println("Please manually install nfpm by running") return fmt.Errorf("executable %s not found: please manually install nfpm by running the command: curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin", binpath)
fmt.Println("curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin")
os.Exit(1)
} }
err = (Release{}).PrepareNFPMConfig() err = (Release{}).PrepareNFPMConfig()
@@ -1371,7 +1395,7 @@ func (s *` + name + `) Handle(msg *message.Message) (err error) {
} }
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
var idx int64 = 0 var idx int64
for scanner.Scan() { for scanner.Scan() {
if scanner.Text() == "}" { if scanner.Text() == "}" {
// idx -= int64(len(scanner.Text())) // idx -= int64(len(scanner.Text()))
@@ -1398,9 +1422,15 @@ func (s *` + name + `) Handle(msg *message.Message) (err error) {
if err != nil { if err != nil {
return err return err
} }
f.Seek(idx, 0) if _, err := f.Seek(idx, 0); err != nil {
f.Write([]byte(registerListenerCode)) return err
f.Write(remainder) }
if _, err := f.Write([]byte(registerListenerCode)); err != nil {
return err
}
if _, err := f.Write(remainder); err != nil {
return err
}
/////// ///////
// Append the listener code // Append the listener code
@@ -1438,7 +1468,7 @@ func (n *` + name + `) ToMail(lang string) *notifications.Mail {
} }
// ToDB returns the ` + name + ` notification in a format which can be saved in the db // ToDB returns the ` + name + ` notification in a format which can be saved in the db
func (n *` + name + `) ToDB() interface{} { func (n *` + name + `) ToDB() any {
return nil return nil
} }
@@ -1466,13 +1496,15 @@ const DefaultConfigYAMLSamplePath = "config.yml.sample"
func (Generate) SwaggerDocs() error { func (Generate) SwaggerDocs() error {
mg.Deps(initVars) mg.Deps(initVars)
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag") if err := checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag"); err != nil {
return err
}
return runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", ".", "-o", "./pkg/swagger") return runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", ".", "-o", "./pkg/swagger")
} }
type ConfigNode struct { type ConfigNode struct {
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
Value interface{} `json:"default_value,omitempty"` Value any `json:"default_value,omitempty"`
Comment string `json:"comment,omitempty"` Comment string `json:"comment,omitempty"`
Children []*ConfigNode `json:"children,omitempty"` Children []*ConfigNode `json:"children,omitempty"`
} }
@@ -1523,14 +1555,15 @@ func convertConfigJSONToYAML(node *ConfigNode, indent int, isTopLevel bool, pare
isProviders := node.Key == "providers" && parentKey == "openid" isProviders := node.Key == "providers" && parentKey == "openid"
isArray := len(node.Children) > 0 && node.Children[0].Key == "" isArray := len(node.Children) > 0 && node.Children[0].Key == ""
for i, child := range node.Children { for i, child := range node.Children {
if isProviders { switch {
case isProviders:
writeComment(child.Comment, indent+1) writeComment(child.Comment, indent+1)
writeLine("-", indent+1) writeLine("-", indent+1)
result.WriteString(convertConfigJSONToYAML(child, indent+1, false, node.Key, commentOut)) result.WriteString(convertConfigJSONToYAML(child, indent+1, false, node.Key, commentOut))
} else if isArray { case isArray:
writeComment(child.Comment, indent+1) writeComment(child.Comment, indent+1)
writeLine("- "+formatValue(child.Value), indent+1) writeLine("- "+formatValue(child.Value), indent+1)
} else { default:
result.WriteString(convertConfigJSONToYAML(child, indent+1, false, node.Key, commentOut)) result.WriteString(convertConfigJSONToYAML(child, indent+1, false, node.Key, commentOut))
} }
if i == len(node.Children)-1 && !isProviders && !isArray { if i == len(node.Children)-1 && !isProviders && !isArray {
@@ -1542,7 +1575,7 @@ func convertConfigJSONToYAML(node *ConfigNode, indent int, isTopLevel bool, pare
return result.String() return result.String()
} }
func formatValue(value interface{}) string { func formatValue(value any) string {
switch v := value.(type) { switch v := value.(type) {
case string: case string:
if intValue, err := strconv.Atoi(v); err == nil { if intValue, err := strconv.Atoi(v); err == nil {
@@ -1584,7 +1617,7 @@ func generateConfigYAMLFromJSON(yamlPath string, commented bool) {
yamlData := convertConfigJSONToYAML(&root, -1, true, "", commented) yamlData := convertConfigJSONToYAML(&root, -1, true, "", commented)
err = os.WriteFile(yamlPath, []byte(yamlData), 0o644) err = os.WriteFile(yamlPath, []byte(yamlData), 0o600)
if err != nil { if err != nil {
fmt.Println("Error writing YAML file:", err) fmt.Println("Error writing YAML file:", err)
return return
@@ -1640,7 +1673,7 @@ func (Dev) PrepareWorktree(name string, planPath string) error {
re2 := regexp.MustCompile(`(?m)^(\s*rootpath:\s*)(/[^\s\n]+)`) re2 := regexp.MustCompile(`(?m)^(\s*rootpath:\s*)(/[^\s\n]+)`)
newConfig = re2.ReplaceAllString(newConfig, `${1}"`+worktreePath+`"`) newConfig = re2.ReplaceAllString(newConfig, `${1}"`+worktreePath+`"`)
if err := os.WriteFile(configDst, []byte(newConfig), 0o644); err != nil { if err := os.WriteFile(configDst, []byte(newConfig), 0o600); err != nil {
return fmt.Errorf("failed to write config.yml: %w", err) return fmt.Errorf("failed to write config.yml: %w", err)
} }
printSuccess("Config copied with updated rootpath!") printSuccess("Config copied with updated rootpath!")
@@ -1724,7 +1757,7 @@ func (Dev) PrepareWorktree(name string, planPath string) error {
// printReleaseStats prints commit statistics for the range between two refs. // printReleaseStats prints commit statistics for the range between two refs.
func printReleaseStats(fromRef, toRef string) error { func printReleaseStats(fromRef, toRef string) error {
output, err := runCmdWithOutput("git", "log", fromRef+".."+toRef, "--oneline") output, err := runGitCommandWithOutput("log", fromRef+".."+toRef, "--oneline")
if err != nil { if err != nil {
return fmt.Errorf("failed to get commit log: %w", err) return fmt.Errorf("failed to get commit log: %w", err)
} }
@@ -1791,7 +1824,7 @@ func (Dev) TagRelease(version string) error {
fmt.Printf("Creating release %s...\n", version) fmt.Printf("Creating release %s...\n", version)
// Get the last tag // Get the last tag
lastTagBytes, err := runCmdWithOutput("git", "describe", "--tags", "--abbrev=0") lastTagBytes, err := runGitCommandWithOutput("describe", "--tags", "--abbrev=0")
if err != nil { if err != nil {
return fmt.Errorf("failed to get last tag: %w", err) return fmt.Errorf("failed to get last tag: %w", err)
} }
@@ -1805,7 +1838,7 @@ func (Dev) TagRelease(version string) error {
// Generate changelog using git cliff // Generate changelog using git cliff
fmt.Println("Generating changelog...") fmt.Println("Generating changelog...")
changelogBytes, err := runCmdWithOutput("git", "cliff", lastTag+"..HEAD", "--tag", version) changelogBytes, err := runGitCommandWithOutput("cliff", lastTag+"..HEAD", "--tag", version)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate changelog: %w", err) return fmt.Errorf("failed to generate changelog: %w", err)
} }
@@ -1887,7 +1920,8 @@ func cleanupChangelog(changelog string) string {
strings.HasPrefix(trimmedLine, "### ") || strings.HasPrefix(trimmedLine, "### ") ||
trimmedLine == "" trimmedLine == ""
if isNewEntry { switch {
case isNewEntry:
// Flush the current entry if any // Flush the current entry if any
if currentEntry.Len() > 0 { if currentEntry.Len() > 0 {
entryStr := strings.TrimSpace(currentEntry.String()) entryStr := strings.TrimSpace(currentEntry.String())
@@ -1899,22 +1933,23 @@ func cleanupChangelog(changelog string) string {
} }
// Start a new entry or add empty line/header // Start a new entry or add empty line/header
if trimmedLine == "" { switch {
case trimmedLine == "":
// Only add empty line if the previous line wasn't empty // Only add empty line if the previous line wasn't empty
if len(cleanedLines) > 0 && cleanedLines[len(cleanedLines)-1] != "" { if len(cleanedLines) > 0 && cleanedLines[len(cleanedLines)-1] != "" {
cleanedLines = append(cleanedLines, "") cleanedLines = append(cleanedLines, "")
} }
} else if strings.HasPrefix(trimmedLine, "## ") || strings.HasPrefix(trimmedLine, "### ") { case strings.HasPrefix(trimmedLine, "## ") || strings.HasPrefix(trimmedLine, "### "):
// Headers are never duplicates // Headers are never duplicates
cleanedLines = append(cleanedLines, trimmedLine) cleanedLines = append(cleanedLines, trimmedLine)
} else { default:
currentEntry.WriteString(trimmedLine) currentEntry.WriteString(trimmedLine)
} }
} else if currentEntry.Len() > 0 { case currentEntry.Len() > 0:
// This is a continuation of the current entry // This is a continuation of the current entry
currentEntry.WriteString(" ") currentEntry.WriteString(" ")
currentEntry.WriteString(trimmedLine) currentEntry.WriteString(trimmedLine)
} else if trimmedLine != "" { case trimmedLine != "":
// Standalone line that's not part of an entry // Standalone line that's not part of an entry
if !seenLines[trimmedLine] { if !seenLines[trimmedLine] {
cleanedLines = append(cleanedLines, trimmedLine) cleanedLines = append(cleanedLines, trimmedLine)
@@ -1949,7 +1984,7 @@ func updateReadmeBadge(version string) error {
re := regexp.MustCompile(`(download-)(v[0-9a-zA-Z.]+)(-brightgreen)`) re := regexp.MustCompile(`(download-)(v[0-9a-zA-Z.]+)(-brightgreen)`)
newContent := re.ReplaceAllString(string(content), "${1}"+badgeVersion+"${3}") newContent := re.ReplaceAllString(string(content), "${1}"+badgeVersion+"${3}")
if err := os.WriteFile(readmePath, []byte(newContent), 0o644); err != nil { if err := os.WriteFile(readmePath, []byte(newContent), 0o600); err != nil {
return fmt.Errorf("failed to write README.md: %w", err) return fmt.Errorf("failed to write README.md: %w", err)
} }
@@ -1970,7 +2005,7 @@ func updateFrontendPackageJSON(version string) error {
re := regexp.MustCompile(`("version"\s*:\s*")([^"]+)(")`) re := regexp.MustCompile(`("version"\s*:\s*")([^"]+)(")`)
newContent := re.ReplaceAllString(string(content), "${1}"+npmVersion+"${3}") newContent := re.ReplaceAllString(string(content), "${1}"+npmVersion+"${3}")
if err := os.WriteFile(pkgPath, []byte(newContent), 0o644); err != nil { if err := os.WriteFile(pkgPath, []byte(newContent), 0o600); err != nil {
return fmt.Errorf("failed to write %s: %w", pkgPath, err) return fmt.Errorf("failed to write %s: %w", pkgPath, err)
} }
@@ -2005,7 +2040,7 @@ func prependChangelog(newChangelog string) error {
strings.TrimSpace(newChangelog) + "\n" + strings.TrimSpace(newChangelog) + "\n" +
existingVersions existingVersions
if err := os.WriteFile(changelogPath, []byte(newContent), 0o644); err != nil { if err := os.WriteFile(changelogPath, []byte(newContent), 0o600); err != nil {
return fmt.Errorf("failed to write CHANGELOG.md: %w", err) return fmt.Errorf("failed to write CHANGELOG.md: %w", err)
} }
@@ -2019,11 +2054,12 @@ func prepareTagMessage(changelog string) string {
for _, line := range lines { for _, line := range lines {
// Remove ## and ### prefixes // Remove ## and ### prefixes
if strings.HasPrefix(line, "### ") { switch {
case strings.HasPrefix(line, "### "):
result = append(result, strings.TrimPrefix(line, "### ")) result = append(result, strings.TrimPrefix(line, "### "))
} else if strings.HasPrefix(line, "## ") { case strings.HasPrefix(line, "## "):
result = append(result, strings.TrimPrefix(line, "## ")) result = append(result, strings.TrimPrefix(line, "## "))
} else { default:
result = append(result, line) result = append(result, line)
} }
} }
@@ -2044,7 +2080,7 @@ func (Plugins) Build(pathToSourceFiles string) error {
if !strings.HasPrefix(pathToSourceFiles, "/") { if !strings.HasPrefix(pathToSourceFiles, "/") {
absPath, err := filepath.Abs(pathToSourceFiles) absPath, err := filepath.Abs(pathToSourceFiles)
if err != nil { if err != nil {
return fmt.Errorf("failed to resolve absolute path: %v", err) return fmt.Errorf("failed to resolve absolute path: %w", err)
} }
pathToSourceFiles = absPath pathToSourceFiles = absPath
} }