Skip to content

Commit 2758ba2

Browse files
feat: add .golangci.yml config and fix all lint issues
Add golangci-lint v2 config with sensible linters for a CLI project (gosec, revive, gocritic, errorlint, noctx, etc.) and fix all 9 issues found: use errors.As for wrapped errors, avoid range copy of large structs, replace http.Get with context-aware request, rename shadowed builtin, and fix gofmt alignment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9c2bafc commit 2758ba2

9 files changed

Lines changed: 144 additions & 60 deletions

File tree

.golangci.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
version: "2"
2+
3+
linters:
4+
enable:
5+
# Bugs & correctness
6+
- bodyclose
7+
- nilerr
8+
- nilnesserr
9+
- noctx
10+
- durationcheck
11+
- copyloopvar
12+
- errorlint
13+
14+
# Security
15+
- gosec
16+
17+
# Style & clarity
18+
- revive
19+
- gocritic
20+
- misspell
21+
- unconvert
22+
- unparam
23+
- nakedret
24+
- goconst
25+
- predeclared
26+
- usestdlibvars
27+
- whitespace
28+
29+
# Performance
30+
- intrange
31+
32+
settings:
33+
gosec:
34+
excludes:
35+
- G115 # uintptr->int for term.IsTerminal is standard
36+
- G117 # Marshaling API key to config file is intentional
37+
- G301 # 0755 for output dirs is fine for a CLI
38+
- G304 # File paths from variables are expected in a CLI
39+
40+
revive:
41+
rules:
42+
- name: blank-imports
43+
- name: context-as-argument
44+
- name: dot-imports
45+
- name: error-return
46+
- name: error-strings
47+
- name: increment-decrement
48+
- name: indent-error-flow
49+
- name: range
50+
- name: receiver-naming
51+
- name: time-naming
52+
- name: var-declaration
53+
- name: var-naming
54+
55+
gocritic:
56+
enabled-tags:
57+
- diagnostic
58+
- performance
59+
disabled-checks:
60+
- hugeParam
61+
62+
nakedret:
63+
max-func-lines: 20
64+
65+
goconst:
66+
min-len: 3
67+
min-occurrences: 4
68+
69+
formatters:
70+
enable:
71+
- gofmt
72+
73+
exclusions:
74+
paths:
75+
- "third_party$"
76+
- "builtin$"
77+
- "examples$"

