cmdutil

package
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package cmdutil provides shared infrastructure for command implementations: the Factory for dependency injection, typed errors for centralized classification, build metadata extraction, and flag helpers. Command packages import cmdutil to access these building blocks; cmdutil itself has no knowledge of individual commands.

Index

Constants

View Source
const (
	// FlagCategoryRequired is the flag category for mandatory flags. The user
	// must supply these for the command to succeed.
	FlagCategoryRequired = "Required"

	// FlagCategorySupplemental is the flag category for optional flags that
	// modify command behavior but are not strictly necessary.
	FlagCategorySupplemental = "Supplemental"
)
View Source
const DefaultLimit = 20

DefaultLimit is the default result limit for all list operations. Commands set this as the Value on their --limit IntFlag so the help text displays the correct default and the flag destination is pre-populated.

Variables

View Source
var ErrInvalidLimit = errors.New("--limit must be a positive integer; use --no-limit for unbounded results")

ErrInvalidLimit indicates that a non-positive value was provided for the --limit flag. The value must be a positive integer. Callers should use --no-limit to request unbounded results instead.

View Source
var ErrSilent = errors.New("silent error")

ErrSilent is a sentinel error indicating the error message has already been printed to the user. The top-level error handler should exit with a non-zero code but not print the error again. Commands use this after displaying a formatted error message directly to IOStreams.ErrOut.

Functions

func AvailableTitleWidth

func AvailableTitleWidth(termWidth, overhead int) int

AvailableTitleWidth calculates how many columns the title may occupy given the terminal width and the overhead consumed by non-title columns and tab padding. Returns 0 when termWidth is 0 (non-TTY), signaling that callers should not truncate. Returns at least minTitleWidth when the terminal is very narrow, so the title remains somewhat readable.

func ColorBlockedText

func ColorBlockedText(cs *iostreams.ColorScheme, text string) string

ColorBlockedText applies the canonical "blocked" color (134, purple) to arbitrary text.

func ColorEmpty

func ColorEmpty(cs *iostreams.ColorScheme, text string) string

ColorEmpty applies the "empty" color (dark gray, 238) to text. Use this for zero-count indicators in dashboard-style output.

func ColorSecondaryText

func ColorSecondaryText(cs *iostreams.ColorScheme, s domain.SecondaryState, text string) string

ColorSecondaryText applies the canonical color for a secondary state to arbitrary text. Use this when the text is not the secondary state name itself — e.g., wrapping a label like "[blocked]" in the blocked color.

func ColorState

func ColorState(cs *iostreams.ColorScheme, state domain.State) string

ColorState applies the canonical ANSI color to a primary state string. Use this when rendering a bare state label outside of FormatState or FormatDetailState — e.g., blocker state annotations in the show command.

func ColorStateText

func ColorStateText(cs *iostreams.ColorScheme, state domain.State, text string) string

ColorStateText applies the canonical color for the given primary state to arbitrary text. Use this when the text is not the state name itself — e.g., coloring a numeric count with the state's color.

func FormatBlockerSuffix

func FormatBlockerSuffix(blockerIDs []string) string

FormatBlockerSuffix formats blocker IDs for display after the issue title in text output. Shows at most maxBlockerDisplay IDs; appends "…" when truncated. Accepts pre-formatted string IDs from the service-layer DTO.

func FormatDetailState

func FormatDetailState(cs *iostreams.ColorScheme, primary domain.State, details []domain.SecondaryState) string

FormatDetailState formats a primary state with multiple secondary conditions for detail views. Returns "primary (sec1, sec2)" when secondary conditions are present, or just "primary" when the slice is empty or nil. Both the primary state and each secondary condition are individually colored.

func FormatJSONTimestamp

func FormatJSONTimestamp(t time.Time) string

FormatJSONTimestamp formats t as a UTC timestamp with millisecond precision and a Z suffix, suitable for JSON output. Returns the empty string when t is the zero value, so callers can use omitzero struct tags to suppress it.

func FormatState

func FormatState(cs *iostreams.ColorScheme, primary domain.State, secondary domain.SecondaryState) string

FormatState formats a primary state with a single secondary state for list views. Returns "primary (secondary)" when a secondary state is present, or just "primary" when secondary is SecondaryNone. Both the primary and secondary state texts are colored according to the unified state palette.

func NewTracker

func NewTracker(f *Factory) (driving.Service, error)

NewTracker constructs the application service from the Factory's Store connection. This is a convenience function that prevents boilerplate in every command — the Factory provides the architecture-neutral database connection; this function wires it into the application layer.

func ParseLabelFilters

func ParseLabelFilters(raw []string) ([]driving.LabelFilterInput, error)

ParseLabelFilters converts a slice of "key:value" strings into service-layer LabelFilterInput DTOs. A wildcard value ("*") matches any value for the key and is represented by an empty Value field. Negation ("!key:value") is indicated by a true Negate field.

func ParseLabels

