chore: Go 1.26 modernization #2

Merged
Charon merged 7 commits from chore/go-1.26-modernization into main 2026-02-24 18:01:41 +00:00
66 changed files with 267 additions and 245 deletions

View file

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"regexp"
"slices"
"strings"
"sync"
"text/template"
@ -920,20 +921,16 @@ func (e *Executor) matchesTags(taskTags []string) bool {
// Check skip tags
for _, skip := range e.SkipTags {
for _, tt := range taskTags {
if skip == tt {
return false
}
if slices.Contains(taskTags, skip) {
return false
}
}
// Check include tags
if len(e.Tags) > 0 {
for _, tag := range e.Tags {
for _, tt := range taskTags {
if tag == tt || tag == "all" {
return true
}
if tag == "all" || slices.Contains(taskTags, tag) {
return true
}
}
return false

View file

@ -3,6 +3,7 @@ package ansible
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
@ -178,7 +179,7 @@ func (e *Executor) moduleShell(ctx context.Context, client *SSHClient, args map[
cmd = getStringArg(args, "cmd", "")
}
if cmd == "" {
return nil, fmt.Errorf("shell: no command specified")
return nil, errors.New("shell: no command specified")
}
// Handle chdir
@ -206,7 +207,7 @@ func (e *Executor) moduleCommand(ctx context.Context, client *SSHClient, args ma
cmd = getStringArg(args, "cmd", "")
}
if cmd == "" {
return nil, fmt.Errorf("command: no command specified")
return nil, errors.New("command: no command specified")
}
// Handle chdir
@ -231,7 +232,7 @@ func (e *Executor) moduleCommand(ctx context.Context, client *SSHClient, args ma
func (e *Executor) moduleRaw(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
cmd := getStringArg(args, "_raw_params", "")
if cmd == "" {
return nil, fmt.Errorf("raw: no command specified")
return nil, errors.New("raw: no command specified")
}
stdout, stderr, rc, err := client.Run(ctx, cmd)
@ -250,7 +251,7 @@ func (e *Executor) moduleRaw(ctx context.Context, client *SSHClient, args map[st
func (e *Executor) moduleScript(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
script := getStringArg(args, "_raw_params", "")
if script == "" {
return nil, fmt.Errorf("script: no script specified")
return nil, errors.New("script: no script specified")
}
// Read local script
@ -278,7 +279,7 @@ func (e *Executor) moduleScript(ctx context.Context, client *SSHClient, args map
func (e *Executor) moduleCopy(ctx context.Context, client *SSHClient, args map[string]any, host string, task *Task) (*TaskResult, error) {
dest := getStringArg(args, "dest", "")
if dest == "" {
return nil, fmt.Errorf("copy: dest required")
return nil, errors.New("copy: dest required")
}
var content []byte
@ -292,7 +293,7 @@ func (e *Executor) moduleCopy(ctx context.Context, client *SSHClient, args map[s
} else if c := getStringArg(args, "content", ""); c != "" {
content = []byte(c)
} else {
return nil, fmt.Errorf("copy: src or content required")
return nil, errors.New("copy: src or content required")
}
mode := os.FileMode(0644)
@ -322,7 +323,7 @@ func (e *Executor) moduleTemplate(ctx context.Context, client *SSHClient, args m
src := getStringArg(args, "src", "")
dest := getStringArg(args, "dest", "")
if src == "" || dest == "" {
return nil, fmt.Errorf("template: src and dest required")
return nil, errors.New("template: src and dest required")
}
// Process template
@ -352,7 +353,7 @@ func (e *Executor) moduleFile(ctx context.Context, client *SSHClient, args map[s
path = getStringArg(args, "dest", "")
}
if path == "" {
return nil, fmt.Errorf("file: path required")
return nil, errors.New("file: path required")
}
state := getStringArg(args, "state", "file")
@ -383,7 +384,7 @@ func (e *Executor) moduleFile(ctx context.Context, client *SSHClient, args map[s
case "link":
src := getStringArg(args, "src", "")
if src == "" {
return nil, fmt.Errorf("file: src required for link state")
return nil, errors.New("file: src required for link state")
}
cmd := fmt.Sprintf("ln -sf %q %q", src, path)
_, stderr, rc, err := client.Run(ctx, cmd)
@ -420,7 +421,7 @@ func (e *Executor) moduleLineinfile(ctx context.Context, client *SSHClient, args
path = getStringArg(args, "dest", "")
}
if path == "" {
return nil, fmt.Errorf("lineinfile: path required")
return nil, errors.New("lineinfile: path required")
}
line := getStringArg(args, "line", "")
@ -460,7 +461,7 @@ func (e *Executor) moduleLineinfile(ctx context.Context, client *SSHClient, args
func (e *Executor) moduleStat(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
path := getStringArg(args, "path", "")
if path == "" {
return nil, fmt.Errorf("stat: path required")
return nil, errors.New("stat: path required")
}
stat, err := client.Stat(ctx, path)
@ -480,7 +481,7 @@ func (e *Executor) moduleSlurp(ctx context.Context, client *SSHClient, args map[
path = getStringArg(args, "src", "")
}
if path == "" {
return nil, fmt.Errorf("slurp: path required")
return nil, errors.New("slurp: path required")
}
content, err := client.Download(ctx, path)
@ -500,7 +501,7 @@ func (e *Executor) moduleFetch(ctx context.Context, client *SSHClient, args map[
src := getStringArg(args, "src", "")
dest := getStringArg(args, "dest", "")
if src == "" || dest == "" {
return nil, fmt.Errorf("fetch: src and dest required")
return nil, errors.New("fetch: src and dest required")
}
content, err := client.Download(ctx, src)
@ -524,7 +525,7 @@ func (e *Executor) moduleGetURL(ctx context.Context, client *SSHClient, args map
url := getStringArg(args, "url", "")
dest := getStringArg(args, "dest", "")
if url == "" || dest == "" {
return nil, fmt.Errorf("get_url: url and dest required")
return nil, errors.New("get_url: url and dest required")
}
// Use curl or wget
@ -591,7 +592,7 @@ func (e *Executor) moduleAptKey(ctx context.Context, client *SSHClient, args map
}
if url == "" {
return nil, fmt.Errorf("apt_key: url required")
return nil, errors.New("apt_key: url required")
}
var cmd string
@ -615,7 +616,7 @@ func (e *Executor) moduleAptRepository(ctx context.Context, client *SSHClient, a
state := getStringArg(args, "state", "present")
if repo == "" {
return nil, fmt.Errorf("apt_repository: repo required")
return nil, errors.New("apt_repository: repo required")
}
if filename == "" {
@ -690,7 +691,7 @@ func (e *Executor) moduleService(ctx context.Context, client *SSHClient, args ma
enabled := args["enabled"]
if name == "" {
return nil, fmt.Errorf("service: name required")
return nil, errors.New("service: name required")
}
var cmds []string
@ -742,7 +743,7 @@ func (e *Executor) moduleUser(ctx context.Context, client *SSHClient, args map[s
state := getStringArg(args, "state", "present")
if name == "" {
return nil, fmt.Errorf("user: name required")
return nil, errors.New("user: name required")
}
if state == "absent" {
@ -799,7 +800,7 @@ func (e *Executor) moduleGroup(ctx context.Context, client *SSHClient, args map[
state := getStringArg(args, "state", "present")
if name == "" {
return nil, fmt.Errorf("group: name required")
return nil, errors.New("group: name required")
}
if state == "absent" {
@ -834,7 +835,7 @@ func (e *Executor) moduleURI(ctx context.Context, client *SSHClient, args map[st
method := getStringArg(args, "method", "GET")
if url == "" {
return nil, fmt.Errorf("uri: url required")
return nil, errors.New("uri: url required")
}
var curlOpts []string
@ -912,7 +913,7 @@ func (e *Executor) moduleFail(args map[string]any) (*TaskResult, error) {
func (e *Executor) moduleAssert(args map[string]any, host string) (*TaskResult, error) {
that, ok := args["that"]
if !ok {
return nil, fmt.Errorf("assert: 'that' required")
return nil, errors.New("assert: 'that' required")
}
conditions := normalizeConditions(that)
@ -1013,7 +1014,7 @@ func (e *Executor) moduleGit(ctx context.Context, client *SSHClient, args map[st
version := getStringArg(args, "version", "HEAD")
if repo == "" || dest == "" {
return nil, fmt.Errorf("git: repo and dest required")
return nil, errors.New("git: repo and dest required")
}
// Check if dest exists
@ -1042,7 +1043,7 @@ func (e *Executor) moduleUnarchive(ctx context.Context, client *SSHClient, args
remote := getBoolArg(args, "remote_src", false)
if src == "" || dest == "" {
return nil, fmt.Errorf("unarchive: src and dest required")
return nil, errors.New("unarchive: src and dest required")
}
// Create dest directory (best-effort)
@ -1117,7 +1118,7 @@ func getBoolArg(args map[string]any, key string, def bool) bool {
func (e *Executor) moduleHostname(ctx context.Context, client *SSHClient, args map[string]any) (*TaskResult, error) {
name := getStringArg(args, "name", "")
if name == "" {
return nil, fmt.Errorf("hostname: name required")
return nil, errors.New("hostname: name required")
}
// Set hostname
@ -1139,7 +1140,7 @@ func (e *Executor) moduleSysctl(ctx context.Context, client *SSHClient, args map
state := getStringArg(args, "state", "present")
if name == "" {
return nil, fmt.Errorf("sysctl: name required")
return nil, errors.New("sysctl: name required")
}
if state == "absent" {
@ -1209,7 +1210,7 @@ func (e *Executor) moduleBlockinfile(ctx context.Context, client *SSHClient, arg
path = getStringArg(args, "dest", "")
}
if path == "" {
return nil, fmt.Errorf("blockinfile: path required")
return nil, errors.New("blockinfile: path required")
}
block := getStringArg(args, "block", "")
@ -1357,7 +1358,7 @@ func (e *Executor) moduleAuthorizedKey(ctx context.Context, client *SSHClient, a
state := getStringArg(args, "state", "present")
if user == "" || key == "" {
return nil, fmt.Errorf("authorized_key: user and key required")
return nil, errors.New("authorized_key: user and key required")
}
// Get user's home directory
@ -1407,7 +1408,7 @@ func (e *Executor) moduleDockerCompose(ctx context.Context, client *SSHClient, a
state := getStringArg(args, "state", "present")
if projectSrc == "" {
return nil, fmt.Errorf("docker_compose: project_src required")
return nil, errors.New("docker_compose: project_src required")
}
var cmd string

View file

@ -6,13 +6,14 @@ import (
"archive/zip"
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/Snider/Borg/pkg/compress"
io_interface "forge.lthn.ai/core/go/pkg/io"
"github.com/Snider/Borg/pkg/compress"
)
// ArchiveFormat specifies the compression format for archives.
@ -48,7 +49,7 @@ func ArchiveXZ(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
// Returns a new Artifact with Path pointing to the archive.
func ArchiveWithFormat(fs io_interface.Medium, artifact Artifact, format ArchiveFormat) (Artifact, error) {
if artifact.Path == "" {
return Artifact{}, fmt.Errorf("build.Archive: artifact path is empty")
return Artifact{}, errors.New("build.Archive: artifact path is empty")
}
// Verify the source file exists
@ -57,7 +58,7 @@ func ArchiveWithFormat(fs io_interface.Medium, artifact Artifact, format Archive
return Artifact{}, fmt.Errorf("build.Archive: source file not found: %w", err)
}
if info.IsDir() {
return Artifact{}, fmt.Errorf("build.Archive: source path is a directory, expected file")
return Artifact{}, errors.New("build.Archive: source path is a directory, expected file")
}
// Determine archive type based on OS and format

View file

@ -48,7 +48,7 @@ func runProjectBuild(ctx context.Context, buildType string, ciMode bool, targets
return fmt.Errorf("%s: %w", i18n.T("common.error.failed", map[string]any{"Action": "detect project type"}), err)
}
if projectType == "" {
return fmt.Errorf("%s", i18n.T("cmd.build.error.no_project_type", map[string]interface{}{"Dir": projectDir}))
return fmt.Errorf("%s", i18n.T("cmd.build.error.no_project_type", map[string]any{"Dir": projectDir}))
}
}
@ -139,7 +139,7 @@ func runProjectBuild(ctx context.Context, buildType string, ciMode bool, targets
}
if verbose && !ciMode {
fmt.Printf("%s %s\n", buildSuccessStyle.Render(i18n.T("common.label.success")), i18n.T("cmd.build.built_artifacts", map[string]interface{}{"Count": len(artifacts)}))
fmt.Printf("%s %s\n", buildSuccessStyle.Render(i18n.T("common.label.success")), i18n.T("cmd.build.built_artifacts", map[string]any{"Count": len(artifacts)}))
fmt.Println()
for _, artifact := range artifacts {
relPath, err := filepath.Rel(projectDir, artifact.Path)
@ -260,7 +260,7 @@ func runProjectBuild(ctx context.Context, buildType string, ciMode bool, targets
// Minimal output: just success with artifact count
fmt.Printf("%s %s %s\n",
buildSuccessStyle.Render(i18n.T("common.label.success")),
i18n.T("cmd.build.built_artifacts", map[string]interface{}{"Count": len(artifacts)}),
i18n.T("cmd.build.built_artifacts", map[string]any{"Count": len(artifacts)}),
buildDimStyle.Render(fmt.Sprintf("(%s)", outputDir)),
)
}
@ -342,7 +342,7 @@ func parseTargets(targetsFlag string) ([]build.Target, error) {
osArch := strings.Split(part, "/")
if len(osArch) != 2 {
return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.invalid_target", map[string]interface{}{"Target": part}))
return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.invalid_target", map[string]any{"Target": part}))
}
targets = append(targets, build.Target{

View file

@ -147,14 +147,14 @@ func findManifestURL(htmlContent, baseURL string) (string, error) {
}
// fetchManifest downloads and parses a PWA manifest.
func fetchManifest(manifestURL string) (map[string]interface{}, error) {
func fetchManifest(manifestURL string) (map[string]any, error) {
resp, err := http.Get(manifestURL)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
var manifest map[string]interface{}
var manifest map[string]any
if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
return nil, err
}
@ -162,7 +162,7 @@ func fetchManifest(manifestURL string) (map[string]interface{}, error) {
}
// collectAssets extracts asset URLs from a PWA manifest.
func collectAssets(manifest map[string]interface{}, manifestURL string) []string {
func collectAssets(manifest map[string]any, manifestURL string) []string {
var assets []string
base, _ := url.Parse(manifestURL)
@ -174,9 +174,9 @@ func collectAssets(manifest map[string]interface{}, manifestURL string) []string
}
// Add icons
if icons, ok := manifest["icons"].([]interface{}); ok {
if icons, ok := manifest["icons"].([]any); ok {
for _, icon := range icons {
if iconMap, ok := icon.(map[string]interface{}); ok {
if iconMap, ok := icon.(map[string]any); ok {
if src, ok := iconMap["src"].(string); ok {
if resolved, err := base.Parse(src); err == nil {
assets = append(assets, resolved.String())

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -38,7 +39,7 @@ func (b *CPPBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// Cross-compilation is handled via Conan profiles specified in .core/build.yaml.
func (b *CPPBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, fmt.Errorf("builders.CPPBuilder.Build: config is nil")
return nil, errors.New("builders.CPPBuilder.Build: config is nil")
}
// Validate make is available
@ -244,7 +245,7 @@ func (b *CPPBuilder) targetToProfile(target build.Target) string {
// validateMake checks if make is available.
func (b *CPPBuilder) validateMake() error {
if _, err := exec.LookPath("make"); err != nil {
return fmt.Errorf("cpp: make not found. Install build-essential (Linux) or Xcode Command Line Tools (macOS)")
return errors.New("cpp: make not found. Install build-essential (Linux) or Xcode Command Line Tools (macOS)")
}
return nil
}

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -186,7 +187,7 @@ func (b *DockerBuilder) Build(ctx context.Context, cfg *build.Config, targets []
func (b *DockerBuilder) validateDockerCli() error {
cmd := exec.Command("docker", "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("docker: docker CLI not found. Install it from https://docs.docker.com/get-docker/")
return errors.New("docker: docker CLI not found. Install it from https://docs.docker.com/get-docker/")
}
return nil
}
@ -196,7 +197,7 @@ func (b *DockerBuilder) ensureBuildx(ctx context.Context) error {
// Check if buildx is available
cmd := exec.CommandContext(ctx, "docker", "buildx", "version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("docker: buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/")
return errors.New("docker: buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/")
}
// Check if we have a builder, create one if not

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -37,11 +38,11 @@ func (b *GoBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// applies ldflags and trimpath, and runs go build.
func (b *GoBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, fmt.Errorf("builders.GoBuilder.Build: config is nil")
return nil, errors.New("builders.GoBuilder.Build: config is nil")
}
if len(targets) == 0 {
return nil, fmt.Errorf("builders.GoBuilder.Build: no targets specified")
return nil, errors.New("builders.GoBuilder.Build: no targets specified")
}
// Ensure output directory exists

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -78,7 +79,7 @@ func (b *LinuxKitBuilder) Build(ctx context.Context, cfg *build.Config, targets
}
if configPath == "" {
return nil, fmt.Errorf("linuxkit.Build: no LinuxKit config file found. Specify with --config or create linuxkit.yml")
return nil, errors.New("linuxkit.Build: no LinuxKit config file found. Specify with --config or create linuxkit.yml")
}
// Validate config file exists
@ -266,5 +267,5 @@ func (b *LinuxKitBuilder) validateLinuxKitCli() error {
}
}
return fmt.Errorf("linuxkit: linuxkit CLI not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit")
return errors.New("linuxkit: linuxkit CLI not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit")
}

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -271,5 +272,5 @@ func (b *TaskfileBuilder) validateTaskCli() error {
}
}
return fmt.Errorf("taskfile: task CLI not found. Install with: brew install go-task (macOS), go install github.com/go-task/task/v3/cmd/task@latest, or see https://taskfile.dev/installation/")
return errors.New("taskfile: task CLI not found. Install with: brew install go-task (macOS), go install github.com/go-task/task/v3/cmd/task@latest, or see https://taskfile.dev/installation/")
}

View file

@ -3,6 +3,7 @@ package builders
import (
"context"
"errors"
"fmt"
"os/exec"
"path/filepath"
@ -37,11 +38,11 @@ func (b *WailsBuilder) Detect(fs io.Medium, dir string) (bool, error) {
// - Wails v2: Uses 'wails build' command
func (b *WailsBuilder) Build(ctx context.Context, cfg *build.Config, targets []build.Target) ([]build.Artifact, error) {
if cfg == nil {
return nil, fmt.Errorf("builders.WailsBuilder.Build: config is nil")
return nil, errors.New("builders.WailsBuilder.Build: config is nil")
}
if len(targets) == 0 {
return nil, fmt.Errorf("builders.WailsBuilder.Build: no targets specified")
return nil, errors.New("builders.WailsBuilder.Build: no targets specified")
}
// Detect Wails version
@ -53,7 +54,7 @@ func (b *WailsBuilder) Build(ctx context.Context, cfg *build.Config, targets []b
if detected, _ := taskBuilder.Detect(cfg.FS, cfg.ProjectDir); detected {
return taskBuilder.Build(ctx, cfg, targets)
}
return nil, fmt.Errorf("wails v3 projects require a Taskfile for building")
return nil, errors.New("wails v3 projects require a Taskfile for building")
}
// Wails v2 strategy: Use 'wails build'

View file

@ -4,19 +4,21 @@ package build
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"path/filepath"
"slices"
"strings"
io_interface "forge.lthn.ai/core/go/pkg/io"
"sort"
"strings"
)
// Checksum computes SHA256 for an artifact and returns the artifact with the Checksum field filled.
func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
if artifact.Path == "" {
return Artifact{}, fmt.Errorf("build.Checksum: artifact path is empty")
return Artifact{}, errors.New("build.Checksum: artifact path is empty")
}
// Open the file
@ -84,7 +86,7 @@ func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string
}
// Sort lines for consistent output
sort.Strings(lines)
slices.Sort(lines)
content := strings.Join(lines, "\n") + "\n"

View file

@ -2,6 +2,7 @@ package signing
import (
"context"
"errors"
"fmt"
"os/exec"
"runtime"
@ -42,7 +43,7 @@ func (s *MacOSSigner) Available() bool {
// Sign codesigns a binary with hardened runtime.
func (s *MacOSSigner) Sign(ctx context.Context, fs io.Medium, binary string) error {
if !s.Available() {
return fmt.Errorf("codesign.Sign: codesign not available")
return errors.New("codesign.Sign: codesign not available")
}
cmd := exec.CommandContext(ctx, "codesign",
@ -65,7 +66,7 @@ func (s *MacOSSigner) Sign(ctx context.Context, fs io.Medium, binary string) err
// This blocks until Apple responds (typically 1-5 minutes).
func (s *MacOSSigner) Notarize(ctx context.Context, fs io.Medium, binary string) error {
if s.config.AppleID == "" || s.config.TeamID == "" || s.config.AppPassword == "" {
return fmt.Errorf("codesign.Notarize: missing Apple credentials (apple_id, team_id, app_password)")
return errors.New("codesign.Notarize: missing Apple credentials (apple_id, team_id, app_password)")
}
// Create ZIP for submission

View file

@ -2,6 +2,7 @@ package signing
import (
"context"
"errors"
"fmt"
"os/exec"
@ -39,7 +40,7 @@ func (s *GPGSigner) Available() bool {
// For file.txt, creates file.txt.asc
func (s *GPGSigner) Sign(ctx context.Context, fs io.Medium, file string) error {
if !s.Available() {
return fmt.Errorf("gpg.Sign: gpg not available or key not configured")
return errors.New("gpg.Sign: gpg not available or key not configured")
}
cmd := exec.CommandContext(ctx, "gpg",

View file

@ -2,6 +2,7 @@ package signing
import (
"context"
"errors"
"fmt"
"runtime"
@ -59,7 +60,7 @@ func NotarizeBinaries(ctx context.Context, fs io.Medium, cfg SignConfig, artifac
signer := NewMacOSSigner(cfg.MacOS)
if !signer.Available() {
return fmt.Errorf("notarization requested but codesign not available")
return errors.New("notarization requested but codesign not available")
}
for _, artifact := range artifacts {

View file

@ -203,19 +203,19 @@ func runApply() error {
cli.Blank()
cli.Print("%s: ", i18n.T("cmd.dev.apply.summary"))
if succeeded > 0 {
cli.Print("%s", successStyle.Render(i18n.T("common.count.succeeded", map[string]interface{}{"Count": succeeded})))
cli.Print("%s", successStyle.Render(i18n.T("common.count.succeeded", map[string]any{"Count": succeeded})))
}
if skipped > 0 {
if succeeded > 0 {
cli.Print(", ")
}
cli.Print("%s", dimStyle.Render(i18n.T("common.count.skipped", map[string]interface{}{"Count": skipped})))
cli.Print("%s", dimStyle.Render(i18n.T("common.count.skipped", map[string]any{"Count": skipped})))
}
if failed > 0 {
if succeeded > 0 || skipped > 0 {
cli.Print(", ")
}
cli.Print("%s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
cli.Print("%s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed})))
}
cli.Blank()

View file

@ -146,18 +146,18 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
// Print summary
cli.Blank()
cli.Print("%s", i18n.T("cmd.dev.ci.repos_checked", map[string]interface{}{"Count": len(repoList)}))
cli.Print("%s", i18n.T("cmd.dev.ci.repos_checked", map[string]any{"Count": len(repoList)}))
if success > 0 {
cli.Print(" * %s", ciSuccessStyle.Render(i18n.T("cmd.dev.ci.passing", map[string]interface{}{"Count": success})))
cli.Print(" * %s", ciSuccessStyle.Render(i18n.T("cmd.dev.ci.passing", map[string]any{"Count": success})))
}
if failed > 0 {
cli.Print(" * %s", ciFailureStyle.Render(i18n.T("cmd.dev.ci.failing", map[string]interface{}{"Count": failed})))
cli.Print(" * %s", ciFailureStyle.Render(i18n.T("cmd.dev.ci.failing", map[string]any{"Count": failed})))
}
if pending > 0 {
cli.Print(" * %s", ciPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
cli.Print(" * %s", ciPendingStyle.Render(i18n.T("common.count.pending", map[string]any{"Count": pending})))
}
if len(noCI) > 0 {
cli.Print(" * %s", ciSkippedStyle.Render(i18n.T("cmd.dev.ci.no_ci", map[string]interface{}{"Count": len(noCI)})))
cli.Print(" * %s", ciSkippedStyle.Render(i18n.T("cmd.dev.ci.no_ci", map[string]any{"Count": len(noCI)})))
}
cli.Blank()
cli.Blank()

View file

@ -86,17 +86,17 @@ func runCommit(registryPath string, all bool) error {
}
// Show dirty repos
cli.Print("\n%s\n\n", i18n.T("cmd.dev.repos_with_changes", map[string]interface{}{"Count": len(dirtyRepos)}))
cli.Print("\n%s\n\n", i18n.T("cmd.dev.repos_with_changes", map[string]any{"Count": len(dirtyRepos)}))
for _, s := range dirtyRepos {
cli.Print(" %s: ", repoNameStyle.Render(s.Name))
if s.Modified > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]any{"Count": s.Modified})))
}
if s.Untracked > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]any{"Count": s.Untracked})))
}
if s.Staged > 0 {
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]any{"Count": s.Staged})))
}
cli.Blank()
}
@ -128,9 +128,9 @@ func runCommit(registryPath string, all bool) error {
}
// Summary
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.done_succeeded", map[string]interface{}{"Count": succeeded})))
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.done_succeeded", map[string]any{"Count": succeeded})))
if failed > 0 {
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed})))
}
cli.Blank()
@ -170,13 +170,13 @@ func runCommitSingleRepo(ctx context.Context, repoPath string, all bool) error {
// Show status
cli.Print("%s: ", repoNameStyle.Render(s.Name))
if s.Modified > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]any{"Count": s.Modified})))
}
if s.Untracked > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]any{"Count": s.Untracked})))
}
if s.Staged > 0 {
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]any{"Count": s.Staged})))
}
cli.Blank()

View file

@ -82,7 +82,7 @@ func runFileSync(source string) error {
// Let's stick to os.Stat for source properties finding as typically allowed for CLI args.
if err != nil {
return log.E("dev.sync", i18n.T("cmd.dev.file_sync.error.source_not_found", map[string]interface{}{"Path": source}), err)
return log.E("dev.sync", i18n.T("cmd.dev.file_sync.error.source_not_found", map[string]any{"Path": source}), err)
}
// Find target repos
@ -185,19 +185,19 @@ func runFileSync(source string) error {
cli.Blank()
cli.Print("%s: ", i18n.T("cmd.dev.file_sync.summary"))
if succeeded > 0 {
cli.Print("%s", successStyle.Render(i18n.T("common.count.succeeded", map[string]interface{}{"Count": succeeded})))
cli.Print("%s", successStyle.Render(i18n.T("common.count.succeeded", map[string]any{"Count": succeeded})))
}
if skipped > 0 {
if succeeded > 0 {
cli.Print(", ")
}
cli.Print("%s", dimStyle.Render(i18n.T("common.count.skipped", map[string]interface{}{"Count": skipped})))
cli.Print("%s", dimStyle.Render(i18n.T("common.count.skipped", map[string]any{"Count": skipped})))
}
if failed > 0 {
if succeeded > 0 || skipped > 0 {
cli.Print(", ")
}
cli.Print("%s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
cli.Print("%s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed})))
}
cli.Blank()

View file

@ -159,7 +159,7 @@ func formatRepoList(reposList []string) string {
if len(reposList) <= 5 {
return joinRepos(reposList)
}
return joinRepos(reposList[:5]) + " " + i18n.T("cmd.dev.health.more", map[string]interface{}{"Count": len(reposList) - 5})
return joinRepos(reposList[:5]) + " " + i18n.T("cmd.dev.health.more", map[string]any{"Count": len(reposList) - 5})
}
func joinRepos(reposList []string) string {

View file

@ -2,7 +2,7 @@ package dev
import (
"errors"
"sort"
"slices"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
@ -62,7 +62,7 @@ func runImpact(registryPath string, repoName string) error {
// Check repo exists
repo, exists := reg.Get(repoName)
if !exists {
return errors.New(i18n.T("error.repo_not_found", map[string]interface{}{"Name": repoName}))
return errors.New(i18n.T("error.repo_not_found", map[string]any{"Name": repoName}))
}
// Build reverse dependency graph
@ -86,8 +86,8 @@ func runImpact(registryPath string, repoName string) error {
}
// Sort for consistent output
sort.Strings(direct)
sort.Strings(indirect)
slices.Sort(direct)
slices.Sort(indirect)
// Print results
cli.Blank()
@ -98,7 +98,7 @@ func runImpact(registryPath string, repoName string) error {
cli.Blank()
if len(allAffected) == 0 {
cli.Print("%s %s\n", impactSafeStyle.Render("v"), i18n.T("cmd.dev.impact.no_dependents", map[string]interface{}{"Name": repoName}))
cli.Print("%s %s\n", impactSafeStyle.Render("v"), i18n.T("cmd.dev.impact.no_dependents", map[string]any{"Name": repoName}))
return nil
}
@ -106,7 +106,7 @@ func runImpact(registryPath string, repoName string) error {
if len(direct) > 0 {
cli.Print("%s %s\n",
impactDirectStyle.Render("*"),
i18n.T("cmd.dev.impact.direct_dependents", map[string]interface{}{"Count": len(direct)}),
i18n.T("cmd.dev.impact.direct_dependents", map[string]any{"Count": len(direct)}),
)
for _, d := range direct {
r, _ := reg.Get(d)
@ -123,7 +123,7 @@ func runImpact(registryPath string, repoName string) error {
if len(indirect) > 0 {
cli.Print("%s %s\n",
impactIndirectStyle.Render("o"),
i18n.T("cmd.dev.impact.transitive_dependents", map[string]interface{}{"Count": len(indirect)}),
i18n.T("cmd.dev.impact.transitive_dependents", map[string]any{"Count": len(indirect)}),
)
for _, d := range indirect {
r, _ := reg.Get(d)
@ -139,7 +139,7 @@ func runImpact(registryPath string, repoName string) error {
// Summary
cli.Print("%s %s\n",
dimStyle.Render(i18n.Label("summary")),
i18n.T("cmd.dev.impact.changes_affect", map[string]interface{}{
i18n.T("cmd.dev.impact.changes_affect", map[string]any{
"Repo": repoNameStyle.Render(repoName),
"Affected": len(allAffected),
"Total": len(reg.Repos) - 1,

View file

@ -117,7 +117,7 @@ func runIssues(registryPath string, limit int, assignee string) error {
return nil
}
cli.Print("\n%s\n\n", i18n.T("cmd.dev.issues.open_issues", map[string]interface{}{"Count": len(allIssues)}))
cli.Print("\n%s\n\n", i18n.T("cmd.dev.issues.open_issues", map[string]any{"Count": len(allIssues)}))
for _, issue := range allIssues {
printIssue(issue)

View file

@ -81,13 +81,13 @@ func runPull(registryPath string, all bool) error {
// Show what we're pulling
if all {
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.pulling_repos", map[string]interface{}{"Count": len(toPull)}))
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.pulling_repos", map[string]any{"Count": len(toPull)}))
} else {
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.repos_behind", map[string]interface{}{"Count": len(toPull)}))
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.repos_behind", map[string]any{"Count": len(toPull)}))
for _, s := range toPull {
cli.Print(" %s: %s\n",
repoNameStyle.Render(s.Name),
dimStyle.Render(i18n.T("cmd.dev.pull.commits_behind", map[string]interface{}{"Count": s.Behind})),
dimStyle.Render(i18n.T("cmd.dev.pull.commits_behind", map[string]any{"Count": s.Behind})),
)
}
cli.Blank()
@ -110,9 +110,9 @@ func runPull(registryPath string, all bool) error {
// Summary
cli.Blank()
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.pull.done_pulled", map[string]interface{}{"Count": succeeded})))
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.pull.done_pulled", map[string]any{"Count": succeeded})))
if failed > 0 {
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed})))
}
cli.Blank()

View file

@ -84,12 +84,12 @@ func runPush(registryPath string, force bool) error {
}
// Show repos to push
cli.Print("\n%s\n\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
cli.Print("\n%s\n\n", i18n.T("common.count.repos_unpushed", map[string]any{"Count": len(aheadRepos)}))
totalCommits := 0
for _, s := range aheadRepos {
cli.Print(" %s: %s\n",
repoNameStyle.Render(s.Name),
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})),
aheadStyle.Render(i18n.T("common.count.commits", map[string]any{"Count": s.Ahead})),
)
totalCommits += s.Ahead
}
@ -97,7 +97,7 @@ func runPush(registryPath string, force bool) error {
// Confirm unless --force
if !force {
cli.Blank()
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) {
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]any{"Commits": totalCommits, "Repos": len(aheadRepos)})) {
cli.Text(i18n.T("cli.aborted"))
return nil
}
@ -158,9 +158,9 @@ func runPush(registryPath string, force bool) error {
// Summary
cli.Blank()
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.push.done_pushed", map[string]interface{}{"Count": succeeded})))
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.push.done_pushed", map[string]any{"Count": succeeded})))
if failed > 0 {
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]any{"Count": failed})))
}
cli.Blank()
@ -191,13 +191,13 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error {
if s.IsDirty() {
cli.Print("%s: ", repoNameStyle.Render(s.Name))
if s.Modified > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]any{"Count": s.Modified})))
}
if s.Untracked > 0 {
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]any{"Count": s.Untracked})))
}
if s.Staged > 0 {
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]any{"Count": s.Staged})))
}
cli.Blank()
cli.Blank()
@ -230,12 +230,12 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error {
// Show commits to push
cli.Print("%s: %s\n", repoNameStyle.Render(s.Name),
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})))
aheadStyle.Render(i18n.T("common.count.commits", map[string]any{"Count": s.Ahead})))
// Confirm unless --force
if !force {
cli.Blank()
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": s.Ahead, "Repos": 1})) {
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]any{"Commits": s.Ahead, "Repos": 1})) {
cli.Text(i18n.T("cli.aborted"))
return nil
}

View file

@ -144,15 +144,15 @@ func runReviews(registryPath string, author string, showAll bool) error {
}
cli.Blank()
cli.Print("%s", i18n.T("cmd.dev.reviews.open_prs", map[string]interface{}{"Count": len(allPRs)}))
cli.Print("%s", i18n.T("cmd.dev.reviews.open_prs", map[string]any{"Count": len(allPRs)}))
if pending > 0 {
cli.Print(" * %s", prPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
cli.Print(" * %s", prPendingStyle.Render(i18n.T("common.count.pending", map[string]any{"Count": pending})))
}
if approved > 0 {
cli.Print(" * %s", prApprovedStyle.Render(i18n.T("cmd.dev.reviews.approved", map[string]interface{}{"Count": approved})))
cli.Print(" * %s", prApprovedStyle.Render(i18n.T("cmd.dev.reviews.approved", map[string]any{"Count": approved})))
}
if changesRequested > 0 {
cli.Print(" * %s", prChangesStyle.Render(i18n.T("cmd.dev.reviews.changes_requested", map[string]interface{}{"Count": changesRequested})))
cli.Print(" * %s", prChangesStyle.Render(i18n.T("cmd.dev.reviews.changes_requested", map[string]any{"Count": changesRequested})))
}
cli.Blank()
cli.Blank()

View file

@ -49,7 +49,7 @@ func runVMInstall() error {
if d.IsInstalled() {
cli.Text(successStyle.Render(i18n.T("cmd.dev.vm.already_installed")))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.check_updates", map[string]interface{}{"Command": dimStyle.Render("core dev update")}))
cli.Text(i18n.T("cmd.dev.vm.check_updates", map[string]any{"Command": dimStyle.Render("core dev update")}))
return nil
}
@ -80,9 +80,9 @@ func runVMInstall() error {
elapsed := time.Since(start).Round(time.Second)
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.installed_in", map[string]interface{}{"Duration": elapsed}))
cli.Text(i18n.T("cmd.dev.vm.installed_in", map[string]any{"Duration": elapsed}))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]any{"Command": dimStyle.Render("core dev boot")}))
return nil
}
@ -131,7 +131,7 @@ func runVMBoot(memory, cpus int, fresh bool) error {
}
opts.Fresh = fresh
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.config_label")), i18n.T("cmd.dev.vm.config_value", map[string]interface{}{"Memory": opts.Memory, "CPUs": opts.CPUs}))
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.config_label")), i18n.T("cmd.dev.vm.config_value", map[string]any{"Memory": opts.Memory, "CPUs": opts.CPUs}))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.booting"))
@ -143,7 +143,7 @@ func runVMBoot(memory, cpus int, fresh bool) error {
cli.Blank()
cli.Text(successStyle.Render(i18n.T("cmd.dev.vm.running")))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.connect_with", map[string]interface{}{"Command": dimStyle.Render("core dev shell")}))
cli.Text(i18n.T("cmd.dev.vm.connect_with", map[string]any{"Command": dimStyle.Render("core dev shell")}))
cli.Print("%s %s\n", i18n.T("cmd.dev.vm.ssh_port"), dimStyle.Render("2222"))
return nil
@ -228,7 +228,7 @@ func runVMStatus() error {
} else {
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), errorStyle.Render(i18n.T("cmd.dev.vm.installed_no")))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.install_with", map[string]interface{}{"Command": dimStyle.Render("core dev install")}))
cli.Text(i18n.T("cmd.dev.vm.install_with", map[string]any{"Command": dimStyle.Render("core dev install")}))
return nil
}
@ -245,7 +245,7 @@ func runVMStatus() error {
} else {
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("status")), dimStyle.Render(i18n.T("common.status.stopped")))
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]any{"Command": dimStyle.Render("core dev boot")}))
}
return nil
@ -474,7 +474,7 @@ func runVMUpdate(apply bool) error {
cli.Blank()
if !apply {
cli.Text(i18n.T("cmd.dev.vm.run_to_update", map[string]interface{}{"Command": dimStyle.Render("core dev update --apply")}))
cli.Text(i18n.T("cmd.dev.vm.run_to_update", map[string]any{"Command": dimStyle.Render("core dev update --apply")}))
return nil
}
@ -504,7 +504,7 @@ func runVMUpdate(apply bool) error {
elapsed := time.Since(start).Round(time.Second)
cli.Blank()
cli.Text(i18n.T("cmd.dev.vm.updated_in", map[string]interface{}{"Duration": elapsed}))
cli.Text(i18n.T("cmd.dev.vm.updated_in", map[string]any{"Duration": elapsed}))
return nil
}

View file

@ -174,9 +174,9 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
}
cli.Blank()
cli.Print("%s\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
cli.Print("%s\n", i18n.T("common.count.repos_unpushed", map[string]any{"Count": len(aheadRepos)}))
for _, s := range aheadRepos {
cli.Print(" %s: %s\n", s.Name, i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead}))
cli.Print(" %s: %s\n", s.Name, i18n.T("common.count.commits", map[string]any{"Count": s.Ahead}))
}
cli.Blank()

View file

@ -151,7 +151,7 @@ func runWorkflowSync(registryPath string, workflowFile string, dryRun bool) erro
// Find the template workflow
templatePath := findTemplateWorkflow(registryDir, workflowFile)
if templatePath == "" {
return cli.Err("%s", i18n.T("cmd.dev.workflow.template_not_found", map[string]interface{}{"File": workflowFile}))
return cli.Err("%s", i18n.T("cmd.dev.workflow.template_not_found", map[string]any{"File": workflowFile}))
}
// Read template content
@ -240,15 +240,15 @@ func runWorkflowSync(registryPath string, workflowFile string, dryRun bool) erro
// Summary
if dryRun {
cli.Print("%s %s\n",
i18n.T("cmd.dev.workflow.would_sync_count", map[string]interface{}{"Count": synced}),
dimStyle.Render(i18n.T("cmd.dev.workflow.skipped_count", map[string]interface{}{"Count": skipped})))
i18n.T("cmd.dev.workflow.would_sync_count", map[string]any{"Count": synced}),
dimStyle.Render(i18n.T("cmd.dev.workflow.skipped_count", map[string]any{"Count": skipped})))
cli.Text(i18n.T("cmd.dev.workflow.run_without_dry_run"))
} else {
cli.Print("%s %s\n",
successStyle.Render(i18n.T("cmd.dev.workflow.synced_count", map[string]interface{}{"Count": synced})),
dimStyle.Render(i18n.T("cmd.dev.workflow.skipped_count", map[string]interface{}{"Count": skipped})))
successStyle.Render(i18n.T("cmd.dev.workflow.synced_count", map[string]any{"Count": synced})),
dimStyle.Render(i18n.T("cmd.dev.workflow.skipped_count", map[string]any{"Count": skipped})))
if failed > 0 {
cli.Print("%s\n", errorStyle.Render(i18n.T("cmd.dev.workflow.failed_count", map[string]interface{}{"Count": failed})))
cli.Print("%s\n", errorStyle.Render(i18n.T("cmd.dev.workflow.failed_count", map[string]any{"Count": failed})))
}
}

View file

@ -48,7 +48,7 @@ func runDocsList(registryPath string) error {
docsDir := checkMark(false)
if len(info.DocsFiles) > 0 {
docsDir = docsFoundStyle.Render(i18n.T("common.count.files", map[string]interface{}{"Count": len(info.DocsFiles)}))
docsDir = docsFoundStyle.Render(i18n.T("common.count.files", map[string]any{"Count": len(info.DocsFiles)}))
}
cli.Print("%-20s %-8s %-8s %-10s %s\n",
@ -69,7 +69,7 @@ func runDocsList(registryPath string) error {
cli.Blank()
cli.Print("%s %s\n",
cli.KeyStyle.Render(i18n.Label("coverage")),
i18n.T("cmd.docs.list.coverage_summary", map[string]interface{}{"WithDocs": withDocs, "WithoutDocs": withoutDocs}),
i18n.T("cmd.docs.list.coverage_summary", map[string]any{"WithDocs": withDocs, "WithoutDocs": withoutDocs}),
)
return nil

View file

@ -99,7 +99,7 @@ func runPHPSync(reg *repos.Registry, basePath string, outputDir string, dryRun b
return nil
}
cli.Print("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]interface{}{"Count": len(docsInfo)}))
cli.Print("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]any{"Count": len(docsInfo)}))
// Show what will be synced
var totalFiles int
@ -109,7 +109,7 @@ func runPHPSync(reg *repos.Registry, basePath string, outputDir string, dryRun b
cli.Print(" %s → %s %s\n",
repoNameStyle.Render(info.Name),
docsFileStyle.Render("packages/"+outName+"/"),
dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})))
dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]any{"Count": len(info.DocsFiles)})))
for _, f := range info.DocsFiles {
cli.Print(" %s\n", dimStyle.Render(f))
@ -118,7 +118,7 @@ func runPHPSync(reg *repos.Registry, basePath string, outputDir string, dryRun b
cli.Print("\n%s %s\n",
dimStyle.Render(i18n.Label("total")),
i18n.T("cmd.docs.sync.total_summary", map[string]interface{}{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir}))
i18n.T("cmd.docs.sync.total_summary", map[string]any{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir}))
if dryRun {
cli.Print("\n%s\n", dimStyle.Render(i18n.T("cmd.docs.sync.dry_run_notice")))
@ -167,7 +167,7 @@ func runPHPSync(reg *repos.Registry, basePath string, outputDir string, dryRun b
synced++
}
cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.sync")), i18n.T("cmd.docs.sync.synced_packages", map[string]interface{}{"Count": synced}))
cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.sync")), i18n.T("cmd.docs.sync.synced_packages", map[string]any{"Count": synced}))
return nil
}

View file

@ -2,6 +2,7 @@ package prod
import (
"context"
"errors"
"fmt"
"os"
"time"
@ -56,7 +57,7 @@ func getDNSClient() (*infra.CloudNSClient, error) {
authID := os.Getenv("CLOUDNS_AUTH_ID")
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
if authID == "" || authPass == "" {
return nil, fmt.Errorf("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
return nil, errors.New("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
}
return infra.NewCloudNSClient(authID, authPass), nil
}

View file

@ -2,6 +2,7 @@ package prod
import (
"context"
"errors"
"fmt"
"os"
"time"
@ -39,7 +40,7 @@ func init() {
func getHCloudClient() (*infra.HCloudClient, error) {
token := os.Getenv("HCLOUD_TOKEN")
if token == "" {
return nil, fmt.Errorf("HCLOUD_TOKEN environment variable required")
return nil, errors.New("HCLOUD_TOKEN environment variable required")
}
return infra.NewHCloudClient(token), nil
}

View file

@ -2,6 +2,7 @@ package prod
import (
"context"
"errors"
"fmt"
"os"
"time"
@ -141,7 +142,7 @@ func stepDiscover(ctx context.Context, cfg *infra.Config) error {
func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
hcloudToken := os.Getenv("HCLOUD_TOKEN")
if hcloudToken == "" {
return fmt.Errorf("HCLOUD_TOKEN required for load balancer management")
return errors.New("HCLOUD_TOKEN required for load balancer management")
}
hc := infra.NewHCloudClient(hcloudToken)
@ -237,7 +238,7 @@ func stepDNS(ctx context.Context, cfg *infra.Config) error {
authID := os.Getenv("CLOUDNS_AUTH_ID")
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
if authID == "" || authPass == "" {
return fmt.Errorf("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
return errors.New("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
}
dns := infra.NewCloudNSClient(authID, authPass)

View file

@ -115,7 +115,7 @@ func runWatch() error {
// Check if context deadline exceeded
if ctx.Err() != nil {
cli.Blank()
return log.E("qa.watch", i18n.T("cmd.qa.watch.timeout", map[string]interface{}{"Duration": watchTimeout}), nil)
return log.E("qa.watch", i18n.T("cmd.qa.watch.timeout", map[string]any{"Duration": watchTimeout}), nil)
}
runs, err := fetchWorkflowRunsForCommit(ctx, repoFullName, commitSha)
@ -335,7 +335,7 @@ func printResults(ctx context.Context, repoFullName string, runs []WorkflowRun)
// Exit with error if any failures
if len(failures) > 0 {
cli.Blank()
return cli.Err("%s", i18n.T("cmd.qa.watch.workflows_failed", map[string]interface{}{"Count": len(failures)}))
return cli.Err("%s", i18n.T("cmd.qa.watch.workflows_failed", map[string]any{"Count": len(failures)}))
}
cli.Blank()

View file

@ -126,7 +126,7 @@ func runGitHubSetup() error {
// Single repo mode
repo, ok := reg.Get(ghRepo)
if !ok {
return errors.New(i18n.T("error.repo_not_found", map[string]interface{}{"Name": ghRepo}))
return errors.New(i18n.T("error.repo_not_found", map[string]any{"Name": ghRepo}))
}
reposToProcess = []*repos.Repo{repo}
} else if ghAll {

View file

@ -159,9 +159,9 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP
// Summary
fmt.Println()
fmt.Printf("%s, %s, %s\n",
i18n.T("cmd.setup.to_clone", map[string]interface{}{"Count": len(toClone)}),
i18n.T("cmd.setup.exist", map[string]interface{}{"Count": exists}),
i18n.T("common.count.skipped", map[string]interface{}{"Count": skipped}))
i18n.T("cmd.setup.to_clone", map[string]any{"Count": len(toClone)}),
i18n.T("cmd.setup.exist", map[string]any{"Count": exists}),
i18n.T("common.count.skipped", map[string]any{"Count": skipped}))
if len(toClone) == 0 {
fmt.Printf("\n%s\n", i18n.T("cmd.setup.nothing_to_clone"))
@ -209,12 +209,12 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP
// Summary
fmt.Println()
fmt.Printf("%s %s", successStyle.Render(i18n.Label("done")), i18n.T("cmd.setup.cloned_count", map[string]interface{}{"Count": succeeded}))
fmt.Printf("%s %s", successStyle.Render(i18n.Label("done")), i18n.T("cmd.setup.cloned_count", map[string]any{"Count": succeeded}))
if failed > 0 {
fmt.Printf(", %s", errorStyle.Render(i18n.T("i18n.count.failed", failed)))
}
if exists > 0 {
fmt.Printf(", %s", i18n.T("cmd.setup.already_exist_count", map[string]interface{}{"Count": exists}))
fmt.Printf(", %s", i18n.T("cmd.setup.already_exist_count", map[string]any{"Count": exists}))
}
fmt.Println()

View file

@ -89,6 +89,6 @@ func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string,
// confirmClone asks for confirmation before cloning.
func confirmClone(count int, target string) (bool, error) {
confirmed := cli.Confirm(i18n.T("cmd.setup.wizard.confirm_clone", map[string]interface{}{"Count": count, "Target": target}))
confirmed := cli.Confirm(i18n.T("cmd.setup.wizard.confirm_clone", map[string]any{"Count": count, "Target": target}))
return confirmed, nil
}

View file

@ -4,7 +4,8 @@ package setup
import (
"fmt"
"sort"
"maps"
"slices"
"strings"
"forge.lthn.ai/core/cli/pkg/cli"
@ -184,12 +185,7 @@ func (cs *ChangeSet) printByCategory(category ChangeCategory, title string) {
fmt.Println()
// Print details (sorted for deterministic output)
keys := make([]string, 0, len(c.Details))
for k := range c.Details {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
for _, k := range slices.Sorted(maps.Keys(c.Details)) {
fmt.Printf(" %s: %s\n", dimStyle.Render(k), c.Details[k])
}
}

View file

@ -105,7 +105,7 @@ func SetBranchProtection(repoFullName, branch string, config BranchProtectionCon
}
// Build the protection payload
payload := map[string]interface{}{
payload := map[string]any{
"enforce_admins": config.EnforceAdmins,
"required_linear_history": config.RequireLinearHistory,
"allow_force_pushes": config.AllowForcePushes,
@ -115,7 +115,7 @@ func SetBranchProtection(repoFullName, branch string, config BranchProtectionCon
// Required pull request reviews
if config.RequiredReviews > 0 {
payload["required_pull_request_reviews"] = map[string]interface{}{
payload["required_pull_request_reviews"] = map[string]any{
"dismiss_stale_reviews": config.DismissStale,
"require_code_owner_reviews": config.RequireCodeOwnerReviews,
"required_approving_review_count": config.RequiredReviews,
@ -126,7 +126,7 @@ func SetBranchProtection(repoFullName, branch string, config BranchProtectionCon
// Required status checks
if len(config.RequiredStatusChecks) > 0 {
payload["required_status_checks"] = map[string]interface{}{
payload["required_status_checks"] = map[string]any{
"strict": true,
"contexts": config.RequiredStatusChecks,
}

View file

@ -154,8 +154,8 @@ func UpdateSecurityAndAnalysis(repoFullName string, secretScanning, pushProtecti
}
// Build the payload
payload := map[string]interface{}{
"security_and_analysis": map[string]interface{}{
payload := map[string]any{
"security_and_analysis": map[string]any{
"secret_scanning": map[string]string{
"status": boolToStatus(secretScanning),
},

View file

@ -69,11 +69,11 @@ func CreateWebhook(repoFullName string, name string, config WebhookConfig) error
}
// Build the webhook payload
payload := map[string]interface{}{
payload := map[string]any{
"name": "web",
"active": true,
"events": config.Events,
"config": map[string]interface{}{
"config": map[string]any{
"url": config.URL,
"content_type": config.ContentType,
"insecure_ssl": "0",
@ -85,7 +85,7 @@ func CreateWebhook(repoFullName string, name string, config WebhookConfig) error
}
if config.Secret != "" {
configMap := payload["config"].(map[string]interface{})
configMap := payload["config"].(map[string]any)
configMap["secret"] = config.Secret
}
@ -111,10 +111,10 @@ func UpdateWebhook(repoFullName string, hookID int, config WebhookConfig) error
return fmt.Errorf("invalid repo format: %s", repoFullName)
}
payload := map[string]interface{}{
payload := map[string]any{
"active": true,
"events": config.Events,
"config": map[string]interface{}{
"config": map[string]any{
"url": config.URL,
"content_type": config.ContentType,
"insecure_ssl": "0",
@ -126,7 +126,7 @@ func UpdateWebhook(repoFullName string, hookID int, config WebhookConfig) error
}
if config.Secret != "" {
configMap := payload["config"].(map[string]interface{})
configMap := payload["config"].(map[string]any)
configMap["secret"] = config.Secret
}

View file

@ -99,8 +99,8 @@ func runContainer(image, name string, detach bool, memory, cpus, sshPort int) er
fmt.Printf("%s %s\n", successStyle.Render(i18n.Label("started")), c.ID)
fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.vm.label.pid")), c.PID)
fmt.Println()
fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]interface{}{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]interface{}{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]any{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]any{"ID": c.ID[:8]}))
} else {
fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.container_stopped")), c.ID)
}
@ -261,11 +261,11 @@ func resolveContainerID(manager *container.LinuxKitManager, partialID string) (s
switch len(matches) {
case 0:
return "", errors.New(i18n.T("cmd.vm.error.no_match", map[string]interface{}{"ID": partialID}))
return "", errors.New(i18n.T("cmd.vm.error.no_match", map[string]any{"ID": partialID}))
case 1:
return matches[0].ID, nil
default:
return "", errors.New(i18n.T("cmd.vm.error.multiple_match", map[string]interface{}{"ID": partialID}))
return "", errors.New(i18n.T("cmd.vm.error.multiple_match", map[string]any{"ID": partialID}))
}
}

View file

@ -204,8 +204,8 @@ func RunFromTemplate(templateName string, vars map[string]string, runOpts contai
fmt.Printf("%s %s\n", successStyle.Render(i18n.T("common.label.started")), c.ID)
fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.vm.label.pid")), c.PID)
fmt.Println()
fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]interface{}{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]interface{}{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]any{"ID": c.ID[:8]}))
fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]any{"ID": c.ID[:8]}))
} else {
fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.container_stopped")), c.ID)
}

View file

@ -2,6 +2,7 @@ package container
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -249,7 +250,7 @@ func DetectHypervisor() (Hypervisor, error) {
return qemu, nil
}
return nil, fmt.Errorf("no hypervisor available: install qemu or hyperkit (macOS)")
return nil, errors.New("no hypervisor available: install qemu or hyperkit (macOS)")
}
// GetHypervisor returns a specific hypervisor by name.
@ -258,13 +259,13 @@ func GetHypervisor(name string) (Hypervisor, error) {
case "qemu":
h := NewQemuHypervisor()
if !h.Available() {
return nil, fmt.Errorf("qemu is not available")
return nil, errors.New("qemu is not available")
}
return h, nil
case "hyperkit":
h := NewHyperkitHypervisor()
if !h.Available() {
return nil, fmt.Errorf("hyperkit is not available (requires macOS)")
return nil, errors.New("hyperkit is not available (requires macOS)")
}
return h, nil
default:

View file

@ -3,6 +3,7 @@ package coolify
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"sync"
@ -41,10 +42,10 @@ func DefaultConfig() Config {
// NewClient creates a new Coolify client.
func NewClient(cfg Config) (*Client, error) {
if cfg.BaseURL == "" {
return nil, fmt.Errorf("COOLIFY_URL not set")
return nil, errors.New("COOLIFY_URL not set")
}
if cfg.APIToken == "" {
return nil, fmt.Errorf("COOLIFY_TOKEN not set")
return nil, errors.New("COOLIFY_TOKEN not set")
}
// Initialize Python runtime

View file

@ -155,7 +155,7 @@ func (t *Toolkit) FindTODOs(dir string) ([]TODO, error) {
return nil, nil
}
if err != nil && exitCode != 1 {
return nil, fmt.Errorf("git grep failed (exit %d): %s\n%s", exitCode, err, stderr)
return nil, fmt.Errorf("git grep failed (exit %d): %w\n%s", exitCode, err, stderr)
}
var todos []TODO
@ -191,7 +191,7 @@ func (t *Toolkit) FindTODOs(dir string) ([]TODO, error) {
func (t *Toolkit) AuditDeps() ([]Vulnerability, error) {
stdout, stderr, exitCode, err := t.Run("govulncheck", "./...")
if err != nil && exitCode != 0 && !strings.Contains(stdout, "Vulnerability") {
return nil, fmt.Errorf("govulncheck failed (exit %d): %s\n%s", exitCode, err, stderr)
return nil, fmt.Errorf("govulncheck failed (exit %d): %w\n%s", exitCode, err, stderr)
}
var vulns []Vulnerability
@ -240,7 +240,7 @@ func (t *Toolkit) AuditDeps() ([]Vulnerability, error) {
func (t *Toolkit) DiffStat() (DiffSummary, error) {
stdout, stderr, exitCode, err := t.Run("git", "diff", "--stat")
if err != nil && exitCode != 0 {
return DiffSummary{}, fmt.Errorf("git diff failed (exit %d): %s\n%s", exitCode, err, stderr)
return DiffSummary{}, fmt.Errorf("git diff failed (exit %d): %w\n%s", exitCode, err, stderr)
}
var s DiffSummary
@ -273,7 +273,7 @@ func (t *Toolkit) DiffStat() (DiffSummary, error) {
func (t *Toolkit) UncommittedFiles() ([]string, error) {
stdout, stderr, exitCode, err := t.Run("git", "status", "--porcelain")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("git status failed: %s\n%s", err, stderr)
return nil, fmt.Errorf("git status failed: %w\n%s", err, stderr)
}
var files []string
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
@ -371,7 +371,7 @@ func (t *Toolkit) Build(targets ...string) ([]BuildResult, error) {
func (t *Toolkit) TestCount(pkg string) (int, error) {
stdout, stderr, exitCode, err := t.Run("go", "test", "-list", ".*", pkg)
if err != nil && exitCode != 0 {
return 0, fmt.Errorf("go test -list failed: %s\n%s", err, stderr)
return 0, fmt.Errorf("go test -list failed: %w\n%s", err, stderr)
}
count := 0
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
@ -389,7 +389,7 @@ func (t *Toolkit) Coverage(pkg string) ([]CoverageReport, error) {
}
stdout, stderr, exitCode, err := t.Run("go", "test", "-cover", pkg)
if err != nil && exitCode != 0 && !strings.Contains(stdout, "coverage:") {
return nil, fmt.Errorf("go test -cover failed (exit %d): %s\n%s", exitCode, err, stderr)
return nil, fmt.Errorf("go test -cover failed (exit %d): %w\n%s", exitCode, err, stderr)
}
var reports []CoverageReport
@ -443,7 +443,7 @@ func (t *Toolkit) RaceDetect(pkg string) ([]RaceCondition, error) {
func (t *Toolkit) Complexity(threshold int) ([]ComplexFunc, error) {
stdout, stderr, exitCode, err := t.Run("gocyclo", "-over", strconv.Itoa(threshold), ".")
if err != nil && exitCode == -1 {
return nil, fmt.Errorf("gocyclo not available: %s\n%s", err, stderr)
return nil, fmt.Errorf("gocyclo not available: %w\n%s", err, stderr)
}
var funcs []ComplexFunc
@ -476,7 +476,7 @@ func (t *Toolkit) Complexity(threshold int) ([]ComplexFunc, error) {
func (t *Toolkit) DepGraph(pkg string) (*Graph, error) {
stdout, stderr, exitCode, err := t.Run("go", "mod", "graph")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("go mod graph failed (exit %d): %s\n%s", exitCode, err, stderr)
return nil, fmt.Errorf("go mod graph failed (exit %d): %w\n%s", exitCode, err, stderr)
}
graph := &Graph{Edges: make(map[string][]string)}
@ -503,7 +503,7 @@ func (t *Toolkit) DepGraph(pkg string) (*Graph, error) {
func (t *Toolkit) GitLog(n int) ([]Commit, error) {
stdout, stderr, exitCode, err := t.Run("git", "log", fmt.Sprintf("-n%d", n), "--format=%H|%an|%aI|%s")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("git log failed (exit %d): %s\n%s", exitCode, err, stderr)
return nil, fmt.Errorf("git log failed (exit %d): %w\n%s", exitCode, err, stderr)
}
var commits []Commit

View file

@ -3,6 +3,7 @@ package devops
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
@ -116,14 +117,14 @@ func DefaultBootOptions() BootOptions {
// Boot starts the dev environment.
func (d *DevOps) Boot(ctx context.Context, opts BootOptions) error {
if !d.images.IsInstalled() {
return fmt.Errorf("dev image not installed (run 'core dev install' first)")
return errors.New("dev image not installed (run 'core dev install' first)")
}
// Check if already running
if !opts.Fresh {
running, err := d.IsRunning(ctx)
if err == nil && running {
return fmt.Errorf("dev environment already running (use 'core dev stop' first or --fresh)")
return errors.New("dev environment already running (use 'core dev stop' first or --fresh)")
}
}
@ -177,7 +178,7 @@ func (d *DevOps) Stop(ctx context.Context) error {
return err
}
if c == nil {
return fmt.Errorf("dev environment not found")
return errors.New("dev environment not found")
}
return d.container.Stop(ctx, c.ID)
}

View file

@ -3,6 +3,7 @@ package devops
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
@ -109,7 +110,7 @@ func (m *ImageManager) Install(ctx context.Context, progress func(downloaded, to
}
}
if src == nil {
return fmt.Errorf("no image source available")
return errors.New("no image source available")
}
// Get version
@ -139,7 +140,7 @@ func (m *ImageManager) Install(ctx context.Context, progress func(downloaded, to
func (m *ImageManager) CheckUpdate(ctx context.Context) (current, latest string, hasUpdate bool, err error) {
info, ok := m.manifest.Images[ImageName()]
if !ok {
return "", "", false, fmt.Errorf("image not installed")
return "", "", false, errors.New("image not installed")
}
current = info.Version
@ -152,7 +153,7 @@ func (m *ImageManager) CheckUpdate(ctx context.Context) (current, latest string,
}
}
if src == nil {
return current, "", false, fmt.Errorf("no image source available")
return current, "", false, errors.New("no image source available")
}
latest, err = src.LatestVersion(ctx)

View file

@ -2,6 +2,7 @@ package devops
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -23,7 +24,7 @@ func (d *DevOps) Serve(ctx context.Context, projectDir string, opts ServeOptions
return err
}
if !running {
return fmt.Errorf("dev environment not running (run 'core dev boot' first)")
return errors.New("dev environment not running (run 'core dev boot' first)")
}
if opts.Port == 0 {

View file

@ -2,6 +2,7 @@ package devops
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -20,7 +21,7 @@ func (d *DevOps) Shell(ctx context.Context, opts ShellOptions) error {
return err
}
if !running {
return fmt.Errorf("dev environment not running (run 'core dev boot' first)")
return errors.New("dev environment not running (run 'core dev boot' first)")
}
if opts.Console {
@ -61,7 +62,7 @@ func (d *DevOps) serialConsole(ctx context.Context) error {
return err
}
if c == nil {
return fmt.Errorf("console not available: container not found")
return errors.New("console not available: container not found")
}
// Use socat to connect to the console socket

View file

@ -2,6 +2,7 @@ package devops
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -37,7 +38,7 @@ func ensureHostKey(ctx context.Context, port int) error {
}
if len(out) == 0 {
return fmt.Errorf("ssh-keyscan returned no keys")
return errors.New("ssh-keyscan returned no keys")
}
// Read existing known_hosts to avoid duplicates

View file

@ -3,6 +3,7 @@ package devops
import (
"context"
"encoding/json"
"errors"
"fmt"
"path/filepath"
"strings"
@ -38,7 +39,7 @@ func (d *DevOps) Test(ctx context.Context, projectDir string, opts TestOptions)
return err
}
if !running {
return fmt.Errorf("dev environment not running (run 'core dev boot' first)")
return errors.New("dev environment not running (run 'core dev boot' first)")
}
var cmd string
@ -63,7 +64,7 @@ func (d *DevOps) Test(ctx context.Context, projectDir string, opts TestOptions)
} else {
cmd = DetectTestCommand(d.medium, projectDir)
if cmd == "" {
return fmt.Errorf("could not detect test command (create .core/test.yaml)")
return errors.New("could not detect test command (create .core/test.yaml)")
}
}
@ -177,7 +178,7 @@ func hasComposerScript(m io.Medium, projectDir, script string) bool {
}
var pkg struct {
Scripts map[string]interface{} `json:"scripts"`
Scripts map[string]any `json:"scripts"`
}
if err := json.Unmarshal([]byte(content), &pkg); err != nil {
return false

4
go.sum
View file

@ -4,8 +4,8 @@ forge.lthn.ai/core/cli v0.0.3 h1:Qd/ACf8as4cwWoqAzkLIPF86NhXzfkkt1t/6xmgznVw=
forge.lthn.ai/core/cli v0.0.3/go.mod h1:xa3Nqw3sUtYYJ1k+1jYul18tgs6sBevCUsGsHJI1hHA=
forge.lthn.ai/core/go v0.0.1 h1:ubk4nmkA3treOUNgPS28wKd1jB6cUlEQUV7jDdGa3zM=
forge.lthn.ai/core/go v0.0.1/go.mod h1:59YsnuMaAGQUxIhX68oK2/HnhQJEPWL1iEZhDTrNCbY=
forge.lthn.ai/core/go-agentic v0.0.2 h1:dsngOpUp8ATUWlM1O8gqFgS4FLJG0ngDvsXNQJigBDA=
forge.lthn.ai/core/go-agentic v0.0.2/go.mod h1:KwalcfzQACtedb7wNe/7U/59PtdpvezqMJmmvhTptOY=
forge.lthn.ai/core/go-agentic v0.0.2 h1:G2nhiFY0j66A8/dyPXrS3CDYT1VLIin//GDszz4zEEo=
forge.lthn.ai/core/go-agentic v0.0.2/go.mod h1:wTZRajs+rt0YJbRk26ijC1sfICbg8O2782ZhCz2tv/k=
forge.lthn.ai/core/go-crypt v0.0.2 h1:m8mCIrmC0tserVx9bfmrB8h4GtgAgQeedBPeNBvCxx0=
forge.lthn.ai/core/go-crypt v0.0.2/go.mod h1:+JoZ4mwjzTklysI/DI7f6/0iocdJoJG2ZF/nQy6HTuI=
forge.lthn.ai/core/go-scm v0.0.2 h1:Ue+gS5vxZkDgTvQrqYu9QdaqEezuTV1kZY3TMqM2uho=

View file

@ -7,7 +7,7 @@ import (
"fmt"
"os/exec"
"regexp"
"sort"
"slices"
"strings"
"golang.org/x/text/cases"
@ -269,18 +269,11 @@ func formatChangelog(commits []ConventionalCommit, version string) string {
// Any remaining types not in the order list
var remainingTypes []string
for commitType := range grouped {
found := false
for _, t := range commitTypeOrder {
if t == commitType {
found = true
break
}
}
if !found {
if !slices.Contains(commitTypeOrder, commitType) {
remainingTypes = append(remainingTypes, commitType)
}
}
sort.Strings(remainingTypes)
slices.Sort(remainingTypes)
for _, commitType := range remainingTypes {
commits := grouped[commitType]

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"os"
"os/exec"
@ -47,7 +48,7 @@ func (p *AURPublisher) Publish(ctx context.Context, release *Release, pubCfg Pub
cfg := p.parseConfig(pubCfg, relCfg)
if cfg.Maintainer == "" {
return fmt.Errorf("aur.Publish: maintainer is required (set publish.aur.maintainer in config)")
return errors.New("aur.Publish: maintainer is required (set publish.aur.maintainer in config)")
}
repo := ""

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"os"
"os/exec"
@ -231,7 +232,7 @@ func (p *ChocolateyPublisher) pushToChocolatey(ctx context.Context, packageDir s
// Check for CHOCOLATEY_API_KEY
apiKey := os.Getenv("CHOCOLATEY_API_KEY")
if apiKey == "" {
return fmt.Errorf("chocolatey.Publish: CHOCOLATEY_API_KEY environment variable is required for push")
return errors.New("chocolatey.Publish: CHOCOLATEY_API_KEY environment variable is required for push")
}
// Pack the package

View file

@ -3,6 +3,7 @@ package publishers
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -250,7 +251,7 @@ func (p *DockerPublisher) ensureBuildx(ctx context.Context) error {
// Check if buildx is available
cmd := exec.CommandContext(ctx, "docker", "buildx", "version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("docker: buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/")
return errors.New("docker: buildx is not available. Install it from https://docs.docker.com/buildx/working-with-buildx/")
}
// Check if we have a builder, create one if not
@ -272,7 +273,7 @@ func (p *DockerPublisher) ensureBuildx(ctx context.Context) error {
func validateDockerCli() error {
cmd := exec.Command("docker", "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("docker: docker CLI not found. Install it from https://docs.docker.com/get-docker/")
return errors.New("docker: docker CLI not found. Install it from https://docs.docker.com/get-docker/")
}
return nil
}

View file

@ -3,6 +3,7 @@ package publishers
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -146,18 +147,18 @@ func validateGhCli() error {
// Check if gh is installed
cmd := exec.Command("gh", "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("github: gh CLI not found. Install it from https://cli.github.com")
return errors.New("github: gh CLI not found. Install it from https://cli.github.com")
}
// Check if authenticated
cmd = exec.Command("gh", "auth", "status")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("github: not authenticated with gh CLI. Run 'gh auth login' first")
return errors.New("github: not authenticated with gh CLI. Run 'gh auth login' first")
}
if !strings.Contains(string(output), "Logged in") {
return fmt.Errorf("github: not authenticated with gh CLI. Run 'gh auth login' first")
return errors.New("github: not authenticated with gh CLI. Run 'gh auth login' first")
}
return nil

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"os"
"os/exec"
@ -57,7 +58,7 @@ func (p *HomebrewPublisher) Publish(ctx context.Context, release *Release, pubCf
// Validate configuration
if cfg.Tap == "" && (cfg.Official == nil || !cfg.Official.Enabled) {
return fmt.Errorf("homebrew.Publish: tap is required (set publish.homebrew.tap in config)")
return errors.New("homebrew.Publish: tap is required (set publish.homebrew.tap in config)")
}
// Get repository and project info

View file

@ -3,6 +3,7 @@ package publishers
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -48,7 +49,7 @@ func (p *LinuxKitPublisher) Publish(ctx context.Context, release *Release, pubCf
// Validate config file exists
if release.FS == nil {
return fmt.Errorf("linuxkit.Publish: release filesystem (FS) is nil")
return errors.New("linuxkit.Publish: release filesystem (FS) is nil")
}
if !release.FS.Exists(lkCfg.Config) {
return fmt.Errorf("linuxkit.Publish: config file not found: %s", lkCfg.Config)
@ -297,7 +298,7 @@ func (p *LinuxKitPublisher) getFormatExtension(format string) string {
func validateLinuxKitCli() error {
cmd := exec.Command("linuxkit", "version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("linuxkit: linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit")
return errors.New("linuxkit: linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit")
}
return nil
}

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"os"
"os/exec"
@ -47,7 +48,7 @@ func (p *NpmPublisher) Publish(ctx context.Context, release *Release, pubCfg Pub
// Validate configuration
if npmCfg.Package == "" {
return fmt.Errorf("npm.Publish: package name is required (set publish.npm.package in config)")
return errors.New("npm.Publish: package name is required (set publish.npm.package in config)")
}
// Get repository
@ -162,7 +163,7 @@ func (p *NpmPublisher) dryRunPublish(m io.Medium, data npmTemplateData, cfg *Npm
func (p *NpmPublisher) executePublish(ctx context.Context, m io.Medium, data npmTemplateData, cfg *NpmConfig) error {
// Check for NPM_TOKEN
if os.Getenv("NPM_TOKEN") == "" {
return fmt.Errorf("npm.Publish: NPM_TOKEN environment variable is required")
return errors.New("npm.Publish: NPM_TOKEN environment variable is required")
}
// Create temp directory for package

View file

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"os"
"os/exec"
@ -45,7 +46,7 @@ func (p *ScoopPublisher) Publish(ctx context.Context, release *Release, pubCfg P
cfg := p.parseConfig(pubCfg, relCfg)
if cfg.Bucket == "" && (cfg.Official == nil || !cfg.Official.Enabled) {
return fmt.Errorf("scoop.Publish: bucket is required (set publish.scoop.bucket in config)")
return errors.New("scoop.Publish: bucket is required (set publish.scoop.bucket in config)")
}
repo := ""

View file

@ -5,14 +5,15 @@ package release
import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"forge.lthn.ai/core/go-devops/build"
"forge.lthn.ai/core/go-devops/build/builders"
"forge.lthn.ai/core/go/pkg/io"
"forge.lthn.ai/core/go-devops/release/publishers"
"forge.lthn.ai/core/go/pkg/io"
)
// Release represents a release with its version, artifacts, and changelog.
@ -34,7 +35,7 @@ type Release struct {
// If dryRun is true, it will show what would be done without actually publishing.
func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
if cfg == nil {
return nil, fmt.Errorf("release.Publish: config is nil")
return nil, errors.New("release.Publish: config is nil")
}
m := io.Local
@ -67,7 +68,7 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
}
if len(artifacts) == 0 {
return nil, fmt.Errorf("release.Publish: no artifacts found in dist/\nRun 'core build' first to create artifacts")
return nil, errors.New("release.Publish: no artifacts found in dist/\nRun 'core build' first to create artifacts")
}
// Step 3: Generate changelog
@ -109,7 +110,7 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
// findArtifacts discovers pre-built artifacts in the dist directory.
func findArtifacts(m io.Medium, distDir string) ([]build.Artifact, error) {
if !m.IsDir(distDir) {
return nil, fmt.Errorf("dist/ directory not found")
return nil, errors.New("dist/ directory not found")
}
var artifacts []build.Artifact
@ -145,7 +146,7 @@ func findArtifacts(m io.Medium, distDir string) ([]build.Artifact, error) {
// If dryRun is true, it will show what would be done without actually publishing.
func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
if cfg == nil {
return nil, fmt.Errorf("release.Run: config is nil")
return nil, errors.New("release.Run: config is nil")
}
m := io.Local
@ -317,9 +318,9 @@ func getBuilder(projectType build.ProjectType) (build.Builder, error) {
case build.ProjectTypeGo:
return builders.NewGoBuilder(), nil
case build.ProjectTypeNode:
return nil, fmt.Errorf("node.js builder not yet implemented")
return nil, errors.New("node.js builder not yet implemented")
case build.ProjectTypePHP:
return nil, fmt.Errorf("PHP builder not yet implemented")
return nil, errors.New("PHP builder not yet implemented")
default:
return nil, fmt.Errorf("unsupported project type: %s", projectType)
}

View file

@ -3,6 +3,7 @@ package release
import (
"context"
"errors"
"fmt"
"forge.lthn.ai/core/go-devops/sdk"
@ -22,10 +23,10 @@ type SDKRelease struct {
// If dryRun is true, it shows what would be done without generating.
func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error) {
if cfg == nil {
return nil, fmt.Errorf("release.RunSDK: config is nil")
return nil, errors.New("release.RunSDK: config is nil")
}
if cfg.SDK == nil {
return nil, fmt.Errorf("release.RunSDK: sdk not configured in .core/release.yaml")
return nil, errors.New("release.RunSDK: sdk not configured in .core/release.yaml")
}
projectDir := cfg.projectDir
@ -51,7 +52,7 @@ func RunSDK(ctx context.Context, cfg *Config, dryRun bool) (*SDKRelease, error)
fmt.Printf("Warning: diff check failed: %v\n", err)
} else if breaking {
if cfg.SDK.Diff.FailOnBreaking {
return nil, fmt.Errorf("release.RunSDK: breaking API changes detected")
return nil, errors.New("release.RunSDK: breaking API changes detected")
}
fmt.Printf("Warning: breaking API changes detected\n")
}

View file

@ -1,6 +1,7 @@
package sdk
import (
"errors"
"fmt"
"path/filepath"
"strings"
@ -46,14 +47,14 @@ func (s *SDK) DetectSpec() (string, error) {
return specPath, nil
}
return "", fmt.Errorf("sdk.DetectSpec: no OpenAPI spec found (checked config, common paths, Scramble)")
return "", errors.New("sdk.DetectSpec: no OpenAPI spec found (checked config, common paths, Scramble)")
}
// detectScramble checks for Laravel Scramble and exports the spec.
func (s *SDK) detectScramble() (string, error) {
composerPath := filepath.Join(s.projectDir, "composer.json")
if !coreio.Local.IsFile(composerPath) {
return "", fmt.Errorf("no composer.json")
return "", errors.New("no composer.json")
}
// Check for scramble in composer.json
@ -64,11 +65,11 @@ func (s *SDK) detectScramble() (string, error) {
// Simple check for scramble package
if !containsScramble(data) {
return "", fmt.Errorf("scramble not found in composer.json")
return "", errors.New("scramble not found in composer.json")
}
// TODO: Run php artisan scramble:export
return "", fmt.Errorf("scramble export not implemented")
return "", errors.New("scramble export not implemented")
}
// containsScramble checks if composer.json includes scramble.

View file

@ -2,6 +2,7 @@ package generators
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -37,7 +38,7 @@ func (g *PHPGenerator) Install() string {
// Generate creates SDK from OpenAPI spec.
func (g *PHPGenerator) Generate(ctx context.Context, opts Options) error {
if !g.Available() {
return fmt.Errorf("php.Generate: Docker is required but not available")
return errors.New("php.Generate: Docker is required but not available")
}
if err := coreio.Local.EnsureDir(opts.OutputDir); err != nil {