← 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
A internal/generator/promoted_presence_check_test.goM internal/generator/templates/command_promoted.go.tmpl
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 →