Help us shape the future of Go! Take the 2026 Go Developer Survey

cmdkit

package
v1.0.5 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package cmdkit is the paved path for forge's non-server binary shapes: CLI subcommands, one-shot admin tools, and standalone long-running binaries that ship in the same image as the Connect server but are not themselves Connect services.

serverkit owns the server lifecycle (listener, readiness flip, worker supervision, graceful shutdown). Everything else — a `report` subcommand, a queue drainer, a backfill job, a reverse proxy — got nothing, so each one re-invented the same things by hand and inconsistently: a timeout literal, a freshly-built slog.Logger pointed at the wrong stream, hand-rolled DB open/ping, and fmt.Println for output. cmdkit centralizes exactly those four.

It deliberately centralizes NO input resolution. A command's inputs come from the generated typed config (config.Load) and its own flags; a helper here that took a variable NAME and read the process environment would let a command configure itself from a value the app declares nowhere — the exact thing the typed loader exists to prevent — while satisfying every lint, because the read would live in this package instead of the command's.

Design constraints:

  • Config-agnostic. The typed config struct (config.Config) is generated per-project in the *project's* module, so cmdkit cannot import it. Helpers take plain values (a DSN, a duration) or small option structs. A command loads config.Load(cmd) itself and passes the fields it needs — config stays the single typed source, cmdkit stays reusable across every project.
  • Driver-agnostic. OpenDB takes the database/sql driver name; cmdkit never imports a driver, so a project picks pgx / sqlite / etc. and blank-imports it as it already does.
  • stdout is for data, stderr is for diagnostics. Logger writes to stderr; PrintJSON writes machine-consumable output to stdout. A command's structured result can be piped to jq while its log lines stay out of the pipe.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContextWithLogger

func ContextWithLogger(ctx context.Context, opts LoggerOptions) (context.Context, *slog.Logger)

ContextWithLogger builds a Logger from opts and stores it on ctx via observe.WithLogger, returning both. Downstream code recovers it with observe.FromContext(ctx) (re-exported here as LoggerFromContext) — no extra parameter threaded through call signatures.

func ContextWithTimeout

func ContextWithTimeout(parent context.Context, d, fallback time.Duration) (context.Context, context.CancelFunc)

ContextWithTimeout derives a timeout context from parent. When d is non-positive the fallback is used instead — so a command can pass a config-sourced duration that defaults to zero and still get a sane bound without an inline `if d == 0` at every call site. It replaces the scattered context.WithTimeout(cmd.Context(), 60*time.Second) literals that disagreed across commands.

parent may be nil; context.Background() is substituted.

func Logger

func Logger(opts LoggerOptions) *slog.Logger

Logger builds a *slog.Logger for a CLI command or standalone binary.

It is the one-line replacement for the ad-hoc slog.New(slog.NewJSONHandler(os.Stderr, nil)) that every command was rebuilding — and it fixes the recurring bug of logging to os.Stdout, which corrupts piped data output. Pass the binary/subcommand name so records carry a "cmd" attribute.

To honour a project's config, source Level/Format from config.Load:

cfg, _ := config.Load(cmd)
log := cmdkit.Logger(cmdkit.LoggerOptions{
    Name:   "pnl-report",
    Format: cfg.LogFormat,
    Level:  cmdkit.ParseLevel(cfg.LogLevel),
})

func LoggerFromContext

func LoggerFromContext(ctx context.Context) *slog.Logger

LoggerFromContext returns the request/command-scoped logger stored on ctx, or slog.Default() if none. It is a thin re-export of observe.FromContext so a command package depends on cmdkit alone.

func OpenDB

func OpenDB(ctx context.Context, opts DBOptions) (*sql.DB, error)

OpenDB opens a *sql.DB, applies pool tuning, and (unless SkipPing) verifies connectivity with a bounded ping. It is the single replacement for the per-command sql.Open + PingContext blocks that every report/backfill command was copying.

The returned *sql.DB is the caller's to close (defer db.Close()). On a ping failure the pool is closed before returning, so a failed OpenDB never leaks a half-open pool.