func ParseLabels(raw []string) ([]driving.LabelInput, error)

ParseLabels parses a slice of "key:value" strings into service-layer LabelInput DTOs. Validation of the key and value content is deferred to the service layer, which constructs domain Label values internally.

func ParseOrderBy

func ParseOrderBy(s string) (driving.OrderBy, error)

ParseOrderBy converts a user-supplied sort string into a service-layer OrderBy value. Valid inputs: "priority" (default when empty), "created", "modified". Matching is case-insensitive.

func ResolveLimit

func ResolveLimit(limitFlag int, noLimitFlag bool) (int, error)

ResolveLimit determines the effective limit for list operations based on the --limit flag and the --no-limit flag.

The --limit flag's default value should be set to DefaultLimit at command construction time, so the limitFlag always carries a meaningful positive value when the user does not override it.

Precedence: --no-limit (unlimited) > --limit N (explicit positive). A non-positive limitFlag value is rejected with ErrInvalidLimit.

func TruncateTitle

func TruncateTitle(title string, maxWidth int) string

TruncateTitle truncates a title string to fit within maxWidth columns. If the title fits, it is returned unchanged. If it exceeds maxWidth, the last visible character is replaced with "…" (U+2026). When maxWidth is zero or negative, the title is returned unchanged — this signals a non-TTY environment where no truncation should occur.

func WordWrap

func WordWrap(text string, width int) string

WordWrap wraps text to the given width at word boundaries. Existing newlines are preserved. When width is 0, the text is returned unchanged (no wrapping). Words longer than width are not broken.

func WriteJSON

func WriteJSON(w io.Writer, v any) error

WriteJSON writes v as indented JSON to w, followed by a newline. This is the standard JSON output format for all commands.

Types

type BuildInfo

type BuildInfo struct {
	// VCS is the version control system name (e.g., "git").
	VCS string

	// Revision is the full commit identifier at which the binary was built.
	Revision string

	// Time is the commit timestamp in RFC 3339 format.
	Time string

	// Dirty is true when the working tree had uncommitted changes at build time.
	Dirty bool
}

BuildInfo holds version-control metadata embedded by the Go toolchain at build time. The zero value represents an unknown build — all string fields are empty and Dirty is false.

func ReadBuildInfo

func ReadBuildInfo() BuildInfo

ReadBuildInfo extracts VCS metadata from the running binary's embedded build information. Returns a zero-value BuildInfo when the information is unavailable (e.g., when running via `go run` or in tests).

type ExitCode

type ExitCode int

ExitCode represents the process exit code returned to the operating system. Each value has a specific semantic meaning per §9 of the specification, allowing scripts, CI systems, and AI agents to distinguish between different failure modes.

const (
	// ExitOK indicates the command completed successfully.
	ExitOK ExitCode = 0

	// ExitError indicates a general or unexpected error.
	ExitError ExitCode = 1

	// ExitNotFound indicates the requested entity (issue, comment, etc.)
	// does not exist.
	ExitNotFound ExitCode = 2

	// ExitClaimConflict indicates the issue is already claimed and the
	// claim is not stale, or the claim ID does not match.
	ExitClaimConflict ExitCode = 3

	// ExitValidation indicates invalid input (bad flags, missing required
	// fields, constraint violations).
	ExitValidation ExitCode = 4

	// ExitDatabase indicates a database error (corruption, locked, etc.).
	ExitDatabase ExitCode = 5

	// ExitPending indicates the operation was accepted but is not yet
	// complete. Used for asynchronous workflows.
	ExitPending ExitCode = 8
)

func ClassifyError

func ClassifyError(stderr io.Writer, err error, signalCancelIsError bool) ExitCode

ClassifyError maps a command error to an exit code and prints appropriate messages to stderr. This is the single place where error types are translated into user-visible behavior and exit codes.

The signalCancelIsError parameter controls whether context.Canceled (typically from SIGINT/SIGTERM via signal.NotifyContext) produces a non-zero exit code. When false, signal cancellation is treated as graceful shutdown (exit 0) — the correct default for Kubernetes-deployed services where SIGTERM is a normal lifecycle event.

Uses Go 1.26's errors.AsType for type-safe, generic error classification — no need for a target variable declaration before the call.

type Factory

