[object Object]

← back to Cli Printing Press

fix(cli): avoid duplicate batch store upsert generation (#231)

24785ee5bc564a1c7e5d5cab82385b114719a55d · 2026-04-24 17:13:33 -0700 · Pejman Pour-Moezzi

Files touched

Diff

commit 24785ee5bc564a1c7e5d5cab82385b114719a55d
Author: Pejman Pour-Moezzi <pejman.pourmoezzi@gmail.com>
Date:   Fri Apr 24 17:13:33 2026 -0700

    fix(cli): avoid duplicate batch store upsert generation (#231)
---
 internal/generator/generator.go            |  7 ++++
 internal/generator/generator_test.go       | 54 ++++++++++++++++++++++++++++++
 internal/generator/templates/store.go.tmpl |  2 +-
 internal/generator/templates/sync.go.tmpl  |  2 +-
 4 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 328a493a..b7a40c0e 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -249,6 +249,9 @@ func New(s *spec.APISpec, outputDir string) *Generator {
 		},
 		"envName":  func(s string) string { return strings.ToUpper(strings.ReplaceAll(s, "-", "_")) },
 		"safeName": safeSQLName,
+		"hasDomainUpsert": func(name string) bool {
+			return domainUpsertMethodName(name) != "UpsertBatch"
+		},
 		"pathContainsParam": func(path, name string) bool {
 			return strings.Contains(path, "{"+name+"}")
 		},
@@ -1737,6 +1740,10 @@ func toPascal(s string) string {
 	return strings.Join(parts, "")
 }
 
+func domainUpsertMethodName(tableName string) string {
+	return "Upsert" + toPascal(tableName)
+}
+
 // isIDParam returns true if the parameter name suggests it's an identifier
 // that should be typed as string regardless of the spec's declared type.
 // IDs like steamid (17-digit number) overflow int64, and zero-value confusion
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 10b63b33..6607dd8d 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -872,6 +872,60 @@ func TestGenerateWithEmptyOwner(t *testing.T) {
 	assert.NotContains(t, string(gomod), "module github.com/")
 }
 
+func TestGenerateStoreWithBatchResourceDoesNotDuplicateUpsertBatch(t *testing.T) {
+	t.Parallel()
+
+	apiSpec := &spec.APISpec{
+		Name:    "batch",
+		Version: "0.1.0",
+		BaseURL: "https://api.example.com",
+		Auth:    spec.AuthConfig{Type: "none"},
+		Config: spec.ConfigSpec{
+			Format: "toml",
+			Path:   "~/.config/batch-pp-cli/config.toml",
+		},
+		Resources: map[string]spec.Resource{
+			"batch": {
+				Description: "Manage batch jobs",
+				Endpoints: map[string]spec.Endpoint{
+					"list": {
+						Method:      "GET",
+						Path:        "/batch",
+						Description: "List batch jobs",
+						Params: []spec.Param{
+							{Name: "id", Type: "string"},
+							{Name: "name", Type: "string"},
+							{Name: "status", Type: "string"},
+							{Name: "created_at", Type: "string", Format: "date-time"},
+						},
+					},
+					"create": {
+						Method:      "POST",
+						Path:        "/batch",
+						Description: "Create a batch job",
+						Body: []spec.Param{
+							{Name: "name", Type: "string"},
+							{Name: "description", Type: "string"},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+	gen := New(apiSpec, outputDir)
+	gen.VisionSet = VisionTemplateSet{Store: true}
+	require.NoError(t, gen.Generate())
+
+	storeSrc, err := os.ReadFile(filepath.Join(outputDir, "internal", "store", "store.go"))
+	require.NoError(t, err)
+	assert.Equal(t, 1, strings.Count(string(storeSrc), "func (s *Store) UpsertBatch("))
+
+	runGoCommand(t, outputDir, "mod", "tidy")
+	runGoCommand(t, outputDir, "test", "./internal/store")
+}
+
 // --- Unit 7: Feature Verification Tests ---
 
 func generatePetstore(t *testing.T) string {
diff --git a/internal/generator/templates/store.go.tmpl b/internal/generator/templates/store.go.tmpl
index 14b66792..5db059e2 100644
--- a/internal/generator/templates/store.go.tmpl
+++ b/internal/generator/templates/store.go.tmpl
@@ -344,7 +344,7 @@ func lookupFieldValue(obj map[string]any, snakeKey string) any {
 }
 
 {{- range .Tables}}
-{{- if and (gt (len .Columns) 3) (ne .Name "sync_state")}}
+{{- if and (gt (len .Columns) 3) (ne .Name "sync_state") (hasDomainUpsert .Name)}}
 // Upsert{{pascal .Name}} inserts or updates a {{.Name}} record with domain-specific columns.
 func (s *Store) Upsert{{pascal .Name}}(data json.RawMessage) error {
 	var obj map[string]any
diff --git a/internal/generator/templates/sync.go.tmpl b/internal/generator/templates/sync.go.tmpl
index 87a074ec..aa843495 100644
--- a/internal/generator/templates/sync.go.tmpl
+++ b/internal/generator/templates/sync.go.tmpl
@@ -499,7 +499,7 @@ func upsertSingleObject(db *store.Store, resource string, data json.RawMessage)
 
 	switch resource {
 {{- range .Tables}}
-{{- if and (gt (len .Columns) 3) (ne .Name "sync_state")}}
+{{- if and (gt (len .Columns) 3) (ne .Name "sync_state") (hasDomainUpsert .Name)}}
 	case "{{.Name}}":
 		return db.Upsert{{pascal .Name}}(data)
 {{- end}}

← 55b3f5e1 fix(megamcp): reject env var values containing '}' in ApplyA  ·  back to Cli Printing Press  ·  fix(cli): stop doctor from claiming valid creds are invalid 6a184771 →