cmd/account.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ var accountCreditsCmd = &cobra.Command{
3737
format := output.ParseFormat(getFormat())
3838

3939
data := map[string]interface{}{
40-
"balance": result.Balance,
41-
"today": result.Usage.Today,
42-
"last_7d": result.Usage.Last7Days,
43-
"last_30d": result.Usage.Last30Days,
44-
"total": result.Usage.Total,
40+
"balance": result.Balance,
41+
"today": result.Usage.Today,
42+
"last_7d": result.Usage.Last7Days,
43+
"last_30d": result.Usage.Last30Days,
44+
"total": result.Usage.Total,
4545
}
4646

4747
return output.Print(format, data,

cmd/image_inference.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,11 @@ func encodeImageFile(path string) (string, error) {
335335

336336
// downloadImage downloads a URL to a local file.
337337
func downloadImage(url, destPath string) error {
338-
resp, err := http.Get(url) //nolint:gosec // URL comes from API response
338+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
339+
if err != nil {
340+
return fmt.Errorf("failed to create request: %w", err)
341+
}
342+
resp, err := http.DefaultClient.Do(req)
339343
if err != nil {
340344
return fmt.Errorf("failed to download: %w", err)
341345
}

cmd/model_search.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ func runModelSearch(cmd *cobra.Command, args []string) error {
8989

9090
headers := []interface{}{"AIR", "Name", "Category", "Arch", "Version"}
9191
var rows [][]interface{}
92-
for _, m := range result.Results {
92+
for i := range result.Results {
93+
m := &result.Results[i]
9394
name := truncate(m.Name, 45)
9495
rows = append(rows, []interface{}{m.AIR, name, m.Category, m.Architecture, m.Version})
9596
}
@@ -102,10 +103,10 @@ func runModelSearch(cmd *cobra.Command, args []string) error {
102103
return nil
103104
}
104105

105-
func truncate(s string, max int) string {
106+
func truncate(s string, maxLen int) string {
106107
s = strings.ReplaceAll(s, "\n", " ")
107-
if len(s) <= max {
108+
if len(s) <= maxLen {
108109
return s
109110
}
110-
return s[:max-3] + "..."
111+
return s[:maxLen-3] + "..."
111112
}

cmd/ping.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ var pingCmd = &cobra.Command{
4141

4242
format := output.ParseFormat(getFormat())
4343
data := map[string]interface{}{
44-
"status": "ok",
45-
"latency_ms": latencyMs,
44+
"status": "ok",
45+
"latency_ms": latencyMs,
4646
"environment": env,
4747
}
4848

cmd/video_inference.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,14 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
271271
if noDownload {
272272
headers := []interface{}{"#", "URL", "Seed"}
273273
var rows [][]interface{}
274-
for i, r := range results {
275-
url := r.VideoURL
274+
for i := range results {
275+
url := results[i].VideoURL
276276
if url == "" {
277-
url = r.MediaURL
277+
url = results[i].MediaURL
278278
}
279-
row := []interface{}{i + 1, url, r.Seed}
279+
row := []interface{}{i + 1, url, results[i].Seed}
280280
if includeCost {
281-
row = append(row, fmt.Sprintf("%.4f", r.Cost))
281+
row = append(row, fmt.Sprintf("%.4f", results[i].Cost))
282282
}
283283
rows = append(rows, row)
284284
}
@@ -299,27 +299,27 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
299299
}
300300
var rows [][]interface{}
301301

302-
for i, r := range results {
302+
for i := range results {
303303
ext := outputFormat
304304
if ext == "" {
305305
ext = "mp4"
306306
}
307307
filename := fmt.Sprintf("runware_%s_%d.%s", time.Now().Format("20060102_150405"), i+1, ext)
308308
destPath := filepath.Join(outputDir, filename)
309309

310-
url := r.VideoURL
310+
url := results[i].VideoURL
311311
if url == "" {
312-
url = r.MediaURL
312+
url = results[i].MediaURL
313313
}
314314

315315
if err := downloadFile(url, destPath); err != nil {
316316
output.Error(fmt.Sprintf("Failed to download video %d: %s", i+1, err))
317317
continue
318318
}
319319

320-
row := []interface{}{i + 1, destPath, r.Seed}
320+
row := []interface{}{i + 1, destPath, results[i].Seed}
321321
if includeCost {
322-
row = append(row, fmt.Sprintf("%.4f", r.Cost))
322+
row = append(row, fmt.Sprintf("%.4f", results[i].Cost))
323323
}
324324
rows = append(rows, row)
325325
}

internal/api/types.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ type AccountResult struct {
9090
}
9191

9292
type AccountUsage struct {
93-
Total UsagePeriod `json:"total"`
94-
Today UsagePeriod `json:"today"`
95-
Last7Days UsagePeriod `json:"last7Days"`
93+
Total UsagePeriod `json:"total"`
94+
Today UsagePeriod `json:"today"`
95+
Last7Days UsagePeriod `json:"last7Days"`
9696
Last30Days UsagePeriod `json:"last30Days"`
9797
}
9898

@@ -103,22 +103,22 @@ type UsagePeriod struct {
103103

104104
// VideoInferenceRequest contains fields for the videoInference task type.
105105
type VideoInferenceRequest struct {
106-
TaskType string `json:"taskType"`
107-
TaskUUID string `json:"taskUUID"`
108-
Model string `json:"model"`
109-
PositivePrompt string `json:"positivePrompt,omitempty"`
110-
NegativePrompt string `json:"negativePrompt,omitempty"`
111-
Width int `json:"width,omitempty"`
112-
Height int `json:"height,omitempty"`
113-
Duration float64 `json:"duration,omitempty"`
114-
Steps int `json:"steps,omitempty"`
115-
CFGScale float64 `json:"CFGScale,omitempty"`
116-
Seed int64 `json:"seed,omitempty"`
117-
NumberResults int `json:"numberResults,omitempty"`
118-
OutputFormat string `json:"outputFormat,omitempty"`
119-
DeliveryMethod string `json:"deliveryMethod,omitempty"`
120-
FrameImages []FrameImage `json:"frameImages,omitempty"`
121-
IncludeCost bool `json:"includeCost,omitempty"`
106+
TaskType string `json:"taskType"`
107+
TaskUUID string `json:"taskUUID"`
108+
Model string `json:"model"`
109+
PositivePrompt string `json:"positivePrompt,omitempty"`
110+
NegativePrompt string `json:"negativePrompt,omitempty"`
111+
Width int `json:"width,omitempty"`
112+
Height int `json:"height,omitempty"`
113+
Duration float64 `json:"duration,omitempty"`
114+
Steps int `json:"steps,omitempty"`
115+
CFGScale float64 `json:"CFGScale,omitempty"`
116+
Seed int64 `json:"seed,omitempty"`
117+
NumberResults int `json:"numberResults,omitempty"`
118+
OutputFormat string `json:"outputFormat,omitempty"`
119+
DeliveryMethod string `json:"deliveryMethod,omitempty"`
120+
FrameImages []FrameImage `json:"frameImages,omitempty"`
121+
IncludeCost bool `json:"includeCost,omitempty"`
122122
}
123123

124124
// FrameImage constrains a specific frame with an input image.

internal/config/config.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -10,18 +11,18 @@ import (
1011
)
1112

1213
const (
13-
DefaultBaseURL = "https://api.runware.ai/v1"
14-
DefaultModel = "runware:100@1"
15-
DefaultWidth = 1024
16-
DefaultHeight = 1024
17-
DefaultSteps = 28
18-
DefaultCFGScale = 3.5
19-
DefaultScheduler = "euler"
20-
DefaultOutputDir = "./outputs"
21-
DefaultOutputFmt = "png"
22-
DefaultFormat = "table"
23-
DefaultEnv = "production"
24-
DefaultMode = "public"
14+
DefaultBaseURL = "https://api.runware.ai/v1"
15+
DefaultModel = "runware:100@1"
16+
DefaultWidth = 1024
17+
DefaultHeight = 1024
18+
DefaultSteps = 28
19+
DefaultCFGScale = 3.5
20+
DefaultScheduler = "euler"
21+
DefaultOutputDir = "./outputs"
22+
DefaultOutputFmt = "png"
23+
DefaultFormat = "table"
24+
DefaultEnv = "production"
25+
DefaultMode = "public"
2526
)
2627

2728
// Defaults holds default values for inference commands.
@@ -90,7 +91,8 @@ func Init() error {
9091
_ = viper.BindEnv("environment", "RUNWARE_ENV")
9192

9293
if err := viper.ReadInConfig(); err != nil {
93-
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
94+
var notFound viper.ConfigFileNotFoundError
95+
if !errors.As(err, &notFound) {
9496
return fmt.Errorf("error reading config: %w", err)
9597
}
9698
// Config file not found is fine — we use defaults

internal/config/config_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ func TestSaveAndLoad(t *testing.T) {
5050
},
5151
Presets: map[string]Preset{
5252
"fast": {
53-
Model: "fast-model",
54-
Width: 256,
53+
Model: "fast-model",
54+
Width: 256,
5555
Height: 256,
56-
Steps: 1,
56+
Steps: 1,
5757
},
5858
},
5959
}
@@ -107,10 +107,10 @@ func TestPresetOperations(t *testing.T) {
107107

108108
// Save a preset
109109
preset := Preset{
110-
Model: "test-model",
111-
Width: 768,
110+
Model: "test-model",
111+
Width: 768,
112112
Height: 768,
113-
Steps: 20,
113+
Steps: 20,
114114
}
115115
if err := SavePreset("test-preset", preset); err != nil {
116116
t.Fatalf("SavePreset() error: %v", err)

0 commit comments

Comments
 (0)