type Factory struct {
	// AppName is the application binary name, used in version output and
	// user-facing messages.
	AppName string

	// AppVersion is the build version, injected at compile time via -ldflags
	// or set to "dev" during development.
	AppVersion string

	// BuildInfo holds version-control metadata (VCS name, commit, timestamp)
	// embedded by the Go toolchain at build time.
	BuildInfo BuildInfo

	// IOStreams provides abstracted I/O with TTY awareness and color control.
	// Constructed eagerly because it is needed by almost every command and
	// has no expensive initialization.
	IOStreams *iostreams.IOStreams

	// Workspace is the explicit workspace directory path, set via the
	// --workspace global flag or the NP_WORKSPACE environment variable. When
	// non-empty, database discovery uses this directory directly instead of
	// walking up from the current working directory. The flag takes
	// precedence over the environment variable.
	Workspace string

	// DatabasePath returns the absolute path to the database file, using
	// workspace-aware discovery. When Workspace is set, it checks that
	// directory directly via LookupDatabase; otherwise it walks up from the
	// current working directory via DiscoverDatabase. Commands that need the
	// .np/ directory path (for backup files, reset-key hashes, or path
	// reporting) should call this rather than invoking DiscoverDatabase
	// directly, which would bypass the --workspace override.
	DatabasePath func() (string, error)

	// Store returns the SQLite database connection. Constructed lazily on
	// first access — database discovery and opening happen at that point.
	// Commands that need the application service construct it from this
	// connection via cmdutil.NewTracker.
	Store func() (*sqlite.Store, error)

	// SignalCancelIsError controls whether signal-triggered context
	// cancellation (SIGINT, SIGTERM) produces a non-zero exit code.
	//
	// Long-running processes — HTTP servers, Kafka consumers, queue workers —
	// receive SIGTERM as a routine part of their lifecycle (e.g., Kubernetes
	// pod eviction, rolling deploys). For these, signal cancellation is
	// expected and should exit cleanly with code 0, so leave this false (the
	// default).
	//
	// Short-lived processes — CLI tools, Kubernetes Jobs, cron jobs — run to
	// completion under normal conditions. A signal interrupting one of these
	// typically means something went wrong (user abort, preemption, timeout),
	// so set this to true to surface the interruption as a non-zero exit code.
	SignalCancelIsError bool
}

Factory provides lazy-loaded, substitutable dependencies to commands. Function fields are not called until a command actually needs the dependency, and they can be replaced in tests with trivial stubs or panicking functions to catch accidental dependency usage.

Eager fields (like IOStreams) are cheap to construct and used by virtually every command. As the application grows, expensive dependencies (HTTP clients, database pools) are added as function-typed fields whose cost is deferred until actual use.

Function fields also serve as live configuration providers for long-running services. When a Factory function closes over a mutable configuration source (file watcher, K8s ConfigMap, Vault lease), callers that invoke the function per unit of work — per HTTP request, per batch iteration, per queue message — always receive resources built from the current configuration. This enables credential rotation, feature flag updates, and connection pool recycling without process restarts. Short-lived CLI commands can safely call Factory functions once; the pattern adds no overhead but does not provide its full benefit in that context.

type FlagError

type FlagError struct {
	Err error
}

FlagError indicates invalid flag usage or flag combination. The top-level error handler should print the error along with the command's usage text to help the user correct their invocation.

func FlagErrorf

func FlagErrorf(format string, args ...any) *FlagError

FlagErrorf creates a FlagError with a formatted message. Commands use this when flag validation fails — the top-level error handler will print the error along with usage text to guide the user.

func (*FlagError) Error

func (e *FlagError) Error() string

Error returns the underlying error message.

func (*FlagError) Unwrap

func (e *FlagError) Unwrap() error

Unwrap returns the underlying error for use with errors.Is and errors.As.

type IDResolver

type IDResolver struct {
	// contains filtered or unexported fields
}

IDResolver resolves issue ID strings that may be either full IDs (PREFIX-random) or bare random parts (just the 5-char Crockford string). It caches the database prefix on first use so that subsequent calls do not require additional database round-trips.

func NewIDResolver

func NewIDResolver(svc driving.Service) *IDResolver

NewIDResolver creates an IDResolver backed by the given driving. The prefix is fetched lazily on the first call to Resolve.

func (*IDResolver) Resolve

func (r *IDResolver) Resolve(ctx context.Context, raw string) (domain.ID, error)

Resolve parses an issue ID string. If the string is a full ID (contains a separator), it is parsed directly. If it is a bare random part, the database prefix is fetched (and cached) and prepended before parsing.

type ListItemOutput

type ListItemOutput struct {
	ID             string   `json:"id"`
	Role           string   `json:"role"`
	State          string   `json:"state"`
	SecondaryState string   `json:"secondary_state,omitempty"`
	DisplayStatus  string   `json:"display_status"`
	Priority       string   `json:"priority"`
	Title          string   `json:"title"`
	BlockerIDs     []string `json:"blocker_ids,omitempty"`
	CreatedAt      string   `json:"created_at"`
}

ListItemOutput is the JSON representation of a single issue in a list. It is shared by the list, ready, and blocked commands so that their --json output is structurally identical.

func ConvertListItems

func ConvertListItems(items []driving.IssueListItemDTO) []ListItemOutput

ConvertListItems transforms a slice of service-layer IssueListItemDTO values into the shared JSON output representation.

type ListOutput

type ListOutput struct {
	Items   []ListItemOutput `json:"items"`
	HasMore bool             `json:"has_more"`
}

ListOutput is the JSON representation of a list command result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL