← back to Cli Printing Press
fix(cli): thread ctx through wrapper functions in generated CLIs (#442) (#445)
493a1b15d6eecac3ec5419dea45a73ac86412487 · 2026-04-30 11:58:40 -0700 · Trevin Chow
* fix(cli): thread ctx through wrapper functions in generated CLIs
Closes #442. Completes the ctx-propagation work started in #436/#441
by threading caller context through the four wrapper functions that
PR #441's review surfaced as still-on-context.Background():
- openStoreForRead → resolveLocal → resolveRead / resolvePaginatedRead.
All four take a context.Context now. The two public functions
(resolveRead, resolvePaginatedRead) carry the change to their callers
in command_promoted.go.tmpl and command_endpoint.go.tmpl, which now
pass cmd.Context() from inside the Cobra RunE.
- writeThroughCache(ctx, resourceType, data) — the best-effort cache
writer in resolveRead's auto path. Caller is resolveRead itself, so
the ctx flows naturally.
- openShareStore(ctx) — share_commands.go.tmpl. Four Cobra RunE callers
updated to pass cmd.Context().
- collectCacheReport(ctx, staleAfterSpec) — doctor.go.tmpl. Single
Cobra RunE caller updated.
Net effect: SIGINT during a generated CLI's resolveRead, share, or
doctor flow now interrupts the migration retry loop in store.Open
instead of waiting out migrationLockTimeout. This was the gap PR #441
explicitly deferred when the wrapper-function refactor scope was
larger than that PR's focused fix.
Golden fixture refreshed for the four new ctx-passing call sites in
the generate-golden-api expected output (doctor.go, projects_list.go,
projects_tasks_list-project.go, promoted_public.go).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cli): regression-guard against store.Open( in generated CLIs
Adds TestGenerateNoUnscopedStoreOpen — generates a representative
store-bearing CLI (cache enabled + share enabled, the broadest set of
templates that historically called store.Open) and walks every
non-test .go file under internal/, asserting no literal store.Open(
remains. The substring "store.Open(" doesn't match
"store.OpenWithContext(" so the check is precise without regex.
Manually verified the test catches a regression by temporarily
reverting share_commands.go.tmpl back to store.Open(dbPath) — the
test correctly fails with the file path and a clear remediation
message. Reverting the revert restores green.
Net result: any future template that re-introduces a context-less
store.Open call fails this test before it ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M internal/generator/generator_test.goM internal/generator/templates/command_endpoint.go.tmplM internal/generator/templates/command_promoted.go.tmplM internal/generator/templates/data_source.go.tmplM internal/generator/templates/doctor.go.tmplM internal/generator/templates/share_commands.go.tmplM testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.goM testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_list.goM testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_list-project.goM testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/promoted_public.go
Diff
commit 493a1b15d6eecac3ec5419dea45a73ac86412487
Author: Trevin Chow <trevin@trevinchow.com>
Date: Thu Apr 30 11:58:40 2026 -0700
fix(cli): thread ctx through wrapper functions in generated CLIs (#442) (#445)
* fix(cli): thread ctx through wrapper functions in generated CLIs
Closes #442. Completes the ctx-propagation work started in #436/#441
by threading caller context through the four wrapper functions that
PR #441's review surfaced as still-on-context.Background():
- openStoreForRead → resolveLocal → resolveRead / resolvePaginatedRead.
All four take a context.Context now. The two public functions
(resolveRead, resolvePaginatedRead) carry the change to their callers
in command_promoted.go.tmpl and command_endpoint.go.tmpl, which now
pass cmd.Context() from inside the Cobra RunE.
- writeThroughCache(ctx, resourceType, data) — the best-effort cache
writer in resolveRead's auto path. Caller is resolveRead itself, so
the ctx flows naturally.
- openShareStore(ctx) — share_commands.go.tmpl. Four Cobra RunE callers
updated to pass cmd.Context().
- collectCacheReport(ctx, staleAfterSpec) — doctor.go.tmpl. Single
Cobra RunE caller updated.
Net effect: SIGINT during a generated CLI's resolveRead, share, or
doctor flow now interrupts the migration retry loop in store.Open
instead of waiting out migrationLockTimeout. This was the gap PR #441
explicitly deferred when the wrapper-function refactor scope was
larger than that PR's focused fix.
Golden fixture refreshed for the four new ctx-passing call sites in
the generate-golden-api expected output (doctor.go, projects_list.go,
projects_tasks_list-project.go, promoted_public.go).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cli): regression-guard against store.Open( in generated CLIs
Adds TestGenerateNoUnscopedStoreOpen — generates a representative
store-bearing CLI (cache enabled + share enabled, the broadest set of
templates that historically called store.Open) and walks every
non-test .go file under internal/, asserting no literal store.Open(
remains. The substring "store.Open(" doesn't match
"store.OpenWithContext(" so the check is precise without regex.
Manually verified the test catches a regression by temporarily
reverting share_commands.go.tmpl back to store.Open(dbPath) — the
test correctly fails with the file path and a clear remediation
message. Reverting the revert restores green.
Net result: any future template that re-introduces a context-less
store.Open call fails this test before it ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
internal/generator/generator_test.go | 53 ++++++++++++++++++++++
.../generator/templates/command_endpoint.go.tmpl | 4 +-
.../generator/templates/command_promoted.go.tmpl | 4 +-
internal/generator/templates/data_source.go.tmpl | 29 ++++++------
internal/generator/templates/doctor.go.tmpl | 9 ++--
.../generator/templates/share_commands.go.tmpl | 13 +++---
.../printing-press-golden/internal/cli/doctor.go | 7 +--
.../internal/cli/projects_list.go | 2 +-
.../internal/cli/projects_tasks_list-project.go | 2 +-
.../internal/cli/promoted_public.go | 2 +-
10 files changed, 92 insertions(+), 33 deletions(-)
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index a3ea3e0d..71fca805 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -154,6 +154,59 @@ func TestGenerateCliutilPackage(t *testing.T) {
runGoCommand(t, outputDir, "test", "./internal/cliutil/...")
}
+// TestGenerateNoUnscopedStoreOpen guards every non-test code path in
+// generated CLIs against silently re-introducing context-less store
+// opens. After PR #441 (store.OpenWithContext) and PR #445 (wrapper-
+// function ctx threading), every emitted call to the store should
+// route through OpenWithContext so caller cancellation propagates
+// through the migration retry loop. A future template addition that
+// regresses to store.Open(...) would defeat the entire ctx story.
+//
+// Excludes test files (cliutil tests, share_test.go) since those
+// don't have a Cobra context and Open(dbPath) → OpenWithContext(
+// context.Background(), dbPath) is the correct fallback there.
+func TestGenerateNoUnscopedStoreOpen(t *testing.T) {
+ t.Parallel()
+
+ // Use the share fixture: it exercises the broadest set of templates
+ // (store, sync, cache, share, doctor, MCP, auto-refresh, workflows)
+ // so the walk covers every emission point that historically called
+ // store.Open.
+ apiSpec := minimalSpec("storeopenguard")
+ apiSpec.Cache.Enabled = true
+ apiSpec.Share.Enabled = true
+ apiSpec.Share.SnapshotTables = []string{"items"}
+
+ outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+ gen := New(apiSpec, outputDir)
+ gen.VisionSet = VisionTemplateSet{Store: true, Sync: true}
+ require.NoError(t, gen.Generate())
+
+ // Walk every .go file under internal/, skipping test files.
+ // strings.Contains("store.Open(") matches the no-ctx form but not
+ // "store.OpenWithContext(" because the literal "Open(" never appears
+ // in the longer name.
+ internalDir := filepath.Join(outputDir, "internal")
+ err := filepath.Walk(internalDir, func(path string, info os.FileInfo, walkErr error) error {
+ if walkErr != nil {
+ return walkErr
+ }
+ if info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
+ return nil
+ }
+ data, readErr := os.ReadFile(path)
+ if readErr != nil {
+ return readErr
+ }
+ if strings.Contains(string(data), "store.Open(") {
+ rel := filepath.ToSlash(strings.TrimPrefix(path, outputDir+"/"))
+ t.Errorf("%s contains store.Open( without ctx — use store.OpenWithContext(ctx, ...) instead", rel)
+ }
+ return nil
+ })
+ require.NoError(t, err, "walking generated CLI tree")
+}
+
// TestGenerateFreshnessHelperEmitted verifies that the cliutil freshness
// helper and auto-refresh wrapper are emitted when the spec opts into
// cache, and that the resulting CLI compiles end-to-end and its cliutil
diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index f29fa1c9..137ae79c 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -157,7 +157,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
{{- if or (eq .Endpoint.Method "GET") (eq .Endpoint.Method "HEAD")}}
{{- if .Endpoint.Pagination}}
{{- if .HasStore}}
- data, prov, err := resolvePaginatedRead(c, flags, "{{lower .ResourceName}}", path, map[string]string{
+ data, prov, err := resolvePaginatedRead(cmd.Context(), c, flags, "{{lower .ResourceName}}", path, map[string]string{
{{- range $i, $p := .Endpoint.Params}}
{{- if and .Positional (not (pathContainsParam $.Endpoint.Path .Name))}}
"{{.Name}}": args[{{$i}}],
@@ -192,7 +192,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
{{- end}}
{{- end}}
{{- if .HasStore}}
- data, prov, err := resolveRead(c, flags, "{{lower .ResourceName}}", {{if .Endpoint.Pagination}}true{{else}}false{{end}}, path, params, {{if .Endpoint.HeaderOverrides}}headerOverrides{{else}}nil{{end}})
+ data, prov, err := resolveRead(cmd.Context(), c, flags, "{{lower .ResourceName}}", {{if .Endpoint.Pagination}}true{{else}}false{{end}}, path, params, {{if .Endpoint.HeaderOverrides}}headerOverrides{{else}}nil{{end}})
{{- else}}
{{- if .Endpoint.HeaderOverrides}}
data, err := c.GetWithHeaders(path, params, headerOverrides)
diff --git a/internal/generator/templates/command_promoted.go.tmpl b/internal/generator/templates/command_promoted.go.tmpl
index 8f14db68..0d3471f9 100644
--- a/internal/generator/templates/command_promoted.go.tmpl
+++ b/internal/generator/templates/command_promoted.go.tmpl
@@ -103,7 +103,7 @@ func new{{camel .PromotedName}}PromotedCmd(flags *rootFlags) *cobra.Command {
{{- if .Endpoint.Pagination}}
{{- if .HasStore}}
- data, prov, err := resolvePaginatedRead(c, flags, "{{lower .ResourceName}}", path, map[string]string{
+ data, prov, err := resolvePaginatedRead(cmd.Context(), c, flags, "{{lower .ResourceName}}", path, map[string]string{
{{- range $i, $p := .Endpoint.Params}}
{{- if and .Positional (not (pathContainsParam $.Endpoint.Path .Name))}}
"{{.Name}}": args[{{$i}}],
@@ -141,7 +141,7 @@ func new{{camel .PromotedName}}PromotedCmd(flags *rootFlags) *cobra.Command {
// resolveRead is GET-only internally, so HasStore + non-GET should
// be marked Hidden in the printed CLI and reached via the typed
// `<resource> <endpoint>` form instead.
- data, prov, err := resolveRead(c, flags, "{{lower .ResourceName}}", {{if .Endpoint.Pagination}}true{{else}}false{{end}}, path, params, nil)
+ data, prov, err := resolveRead(cmd.Context(), c, flags, "{{lower .ResourceName}}", {{if .Endpoint.Pagination}}true{{else}}false{{end}}, path, params, nil)
{{- else if eq (upper .Endpoint.Method) "GET"}}
data, err := c.Get(path, params)
{{- else if eq (upper .Endpoint.Method) "DELETE"}}
diff --git a/internal/generator/templates/data_source.go.tmpl b/internal/generator/templates/data_source.go.tmpl
index 2e010f4d..234f7cbe 100644
--- a/internal/generator/templates/data_source.go.tmpl
+++ b/internal/generator/templates/data_source.go.tmpl
@@ -4,6 +4,7 @@
package cli
import (
+ "context"
"encoding/json"
"fmt"
"net"
@@ -46,12 +47,12 @@ func isNetworkError(err error) bool {
// openStoreForRead opens the local SQLite store for reading.
// Returns nil, nil if the database file does not exist (no sync has been run).
-func openStoreForRead(cliName string) (*store.Store, error) {
+func openStoreForRead(ctx context.Context, cliName string) (*store.Store, error) {
dbPath := defaultDBPath(cliName)
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
return nil, nil
}
- return store.Open(dbPath)
+ return store.OpenWithContext(ctx, dbPath)
}
// localProvenance builds a DataProvenance for local data reads.
@@ -90,10 +91,10 @@ func attachFreshness(prov DataProvenance, flags *rootFlags) DataProvenance {
// declares no per-endpoint header overrides. Without this parameter, store-backed
// reads on per-endpoint-versioned APIs silently get the wrong response shape
// (cal-com retro #334 F1).
-func resolveRead(c *client.Client, flags *rootFlags, resourceType string, isList bool, path string, params map[string]string, headers map[string]string) (json.RawMessage, DataProvenance, error) {
+func resolveRead(ctx context.Context, c *client.Client, flags *rootFlags, resourceType string, isList bool, path string, params map[string]string, headers map[string]string) (json.RawMessage, DataProvenance, error) {
switch flags.dataSource {
case "local":
- data, prov, err := resolveLocal(resourceType, isList, path, params, "user_requested")
+ data, prov, err := resolveLocal(ctx, resourceType, isList, path, params, "user_requested")
return data, attachFreshness(prov, flags), err
case "live":
@@ -106,7 +107,7 @@ func resolveRead(c *client.Client, flags *rootFlags, resourceType string, isList
default: // "auto"
data, err := c.GetWithHeaders(path, params, headers)
if err == nil {
- writeThroughCache(resourceType, data)
+ writeThroughCache(ctx, resourceType, data)
return data, attachFreshness(DataProvenance{Source: "live"}, flags), nil
}
if !isNetworkError(err) {
@@ -114,7 +115,7 @@ func resolveRead(c *client.Client, flags *rootFlags, resourceType string, isList
return nil, DataProvenance{}, err
}
// Network error — try local fallback
- fallbackData, fallbackProv, fallbackErr := resolveLocal(resourceType, isList, path, params, "api_unreachable")
+ fallbackData, fallbackProv, fallbackErr := resolveLocal(ctx, resourceType, isList, path, params, "api_unreachable")
if fallbackErr != nil {
return nil, DataProvenance{}, fmt.Errorf("API unreachable and no local data. Run '{{.Name}}-pp-cli sync' to enable offline access.\n\nOriginal error: %w", err)
}
@@ -126,10 +127,10 @@ func resolveRead(c *client.Client, flags *rootFlags, resourceType string, isList
// or local store. When local, skips pagination and returns all synced data. The
// headers argument carries per-endpoint required headers; pass nil when the
// endpoint declares no overrides.
-func resolvePaginatedRead(c *client.Client, flags *rootFlags, resourceType string, path string, params map[string]string, headers map[string]string, fetchAll bool, cursorParam, nextCursorPath, hasMoreField string) (json.RawMessage, DataProvenance, error) {
+func resolvePaginatedRead(ctx context.Context, c *client.Client, flags *rootFlags, resourceType string, path string, params map[string]string, headers map[string]string, fetchAll bool, cursorParam, nextCursorPath, hasMoreField string) (json.RawMessage, DataProvenance, error) {
switch flags.dataSource {
case "local":
- data, prov, err := resolveLocal(resourceType, true, path, params, "user_requested")
+ data, prov, err := resolveLocal(ctx, resourceType, true, path, params, "user_requested")
return data, attachFreshness(prov, flags), err
case "live":
@@ -142,13 +143,13 @@ func resolvePaginatedRead(c *client.Client, flags *rootFlags, resourceType strin
default: // "auto"
data, err := paginatedGet(c, path, params, headers, fetchAll, cursorParam, nextCursorPath, hasMoreField)
if err == nil {
- writeThroughCache(resourceType, data)
+ writeThroughCache(ctx, resourceType, data)
return data, attachFreshness(DataProvenance{Source: "live"}, flags), nil
}
if !isNetworkError(err) {
return nil, DataProvenance{}, err
}
- fallbackData, fallbackProv, fallbackErr := resolveLocal(resourceType, true, path, params, "api_unreachable")
+ fallbackData, fallbackProv, fallbackErr := resolveLocal(ctx, resourceType, true, path, params, "api_unreachable")
if fallbackErr != nil {
return nil, DataProvenance{}, fmt.Errorf("API unreachable and no local data. Run '{{.Name}}-pp-cli sync' to enable offline access.\n\nOriginal error: %w", err)
}
@@ -159,8 +160,8 @@ func resolvePaginatedRead(c *client.Client, flags *rootFlags, resourceType strin
// writeThroughCache upserts live API results into the local SQLite store so
// FTS search covers everything the user has looked up — not just explicit syncs.
// Best-effort: failures are silently ignored (the live result already succeeded).
-func writeThroughCache(resourceType string, data json.RawMessage) {
- db, err := store.Open(defaultDBPath("{{.Name}}-pp-cli"))
+func writeThroughCache(ctx context.Context, resourceType string, data json.RawMessage) {
+ db, err := store.OpenWithContext(ctx, defaultDBPath("{{.Name}}-pp-cli"))
if err != nil {
return
}
@@ -212,8 +213,8 @@ func writeThroughCache(resourceType string, data json.RawMessage) {
// Note: local reads return ALL synced data for the resource type. Endpoint-specific
// filters (query params, path scoping like /teams/{id}/users) are NOT applied locally.
// The provenance metadata includes "unscoped":true when params were present but not applied.
-func resolveLocal(resourceType string, isList bool, path string, params map[string]string, reason string) (json.RawMessage, DataProvenance, error) {
- db, err := openStoreForRead("{{.Name}}-pp-cli")
+func resolveLocal(ctx context.Context, resourceType string, isList bool, path string, params map[string]string, reason string) (json.RawMessage, DataProvenance, error) {
+ db, err := openStoreForRead(ctx, "{{.Name}}-pp-cli")
if err != nil {
return nil, DataProvenance{}, fmt.Errorf("opening local database: %w\nRun '{{.Name}}-pp-cli sync' first.", err)
}
diff --git a/internal/generator/templates/doctor.go.tmpl b/internal/generator/templates/doctor.go.tmpl
index 9ea3a346..def4d047 100644
--- a/internal/generator/templates/doctor.go.tmpl
+++ b/internal/generator/templates/doctor.go.tmpl
@@ -4,6 +4,9 @@
package cli
import (
+{{- if .HasStore}}
+ "context"
+{{- end}}
{{- if .Auth.RequiresBrowserSession}}
"crypto/sha256"
"encoding/hex"
@@ -315,7 +318,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
// Surfaces rows + last_synced_at per resource, schema version,
// and a fresh/stale/unknown verdict so agents can introspect
// whether to trust the cached data before issuing queries.
- report["cache"] = collectCacheReport({{if .Cache.StaleAfter}}"{{.Cache.StaleAfter}}"{{else}}""{{end}})
+ report["cache"] = collectCacheReport(cmd.Context(), {{if .Cache.StaleAfter}}"{{.Cache.StaleAfter}}"{{else}}""{{end}})
{{- end}}
report["version"] = version
@@ -496,7 +499,7 @@ func doctorExitForFailOn(failOn string, report map[string]any) error {
// staleAfterSpec is the CLI's configured threshold (e.g. "6h"); empty means
// use the runtime default. The default is deliberately conservative (6h)
// because the alternative is no freshness story at all.
-func collectCacheReport(staleAfterSpec string) map[string]any {
+func collectCacheReport(ctx context.Context, staleAfterSpec string) map[string]any {
report := map[string]any{}
dbPath := defaultDBPath("{{.Name}}-pp-cli")
report["db_path"] = dbPath
@@ -514,7 +517,7 @@ func collectCacheReport(staleAfterSpec string) map[string]any {
}
report["db_bytes"] = fi.Size()
- s, err := store.Open(dbPath)
+ s, err := store.OpenWithContext(ctx, dbPath)
if err != nil {
report["status"] = "error"
report["error"] = err.Error()
diff --git a/internal/generator/templates/share_commands.go.tmpl b/internal/generator/templates/share_commands.go.tmpl
index b26b6089..c913ac5b 100644
--- a/internal/generator/templates/share_commands.go.tmpl
+++ b/internal/generator/templates/share_commands.go.tmpl
@@ -4,6 +4,7 @@
package cli
import (
+ "context"
"errors"
"fmt"
"os"
@@ -47,7 +48,7 @@ required to be a git repo — use this for offline handoffs or CI staging.`,
if repoPath == "" {
return fmt.Errorf("--repo is required")
}
- s, err := openShareStore()
+ s, err := openShareStore(cmd.Context())
if err != nil {
return err
}
@@ -83,7 +84,7 @@ CI cache warming, or a manual restore from a known-good snapshot.`,
if repoPath == "" {
return fmt.Errorf("--repo is required")
}
- s, err := openShareStore()
+ s, err := openShareStore(cmd.Context())
if err != nil {
return err
}
@@ -133,7 +134,7 @@ Runs idempotently — an unchanged snapshot produces no commit.`,
// publish rather than aborting.
fmt.Fprintf(os.Stderr, "warning: pull before publish failed (treating as empty remote): %v\n", err)
}
- s, err := openShareStore()
+ s, err := openShareStore(cmd.Context())
if err != nil {
return err
}
@@ -197,7 +198,7 @@ cache.enabled is set in the spec.`,
if err := share.Pull(ctx, opts); err != nil {
return err
}
- s, err := openShareStore()
+ s, err := openShareStore(cmd.Context())
if err != nil {
return err
}
@@ -248,9 +249,9 @@ func resolveShareOptions(repoPath, remote, branch string) (share.Options, error)
// openShareStore opens the local cache store at the canonical path.
// Kept inline to avoid exposing defaultDBPath's internals to share code
// that should remain agnostic about CLI layout.
-func openShareStore() (*store.Store, error) {
+func openShareStore(ctx context.Context) (*store.Store, error) {
dbPath := defaultDBPath("{{.Name}}-pp-cli")
- s, err := store.Open(dbPath)
+ s, err := store.OpenWithContext(ctx, dbPath)
if err != nil {
return nil, fmt.Errorf("open cache at %s: %w", dbPath, err)
}
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
index 5d7afdcd..a438366f 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/doctor.go
@@ -4,6 +4,7 @@
package cli
import (
+ "context"
"database/sql"
"errors"
"fmt"
@@ -196,7 +197,7 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
// Surfaces rows + last_synced_at per resource, schema version,
// and a fresh/stale/unknown verdict so agents can introspect
// whether to trust the cached data before issuing queries.
- report["cache"] = collectCacheReport("")
+ report["cache"] = collectCacheReport(cmd.Context(), "")
report["version"] = version
@@ -314,7 +315,7 @@ func doctorExitForFailOn(failOn string, report map[string]any) error {
// staleAfterSpec is the CLI's configured threshold (e.g. "6h"); empty means
// use the runtime default. The default is deliberately conservative (6h)
// because the alternative is no freshness story at all.
-func collectCacheReport(staleAfterSpec string) map[string]any {
+func collectCacheReport(ctx context.Context, staleAfterSpec string) map[string]any {
report := map[string]any{}
dbPath := defaultDBPath("printing-press-golden-pp-cli")
report["db_path"] = dbPath
@@ -332,7 +333,7 @@ func collectCacheReport(staleAfterSpec string) map[string]any {
}
report["db_bytes"] = fi.Size()
- s, err := store.Open(dbPath)
+ s, err := store.OpenWithContext(ctx, dbPath)
if err != nil {
report["status"] = "error"
report["error"] = err.Error()
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_list.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_list.go
index 864de0f3..6502bb03 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_list.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_list.go
@@ -42,7 +42,7 @@ func newProjectsListCmd(flags *rootFlags) *cobra.Command {
}
path := "/projects"
- data, prov, err := resolvePaginatedRead(c, flags, "projects", path, map[string]string{
+ data, prov, err := resolvePaginatedRead(cmd.Context(), c, flags, "projects", path, map[string]string{
"status": fmt.Sprintf("%v", flagStatus),
"limit": fmt.Sprintf("%v", flagLimit),
"cursor": fmt.Sprintf("%v", flagCursor),
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_list-project.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_list-project.go
index c6c33488..6ca07192 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_list-project.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/projects_tasks_list-project.go
@@ -47,7 +47,7 @@ func newProjectsTasksListProjectCmd(flags *rootFlags) *cobra.Command {
path := "/projects/{projectId}/tasks"
path = replacePathParam(path, "projectId", args[0])
- data, prov, err := resolvePaginatedRead(c, flags, "tasks", path, map[string]string{
+ data, prov, err := resolvePaginatedRead(cmd.Context(), c, flags, "tasks", path, map[string]string{
"priority": fmt.Sprintf("%v", flagPriority),
"limit": fmt.Sprintf("%v", flagLimit),
"cursor": fmt.Sprintf("%v", flagCursor),
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/promoted_public.go b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/promoted_public.go
index 7b5e2ae3..c718bc6b 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/promoted_public.go
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/internal/cli/promoted_public.go
@@ -30,7 +30,7 @@ func newPublicPromotedCmd(flags *rootFlags) *cobra.Command {
// resolveRead is GET-only internally, so HasStore + non-GET should
// be marked Hidden in the printed CLI and reached via the typed
// `<resource> <endpoint>` form instead.
- data, prov, err := resolveRead(c, flags, "public", false, path, params, nil)
+ data, prov, err := resolveRead(cmd.Context(), c, flags, "public", false, path, params, nil)
if err != nil {
return classifyAPIError(err)
}
← 6d357b36 feat(cli): framework-collision detection in resource-name ex
·
back to Cli Printing Press
·
fix(cli): retro #421 WU-4, WU-6, WU-7 — search empty JSON, l ba22f30a →