Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: golangci-lint

on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
schedule:
- cron: "23 6 * * 1"

permissions:
contents: read
pull-requests: read

jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
path: cli
# Sibling checkouts (proto/common) for repos with replace directives.
# No-op for repos that do not need them.
- uses: actions/checkout@v4
if: ${{ hashFiles('cli/go.mod') != '' }}
with:
repository: InstaNode-dev/common
path: common
continue-on-error: true
- uses: actions/checkout@v4
with:
repository: InstaNode-dev/proto
path: proto
continue-on-error: true
- uses: actions/setup-go@v5
with:
go-version-file: cli/go.mod
- uses: golangci/golangci-lint-action@v8
with:
version: latest
working-directory: cli
args: --timeout=5m
35 changes: 35 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# golangci-lint v2 config — start conservative, expand once baseline is clean
version: "2"

run:
timeout: 5m
tests: true

linters:
# Default linter set is govet+errcheck+ineffassign+staticcheck+unused.
# We explicitly add misspell + gocyclo on top. gosimple folded into staticcheck in v2.
enable:
- errcheck # checks unchecked errors
- govet # standard vet
- ineffassign # ineffective assignments
- staticcheck # bug detection (subsumes gosimple in v2)
- unused # unused code
- misspell # spelling
- gocyclo # cyclomatic complexity
settings:
gocyclo:
# Baseline lift to clear the two pre-existing top offenders
# (runUp=31, runResourceDetail=21) without a behavior-changing
# refactor. Set just above the highest (31) so any NEW function
# crossing this threshold still fails the gate.
min-complexity: 32
exclusions:
rules:
- path: _test\.go
linters:
- errcheck
- gocyclo

