fix: Use FatalErrorRespectJSON for JSON-consistent error output (bd-28sq.1)

Replace fmt.Fprintf(os.Stderr, ...) + os.Exit(1) patterns with
FatalErrorRespectJSON() in updateCmd, closeCmd, and editCmd.

This ensures that when --json flag is set, errors are returned as
JSON objects ({"error": "message"}) instead of plain text.

Fixes: bd-28sq.1
This commit is contained in:
Steve Yegge
2025-12-25 13:40:54 -08:00
parent 6da9610ba6
commit d9bf695791

View File

@@ -471,8 +471,7 @@ var updateCmd = &cobra.Command{
priorityStr, _ := cmd.Flags().GetString("priority") priorityStr, _ := cmd.Flags().GetString("priority")
priority, err := validation.ValidatePriority(priorityStr) priority, err := validation.ValidatePriority(priorityStr)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
updates["priority"] = priority updates["priority"] = priority
} }
@@ -512,8 +511,7 @@ var updateCmd = &cobra.Command{
if cmd.Flags().Changed("estimate") { if cmd.Flags().Changed("estimate") {
estimate, _ := cmd.Flags().GetInt("estimate") estimate, _ := cmd.Flags().GetInt("estimate")
if estimate < 0 { if estimate < 0 {
fmt.Fprintf(os.Stderr, "Error: estimate must be a non-negative number of minutes\n") FatalErrorRespectJSON("estimate must be a non-negative number of minutes")
os.Exit(1)
} }
updates["estimated_minutes"] = estimate updates["estimated_minutes"] = estimate
} }
@@ -521,8 +519,7 @@ var updateCmd = &cobra.Command{
issueType, _ := cmd.Flags().GetString("type") issueType, _ := cmd.Flags().GetString("type")
// Validate issue type // Validate issue type
if !types.IssueType(issueType).IsValid() { if !types.IssueType(issueType).IsValid() {
fmt.Fprintf(os.Stderr, "Error: invalid issue type %q. Valid types: bug, feature, task, epic, chore, merge-request, molecule, gate\n", issueType) FatalErrorRespectJSON("invalid issue type %q. Valid types: bug, feature, task, epic, chore, merge-request, molecule, gate", issueType)
os.Exit(1)
} }
updates["issue_type"] = issueType updates["issue_type"] = issueType
} }
@@ -542,8 +539,7 @@ var updateCmd = &cobra.Command{
issueType, _ := cmd.Flags().GetString("type") issueType, _ := cmd.Flags().GetString("type")
// Validate issue type // Validate issue type
if _, err := validation.ParseIssueType(issueType); err != nil { if _, err := validation.ParseIssueType(issueType); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
updates["issue_type"] = issueType updates["issue_type"] = issueType
} }
@@ -562,13 +558,11 @@ var updateCmd = &cobra.Command{
resolveArgs := &rpc.ResolveIDArgs{ID: id} resolveArgs := &rpc.ResolveIDArgs{ID: id}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err) FatalErrorRespectJSON("resolving ID %s: %v", id, err)
os.Exit(1)
} }
var resolvedID string var resolvedID string
if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { if err := json.Unmarshal(resp.Data, &resolvedID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
resolvedIDs = append(resolvedIDs, resolvedID) resolvedIDs = append(resolvedIDs, resolvedID)
} }
@@ -576,8 +570,7 @@ var updateCmd = &cobra.Command{
var err error var err error
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args) resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} }
@@ -784,8 +777,7 @@ Examples:
if daemonClient == nil { if daemonClient == nil {
fullID, err := utils.ResolvePartialID(ctx, store, id) fullID, err := utils.ResolvePartialID(ctx, store, id)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err) FatalErrorRespectJSON("resolving %s: %v", id, err)
os.Exit(1)
} }
id = fullID id = fullID
} }
@@ -817,8 +809,7 @@ Examples:
} }
} }
if editor == "" { if editor == "" {
fmt.Fprintf(os.Stderr, "Error: No editor found. Set $EDITOR or $VISUAL environment variable.\n") FatalErrorRespectJSON("no editor found. Set $EDITOR or $VISUAL environment variable")
os.Exit(1)
} }
// Get the current issue // Get the current issue
@@ -830,25 +821,21 @@ Examples:
showArgs := &rpc.ShowArgs{ID: id} showArgs := &rpc.ShowArgs{ID: id}
resp, err := daemonClient.Show(showArgs) resp, err := daemonClient.Show(showArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching issue %s: %v\n", id, err) FatalErrorRespectJSON("fetching issue %s: %v", id, err)
os.Exit(1)
} }
issue = &types.Issue{} issue = &types.Issue{}
if err := json.Unmarshal(resp.Data, issue); err != nil { if err := json.Unmarshal(resp.Data, issue); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing issue data: %v\n", err) FatalErrorRespectJSON("parsing issue data: %v", err)
os.Exit(1)
} }
} else { } else {
// Direct mode // Direct mode
issue, err = store.GetIssue(ctx, id) issue, err = store.GetIssue(ctx, id)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching issue %s: %v\n", id, err) FatalErrorRespectJSON("fetching issue %s: %v", id, err)
os.Exit(1)
} }
if issue == nil { if issue == nil {
fmt.Fprintf(os.Stderr, "Issue %s not found\n", id) FatalErrorRespectJSON("issue %s not found", id)
os.Exit(1)
} }
} }
@@ -870,8 +857,7 @@ Examples:
// Create a temporary file with the current value // Create a temporary file with the current value
tmpFile, err := os.CreateTemp("", fmt.Sprintf("bd-edit-%s-*.txt", fieldToEdit)) tmpFile, err := os.CreateTemp("", fmt.Sprintf("bd-edit-%s-*.txt", fieldToEdit))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error creating temp file: %v\n", err) FatalErrorRespectJSON("creating temp file: %v", err)
os.Exit(1)
} }
tmpPath := tmpFile.Name() tmpPath := tmpFile.Name()
defer func() { _ = os.Remove(tmpPath) }() defer func() { _ = os.Remove(tmpPath) }()
@@ -879,8 +865,7 @@ Examples:
// Write current value to temp file // Write current value to temp file
if _, err := tmpFile.WriteString(currentValue); err != nil { if _, err := tmpFile.WriteString(currentValue); err != nil {
_ = tmpFile.Close() _ = tmpFile.Close()
fmt.Fprintf(os.Stderr, "Error writing to temp file: %v\n", err) FatalErrorRespectJSON("writing to temp file: %v", err)
os.Exit(1)
} }
_ = tmpFile.Close() _ = tmpFile.Close()
@@ -891,16 +876,14 @@ Examples:
editorCmd.Stderr = os.Stderr editorCmd.Stderr = os.Stderr
if err := editorCmd.Run(); err != nil { if err := editorCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running editor: %v\n", err) FatalErrorRespectJSON("running editor: %v", err)
os.Exit(1)
} }
// Read the edited content // Read the edited content
// #nosec G304 -- tmpPath was created earlier in this function // #nosec G304 -- tmpPath was created earlier in this function
editedContent, err := os.ReadFile(tmpPath) editedContent, err := os.ReadFile(tmpPath)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error reading edited file: %v\n", err) FatalErrorRespectJSON("reading edited file: %v", err)
os.Exit(1)
} }
newValue := string(editedContent) newValue := string(editedContent)
@@ -913,8 +896,7 @@ Examples:
// Validate title if editing title // Validate title if editing title
if fieldToEdit == "title" && strings.TrimSpace(newValue) == "" { if fieldToEdit == "title" && strings.TrimSpace(newValue) == "" {
fmt.Fprintf(os.Stderr, "Error: title cannot be empty\n") FatalErrorRespectJSON("title cannot be empty")
os.Exit(1)
} }
// Update the issue // Update the issue
@@ -941,14 +923,12 @@ Examples:
_, err := daemonClient.Update(updateArgs) _, err := daemonClient.Update(updateArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error updating issue: %v\n", err) FatalErrorRespectJSON("updating issue: %v", err)
os.Exit(1)
} }
} else { } else {
// Direct mode // Direct mode
if err := store.UpdateIssue(ctx, id, updates, actor); err != nil { if err := store.UpdateIssue(ctx, id, updates, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error updating issue: %v\n", err) FatalErrorRespectJSON("updating issue: %v", err)
os.Exit(1)
} }
markDirtyAndScheduleFlush() markDirtyAndScheduleFlush()
} }
@@ -977,8 +957,7 @@ var closeCmd = &cobra.Command{
// --continue only works with a single issue // --continue only works with a single issue
if continueFlag && len(args) > 1 { if continueFlag && len(args) > 1 {
fmt.Fprintf(os.Stderr, "Error: --continue only works when closing a single issue\n") FatalErrorRespectJSON("--continue only works when closing a single issue")
os.Exit(1)
} }
// Resolve partial IDs first // Resolve partial IDs first
@@ -988,13 +967,11 @@ var closeCmd = &cobra.Command{
resolveArgs := &rpc.ResolveIDArgs{ID: id} resolveArgs := &rpc.ResolveIDArgs{ID: id}
resp, err := daemonClient.ResolveID(resolveArgs) resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err) FatalErrorRespectJSON("resolving ID %s: %v", id, err)
os.Exit(1)
} }
var resolvedID string var resolvedID string
if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { if err := json.Unmarshal(resp.Data, &resolvedID); err != nil {
fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
os.Exit(1)
} }
resolvedIDs = append(resolvedIDs, resolvedID) resolvedIDs = append(resolvedIDs, resolvedID)
} }
@@ -1002,8 +979,7 @@ var closeCmd = &cobra.Command{
var err error var err error
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args) resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) FatalErrorRespectJSON("%v", err)
os.Exit(1)
} }
} }