[object Object]

← back to Cli Printing Press

fix(cli): promoted-command presence check uses promoted type (#196)

4ebfa08801e47967a104976ae1f1d73242f19abb · 2026-04-12 20:33:31 -0700 · Trevin Chow

* fix(cli): promoted-command presence check uses promoted type — closes #189

Generator bug surfaced during steam-web regeneration:

    internal/cli/promoted_iecon-items-1269260.go:37:22:
    invalid operation: flagSteamid != 0 (mismatched types string and untyped int)

ID-like int parameters (steamid, appid, etc.) are promoted to string at
declaration by goTypeForParam / cobraFlagFuncForParam — a deliberate
override to avoid 64-bit overflow and empty-vs-unset confusion. The
flag variable ends up as a Go string.

But the promoted-command template used zeroVal(p.Type) for the presence
check, which still sees the original int type and returns "0":

    var flagSteamid string            // promoted type
    if flagSteamid != 0 { ... }       // zeroVal of original type (BUG)

Fix: route the presence check through zeroValForParam, which mirrors
the same ID-promotion logic goTypeForParam / cobraFlagFuncForParam use.
Now the emitted comparison is string-to-string:

    if flagSteamid != "" { ... }

The zeroValForParam helper already existed in the template FuncMap —
only the template callsite needed updating. One-line fix.

Leaving command_endpoint.go.tmpl alone: those body-field comparisons
use bodyX (declared via goType, not goTypeForParam), so the original
type is authoritative and zeroVal is correct.

Regression test in promoted_presence_check_test.go: generates a CLI
with a steamid-shaped parameter, asserts the rendered code contains
`flagSteamid != ""` (not `!= 0`), and runs go vet end-to-end to catch
any future regression at the Go-toolchain level.

1816 tests pass across 22 packages. golangci-lint clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(cli): trim presence-check regression test after simplify review

- Dropped runGoVet: string assertions pin the template emission
  deterministically; go vet adds ~15s per run (mod tidy + vet) and
  is already exercised by TestEndToEndGenerateWithFullNarrativeBuilds
  AndParses in root_long_test.go.
- Replaced Glob + strings.Builder concat with a direct os.ReadFile
  on promoted_items.go — only one promoted file exists for a single-
  endpoint spec.
- Pruned the 14-line doc comment to 3 lines. Bug mechanics live in
  the commit message and PR description, not test narration.
- Removed three narration comments inside the test body.

Net: 45 → 43 lines, -15s test runtime, same coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 4ebfa08801e47967a104976ae1f1d73242f19abb
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Sun Apr 12 20:33:31 2026 -0700

    fix(cli): promoted-command presence check uses promoted type (#196)
    
    * fix(cli): promoted-command presence check uses promoted type — closes #189
    
    Generator bug surfaced during steam-web regeneration:
    
        internal/cli/promoted_iecon-items-1269260.go:37:22:
        invalid operation: flagSteamid != 0 (mismatched types string and untyped int)
    
    ID-like int parameters (steamid, appid, etc.) are promoted to string at
    declaration by goTypeForParam / cobraFlagFuncForParam — a deliberate
    override to avoid 64-bit overflow and empty-vs-unset confusion. The
    flag variable ends up as a Go string.
    
    But the promoted-command template used zeroVal(p.Type) for the presence
    check, which still sees the original int type and returns "0":
    
        var flagSteamid string            // promoted type
        if flagSteamid != 0 { ... }       // zeroVal of original type (BUG)
    
    Fix: route the presence check through zeroValForParam, which mirrors
    the same ID-promotion logic goTypeForParam / cobraFlagFuncForParam use.
    Now the emitted comparison is string-to-string:
    
        if flagSteamid != "" { ... }
    
    The zeroValForParam helper already existed in the template FuncMap —
    only the template callsite needed updating. One-line fix.
    
    Leaving command_endpoint.go.tmpl alone: those body-field comparisons
    use bodyX (declared via goType, not goTypeForParam), so the original
    type is authoritative and zeroVal is correct.
    
    Regression test in promoted_presence_check_test.go: generates a CLI
    with a steamid-shaped parameter, asserts the rendered code contains
    `flagSteamid != ""` (not `!= 0`), and runs go vet end-to-end to catch
    any future regression at the Go-toolchain level.
    
    1816 tests pass across 22 packages. golangci-lint clean.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
    
    * refactor(cli): trim presence-check regression test after simplify review
    
    - Dropped runGoVet: string assertions pin the template emission
      deterministically; go vet adds ~15s per run (mod tidy + vet) and
      is already exercised by TestEndToEndGenerateWithFullNarrativeBuilds
      AndParses in root_long_test.go.
    - Replaced Glob + strings.Builder concat with a direct os.ReadFile
      on promoted_items.go — only one promoted file exists for a single-
      endpoint spec.
    - Pruned the 14-line doc comment to 3 lines. Bug mechanics live in
      the commit message and PR description, not test narration.
    - Removed three narration comments inside the test body.
    
    Net: 45 → 43 lines, -15s test runtime, same coverage.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 internal/generator/promoted_presence_check_test.go | 43 ++++++++++++++++++++++
 .../generator/templates/command_promoted.go.tmpl   |  2 +-
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/internal/generator/promoted_presence_check_test.go b/internal/generator/promoted_presence_check_test.go
new file mode 100644
index 00000000..65742796
--- /dev/null
+++ b/internal/generator/promoted_presence_check_test.go
@@ -0,0 +1,43 @@
+package generator
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/mvanhorn/cli-printing-press/internal/spec"
+	"github.com/stretchr/testify/require"
+)
+
+// Ensures promoted commands emit presence checks against the flag's
+// declared type — which is string for ID-promoted ints — not the
+// spec's original type. Regression guard for #189.
+func TestPromotedPresenceCheckUsesPromotedType(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := minimalSpec("steam-like")
+	apiSpec.Resources["items"] = spec.Resource{
+		Description: "Items",
+		Endpoints: map[string]spec.Endpoint{
+			"get": {
+				Method:      "GET",
+				Path:        "/items/get",
+				Description: "Get items for a Steam account",
+				Params: []spec.Param{
+					{Name: "steamid", Type: "int", Required: false, Description: "Steam ID (64-bit)"},
+				},
+			},
+		},
+	}
+
+	outputDir := filepath.Join(t.TempDir(), "steam-like-pp-cli")
+	require.NoError(t, New(apiSpec, outputDir).Generate())
+
+	src, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "promoted_items.go"))
+	require.NoError(t, err)
+
+	require.Contains(t, string(src), `flagSteamid != ""`,
+		"flag declared as string (ID promotion) must be compared against string zero")
+	require.NotContains(t, string(src), "flagSteamid != 0",
+		"flag declared as string must not be compared against int zero — this is the #189 bug")
+}
diff --git a/internal/generator/templates/command_promoted.go.tmpl b/internal/generator/templates/command_promoted.go.tmpl
index 11114fa6..d42095a3 100644
--- a/internal/generator/templates/command_promoted.go.tmpl
+++ b/internal/generator/templates/command_promoted.go.tmpl
@@ -82,7 +82,7 @@ func new{{camel .PromotedName}}PromotedCmd(flags *rootFlags) *cobra.Command {
 			params["{{.Name}}"] = args[{{$i}}]
 {{- end}}
 {{- if not .Positional}}
-			if flag{{camel .Name}} != {{zeroVal .Type}} {
+			if flag{{camel .Name}} != {{zeroValForParam .Name .Type}} {
 				params["{{.Name}}"] = fmt.Sprintf("%v", flag{{camel .Name}})
 			}
 {{- end}}

← 126b00d0 fix(cli): path-aware dogfood novel-feature matcher (#195)  ·  back to Cli Printing Press  ·  fix(skills): retro 2026-04-13 — stop the ship-broken pattern 3bef9d6c →