issues:
max-issues-per-linter: 0
max-same-issues: 0
2 changes: 1 addition & 1 deletion cmd/deploy_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func newDeployStub(verb, extra string) *cobra.Command {
Short: short,
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Fprintf(cmd.ErrOrStderr(),
_, _ = fmt.Fprintf(cmd.ErrOrStderr(),
"`instant deploy %s` is not yet implemented in the CLI.\n"+
"Use one of:\n"+
" - MCP tool (Claude Code / Cursor: %s)\n"+
Expand Down
8 changes: 4 additions & 4 deletions cmd/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func runResources(cmd *cobra.Command) error {
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

// T16 P1-2 — uniform 401 handling.
if resp.StatusCode == http.StatusUnauthorized {
Expand Down Expand Up @@ -171,7 +171,7 @@ func runResources(cmd *cobra.Command) error {
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "TOKEN\tTYPE\tNAME\tTIER\tSTATUS")
_, _ = fmt.Fprintln(w, "TOKEN\tTYPE\tNAME\tTIER\tSTATUS")
for _, r := range result.Items {
shortToken := r.Token
if len(shortToken) > 12 {
Expand All @@ -181,10 +181,10 @@ func runResources(cmd *cobra.Command) error {
if name == "" {
name = "-"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
shortToken, r.ResourceType, name, r.Tier, r.Status)
}
w.Flush()
_ = w.Flush()
return nil
}

Expand Down
26 changes: 13 additions & 13 deletions cmd/extras.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func runResourceDetail(cmd *cobra.Command, token string) error {
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode == http.StatusUnauthorized {
if haveAuth() {
Expand Down Expand Up @@ -169,36 +169,36 @@ func runResourceDetail(cmd *cobra.Command, token string) error {
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "TOKEN\t%s\n", detail.Token)
_, _ = fmt.Fprintf(w, "TOKEN\t%s\n", detail.Token)
if detail.ID != "" {
fmt.Fprintf(w, "ID\t%s\n", detail.ID)
_, _ = fmt.Fprintf(w, "ID\t%s\n", detail.ID)
}
if detail.ResourceType != "" {
fmt.Fprintf(w, "TYPE\t%s\n", detail.ResourceType)
_, _ = fmt.Fprintf(w, "TYPE\t%s\n", detail.ResourceType)
}
if detail.Name != "" {
fmt.Fprintf(w, "NAME\t%s\n", detail.Name)
_, _ = fmt.Fprintf(w, "NAME\t%s\n", detail.Name)
}
if detail.Env != "" {
fmt.Fprintf(w, "ENV\t%s\n", detail.Env)
_, _ = fmt.Fprintf(w, "ENV\t%s\n", detail.Env)
}
if detail.Tier != "" {
fmt.Fprintf(w, "TIER\t%s\n", detail.Tier)
_, _ = fmt.Fprintf(w, "TIER\t%s\n", detail.Tier)
}
if detail.Status != "" {
fmt.Fprintf(w, "STATUS\t%s\n", detail.Status)
_, _ = fmt.Fprintf(w, "STATUS\t%s\n", detail.Status)
}
if detail.ConnectionURL != "" {
fmt.Fprintf(w, "URL\t%s\n", detail.ConnectionURL)
_, _ = fmt.Fprintf(w, "URL\t%s\n", detail.ConnectionURL)
}
if detail.ReceiveURL != "" {
fmt.Fprintf(w, "RECEIVE_URL\t%s\n", detail.ReceiveURL)
_, _ = fmt.Fprintf(w, "RECEIVE_URL\t%s\n", detail.ReceiveURL)
}
if detail.CreatedAt != "" {
fmt.Fprintf(w, "CREATED\t%s\n", detail.CreatedAt)
_, _ = fmt.Fprintf(w, "CREATED\t%s\n", detail.CreatedAt)
}
if detail.ExpiresAt != "" {
fmt.Fprintf(w, "EXPIRES\t%s\n", detail.ExpiresAt)
_, _ = fmt.Fprintf(w, "EXPIRES\t%s\n", detail.ExpiresAt)
}
return w.Flush()
}
Expand Down Expand Up @@ -235,7 +235,7 @@ func runResourceDelete(cmd *cobra.Command, token string) error {
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized {
if haveAuth() {
Expand Down
8 changes: 4 additions & 4 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func createCLISession(anonTokens []string) (*cliSession, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
Expand Down Expand Up @@ -216,7 +216,7 @@ func pollForAuthCompletion(sessionID string) (*authResult, error) {
}

raw, _ := io.ReadAll(resp.Body)
resp.Body.Close()
_ = resp.Body.Close()

if resp.StatusCode == http.StatusAccepted {
// Still pending — print a progress dot and wait.
Expand All @@ -242,7 +242,7 @@ func pollForAuthCompletion(sessionID string) (*authResult, error) {
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, raw)
}

return nil, fmt.Errorf("timed out waiting for login (%.0f minutes). Try again.", pollTimeout.Minutes())
return nil, fmt.Errorf("timed out waiting for login after %.0f minutes; try again", pollTimeout.Minutes())
}

// pollForTierUpgrade polls GET /auth/me until the tier changes, up to 5 minutes.
Expand All @@ -267,7 +267,7 @@ func pollForTierUpgrade(cfg *cliconfig.Config) error {
TeamName string `json:"team_name"`
}
raw, _ := io.ReadAll(resp.Body)
resp.Body.Close()
_ = resp.Body.Close()
if err := json.Unmarshal(raw, &result); err != nil {
time.Sleep(pollInterval)
continue
Expand Down
4 changes: 3 additions & 1 deletion cmd/login_poll_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ func TestLoadAnonymousTokens_WithEntries(t *testing.T) {
if len(out) != 2 {
t.Fatalf("expected 2 anon tokens, got %d", len(out))
}
if !((out[0] == "tok-1" && out[1] == "tok-2") || (out[0] == "tok-2" && out[1] == "tok-1")) {
order1 := out[0] == "tok-1" && out[1] == "tok-2"
order2 := out[0] == "tok-2" && out[1] == "tok-1"
if !order1 && !order2 {
t.Errorf("unexpected token slice: %v", out)
}
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func provisionResource(endpoint, name string) (*provisionResponse, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized && haveAuth() {
Expand Down Expand Up @@ -282,17 +282,17 @@ With --json, output is a machine-readable JSON array of token entries
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "TOKEN\tNAME\tSOURCE\tCREATED")
_, _ = fmt.Fprintln(w, "TOKEN\tNAME\tSOURCE\tCREATED")
for _, e := range store.Entries {
shortToken := e.Token
if len(shortToken) > 12 {
shortToken = shortToken[:12] + "…"
}
created := e.CreatedAt.Format("2006-01-02")
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
shortToken, e.Name, e.Source, created)
}
w.Flush()
_ = w.Flush()
return nil
},
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func fetchExistingResources(env string) ([]resourceListItem, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()

// 401: unauthenticated. For anonymous callers this is expected (no
// resources to reuse, no error); for callers with a token it means the
Expand Down Expand Up @@ -460,7 +460,7 @@ func provisionForUp(decl manifestRsrc, env string) (*provisionResponse, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized && haveAuth() {
return nil, errSessionExpiredSentinel
Expand Down Expand Up @@ -539,7 +539,7 @@ func fetchCredentials(token string) (string, error) {
if err != nil {
return "", err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("server %d: %s", resp.StatusCode, truncate(string(raw), 120))
Expand Down
9 changes: 4 additions & 5 deletions internal/secretstore/keychain_fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import (
// It also lets each test inject specific errors on Get/Set/Delete to drive
// every branch of the wrapper.
type fakeKeyring struct {
store map[string]string
getErr error
setErr error
deleteErr error
notFoundFn func(err error) bool
store map[string]string
getErr error
setErr error
deleteErr error
}

func newFakeKeyring() *fakeKeyring {
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func run(args []string, stderr io.Writer) int {

err := cmd.ExecuteWithArgs(args)
if err != nil {
fmt.Fprintln(stderr, err)
_, _ = fmt.Fprintln(stderr, err)
}
// Translate any error returned by the cobra tree into the documented
// exit-code contract. A nil error exits 0; an *ExitCodeError carries its
Expand Down
Loading