func ParseLevel

func ParseLevel(s string) slog.Level

ParseLevel converts a config log-level string ("debug", "info", "warn", "error", case-insensitive) to a slog.Level. Unrecognized or empty values map to slog.LevelInfo — a CLI tool should stay loud enough to be useful rather than silently dropping to a stricter level on a typo.

func PrintJSON

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

PrintJSON writes v as indented JSON followed by a newline to w. It is the replacement for hand-rolled json.NewEncoder(os.Stdout) + SetIndent blocks and for fmt.Println-based output. Write structured results to os.Stdout so they pipe cleanly to jq; keep human-facing chatter on the Logger (stderr).

func PrintVersion

func PrintVersion(w io.Writer, info BuildInfo)

PrintVersion writes a human-readable version block for info to w.

The Go toolchain version is read from the embedded build info rather than stamped, because the compiler already records it and a hand-passed value could disagree with the compiler that actually produced the binary. It is omitted entirely when unavailable (a binary built without module information) rather than printed as an empty or "unknown" line — a version command's whole job is to be trustworthy, so a field it cannot vouch for should be absent, not guessed.

Types

type BuildInfo

type BuildInfo struct {
	// Name is the binary's own name, printed first.
	Name string
	// Version is the release version ("dev" when unstamped).
	Version string
	// Commit is the source revision ("none" when unstamped).
	Commit string
	// Date is the build timestamp ("unknown" when unstamped).
	Date string
}

BuildInfo is what a binary knows about its own provenance.

The three string fields are populated at LINK time, not here: the build stamps them with `-ldflags "-X <pkg>.version=..."`, which can only target a variable in the project's own package. That is why this is a plain struct a command fills in rather than something cmdkit reads for itself — a `-X github.com/reliant-labs/forge/pkg/cmdkit.version=...` would stamp the library for every project at once, so the variables must stay in the project's tree and the FORMATTING is the only part that can be shared.

type DBOptions

type DBOptions struct {
	// Driver is the database/sql driver name (e.g. "pgx", "sqlite3").
	// The caller blank-imports the driver as usual; cmdkit never imports
	// one. Zero value defaults to "pgx" — the forge default.
	Driver string
	// DSN is the data source name / connection string. Required.
	DSN string
	// MaxOpenConns, when > 0, caps the open-connection pool. A one-shot
	// CLI tool usually wants a small number (or the default).
	MaxOpenConns int
	// MaxIdleConns, when > 0, caps idle connections kept in the pool.
	MaxIdleConns int
	// ConnMaxIdleTime, when > 0, bounds idle-connection lifetime.
	ConnMaxIdleTime time.Duration
	// ConnMaxLifetime, when > 0, bounds total connection reuse.
	ConnMaxLifetime time.Duration
	// PingTimeout bounds the open-time connectivity check. Zero value
	// defaults to 10s. The check fails fast on an unreachable DB rather
	// than letting the first real query hang — the fail-fast-on-setup
	// posture CLI tools want.
	PingTimeout time.Duration
	// SkipPing disables the open-time ping entirely. Use only when the
	// caller deliberately wants lazy connection (rare for CLI tools).
	SkipPing bool
}

DBOptions configures OpenDB. Only DSN is required; the rest tune the connection pool and the open-time ping.

type LoggerOptions

type LoggerOptions struct {
	// Name, when non-empty, is attached as a "cmd" attribute on every
	// record so log lines from different subcommands are distinguishable
	// in aggregate.
	Name string
	// Level is the minimum slog level. Zero value is slog.LevelInfo.
	Level slog.Level
	// Format selects the handler: "text" emits the text handler, any
	// other value (including "") emits JSON. Mirrors serverkit.Config's
	// LogFormat semantics so a server and its CLI tools log alike.
	Format string
	// Out is the destination. Zero value is os.Stderr — diagnostics
	// belong on stderr so stdout stays clean for piped data.
	Out io.Writer
}

LoggerOptions configures Logger. The zero value is a usable default: info level, JSON format, writing to stderr.

Jump to

Keyboard shortcuts

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