Documentation
¶
Overview ¶
Package cmdutil holds cross-cutting helpers shared by forge's own CLI across MORE THAN ONE command group (internal/cli and its dir-nested subpackages). It is a leaf package — it imports only neutral internal packages — so any command group can depend on it without an import cycle back to internal/cli.
Helpers used by a single command stay with that command; trivial stdlib wrappers (a one-line os.Stat check) are duplicated locally rather than shared. Only genuinely shared logic lives here.
Index ¶
- Constants
- Variables
- func AddPackageScaffoldFlags(cmd *cobra.Command)
- func CmdRouteTokens() ([]string, bool)
- func ConfiguresClone(cmd *cobra.Command, readOnlyFlags ...string) *cobra.Command
- func ConfiguresCloneFor(cmd *cobra.Command) bool
- func CountLineStartScaffoldMarkers(data []byte) int
- func FindProjectRoot() (string, error)
- func HideDevFlags(cmd *cobra.Command, names ...string)
- func HoldHeavySlot(ctx context.Context, stderr io.Writer) (release func(), err error)
- func HoldsOwnHeavySlot() bool
- func IsDryRun(cmd *cobra.Command) bool
- func IsMarkerScannable(name string) bool
- func Name() string
- func ProjectDirOverride() (string, bool)
- func ProjectRoot() (string, error)
- func RecordCmdRoute(forgeRoot *cobra.Command)
- func ReservedServiceNameList() []string
- func ResetCmdRoute()
- func ResolutionRoot() (string, error)
- func SetProjectDir(dir string) error
- func StrictGroup(cmd *cobra.Command) *cobra.Command
- func ValidateFrontendName(name string) error
- func ValidateIdentifier(name string) error
- func ValidateImportablePackageName(name string) error
- func ValidateProjectName(name string) error
- func ValidateServiceDirConsistency(protoServiceName, dirName string) error
- func ValidateServiceName(name string) error
Constants ¶
const ConfiguresCloneAnnotation = "forge.configures-clone"
ConfiguresCloneAnnotation marks a command that writes the files forge's per-clone git setup exists for: generated files, and forge's committed state under .forge/. Only such a command (or a subcommand of one) may write the clone's git configuration as a side effect — the generated-file merge driver (.git/config, info/attributes) and core.hooksPath.
Opt-in, not opt-out: most forge commands only read the project (env status, env render, env deploy --dry-run) or have nothing to do with it (slot run), and a command added tomorrow must not start rewriting .git/config because nobody remembered to exempt it. .git/config is shared by every linked worktree of the clone, so a write there is visible to, and races with, every agent working in any of them.
The annotation's value is a comma-separated list of boolean flags that make a run of the command read-only (generate's --check and --plan). --dry-run needs no listing: on any command that has it, a dry run is a promise to write nothing, git configuration included (DryRunFlag).
const DryRunFlag = "dry-run"
DryRunFlag is the flag every forge command spells a dry run with. Set, it makes the run read-only for ConfiguresCloneFor whatever the annotation lists: `forge scaffold --dry-run` used to enable extensions.worktreeConfig and write the merge driver into .git/config, then print "nothing written".
Variables ¶
var ErrProjectConfigNotFound = errors.New("forge.yaml not found in current directory (run 'forge project new' to create a project)")
ErrProjectConfigNotFound is returned when forge.yaml does not exist. The canonical sentinel lives here (the shared leaf package) so both internal/cli and the dir-nested command groups compare against the same value; internal/cli's config.ErrProjectConfigNotFound aliases this.
var GoKeywords = map[string]bool{ "break": true, "case": true, "chan": true, "const": true, "continue": true, "default": true, "defer": true, "else": true, "fallthrough": true, "for": true, "func": true, "go": true, "goto": true, "if": true, "import": true, "interface": true, "map": true, "package": true, "range": true, "return": true, "select": true, "struct": true, "switch": true, "type": true, "var": true, }
GoKeywords is the set of Go reserved keywords.
var GoPredeclaredIdentifiers = map[string]bool{ "bool": true, "byte": true, "complex64": true, "complex128": true, "error": true, "float32": true, "float64": true, "int": true, "int8": true, "int16": true, "int32": true, "int64": true, "rune": true, "string": true, "uint": true, "uint8": true, "uint16": true, "uint32": true, "uint64": true, "uintptr": true, "any": true, "comparable": true, "true": true, "false": true, "iota": true, "nil": true, "append": true, "cap": true, "close": true, "complex": true, "copy": true, "delete": true, "imag": true, "len": true, "make": true, "new": true, "panic": true, "print": true, "println": true, "real": true, "recover": true, "min": true, "max": true, "clear": true, }
GoPredeclaredIdentifiers is the set of Go predeclared types, constants, zero value, and builtin functions.
var ReservedServiceNames = map[string]bool{ "worker": true, "scheduler": true, "cron": true, "job": true, }
ReservedServiceNames are names that conflict with forge's worker/scheduler subsystems. Using them as HTTP Connect service names causes confusion.
Functions ¶
func AddPackageScaffoldFlags ¶ added in v1.0.0
AddPackageScaffoldFlags registers the flags every package-scaffolding verb shares (`forge package new`, `forge scaffold package`, `forge scaffold adapter`), so the three spellings cannot drift apart.
- --base-url opts an adapter into a config block with one base_url leaf, bound to <NAME>_BASE_URL; --base-url-env names that env var instead (and implies --base-url). An adapter talks to SOME downstream, not necessarily one HTTP API with a base URL (a queue, a vendor SDK, a storage gateway), so the leaf is asked for, never assumed.
- --dry-run prints what the scaffold would write and changes nothing.
func CmdRouteTokens ¶ added in v0.1.45
CmdRouteTokens returns the recorded mount route (subcommand tokens from the executing binary to forge's root) and whether one was recorded this process.
func ConfiguresClone ¶ added in v1.0.0
ConfiguresClone marks cmd, and through it every subcommand, as one that may set up the clone's git configuration — unless one of readOnlyFlags is set on the run. It returns cmd for chaining.
func ConfiguresCloneFor ¶ added in v1.0.0
ConfiguresCloneFor reports whether this run of cmd may set up the clone's git configuration: cmd or one of its parents carries ConfiguresCloneAnnotation, and none of the read-only flags it names is set.
func CountLineStartScaffoldMarkers ¶
CountLineStartScaffoldMarkers counts line-start `// FORGE_SCAFFOLD:` and `# FORGE_SCAFFOLD:` markers in data. (The lint analyzer is Go-only, but audit/map are allowed to span more file types — hence both comment styles.)
func FindProjectRoot ¶
FindProjectRoot walks upward from the resolution root (--project-dir when set, else the cwd) looking for a forge.yaml. Returns the directory or "" when no project is found. Mirrors the loadProjectConfig walk-up behavior in config.go.
func HideDevFlags ¶
HideDevFlags is the user-vs-maintainer CLI surface split, shared by `forge generate` (internal/cli) and `forge lint` (internal/cli/lint).
Commands like `forge lint` and `forge generate` accumulate flags for two very different audiences: project users (the 5-7 flags they actually reach for) and forge maintainers / debugging agents (wiring audits, pipeline narrowing, migration escape hatches). Showing all of them in --help buries the user-facing surface. HideDevFlags marks the named flags Hidden — fully functional, just invisible in --help — and adds a visible --help-dev flag that lists exactly the hidden set, so the flags stay discoverable in one place.
Must be called after cmd.RunE and all flags are set. Panics on an unknown flag name: that's a programmer error at command-construction time, and panicking keeps a typo from silently leaving a dev flag visible.
It lives here in the shared leaf so the dir-nested lint group reaches it without importing internal/cli (which would cycle — internal/cli blank-imports the groups).
func HoldHeavySlot ¶ added in v0.1.46
HoldHeavySlot takes a host-wide heavy slot (pkg/hostslot) for a forge command that does heavy work in its own process — `forge generate`, `forge lint` — and returns the release. While every slot is held it waits, printing who holds them to stderr.
Only a cancelled context fails the command. A slot directory forge cannot create or lock is a broken limiter, not a reason to refuse to lint or generate: the command warns once and runs unthrottled.
func HoldsOwnHeavySlot ¶ added in v1.0.0
func HoldsOwnHeavySlot() bool
HoldsOwnHeavySlot reports whether this process holds a heavy slot of its own right now: the host-wide limiter, not the tool, is what bounds how many heavy jobs run beside this one. False when the limit is off, when the slot was inherited from a parent (the parent's slot may be shared by several children), and when acquisition failed and the command runs unthrottled.
A tool with its own machine-global mutex (golangci-lint's lock) can drop it while this is true: the slot already bounds the concurrency the mutex was protecting, and keeping both serializes every lint on the machine behind one lock while each waiter holds a slot.
func IsDryRun ¶ added in v1.0.0
IsDryRun reports whether this run of cmd was asked for with --dry-run.
func IsMarkerScannable ¶
IsMarkerScannable identifies file types whose FORGE_SCAFFOLD markers are real unfilled placeholders rather than documentation references. Markdown and JSON commonly cite the marker syntax in prose / fixtures, so they're excluded — those occurrences would otherwise generate noisy "scaffold present" warnings on every project that documents how scaffolds work.
func Name ¶
func Name() string
Name returns the command name users should type to invoke Forge. It prefers the recorded mount route (RecordCmdRoute): a standalone binary reports its own basename (users literally type that name), an embedded mount reports "<basename> forge" (e.g. "reliant forge"). Without a recording it falls back to the legacy basename heuristic. Shared so group commands can print copy-pasteable next-step hints without importing internal/cli.
func ProjectDirOverride ¶ added in v0.1.45
ProjectDirOverride reports the installed resolution root, if any.
func ProjectRoot ¶
ProjectRoot finds the project root by looking for forge.yaml in the resolution root — --project-dir when set, else the cwd (NOT a walk-up; see FindProjectRoot for that). Returns a user-facing error when forge.yaml is absent from that directory.
func RecordCmdRoute ¶ added in v0.1.45
RecordCmdRoute walks from forge's root command up to the executed tree's real root and records the subcommand tokens between them. Called by the forge root PersistentPreRun on every invocation.
func ReservedServiceNameList ¶ added in v0.1.45
func ReservedServiceNameList() []string
ReservedServiceNameList returns the reserved names in stable order, for error messages and help text. Nothing else enumerates them, and a name that is rejected without the set being visible sends people guessing — `job` in particular is the natural noun for a whole class of domains (field service, construction, scheduling, print, logistics).
func ResetCmdRoute ¶ added in v0.1.45
func ResetCmdRoute()
ResetCmdRoute clears the recorded route. Test hook only — production code records exactly once per process, at dispatch.
func ResolutionRoot ¶ added in v0.1.45
ResolutionRoot returns the directory project lookup should start from: the --project-dir override when one is installed, otherwise os.Getwd(). This is the single seam every project-locating helper goes through.
func SetProjectDir ¶ added in v0.1.45
SetProjectDir installs dir as the resolution root for subsequent project lookups. A relative dir resolves against the current working directory. An empty dir clears the override, restoring os.Getwd() resolution.
It rejects a path that is not an existing directory, so a mistyped -C fails loudly here rather than degrading into a confusing "no project found" (or, worse, a silent fallback to "." at one of the call sites that ignores the lookup error).
func StrictGroup ¶ added in v0.1.45
StrictGroup makes a command-GROUP (a command that exists only to host subcommands) reject an unrecognised subcommand instead of reporting success.
Cobra's default for a non-runnable parent is to print help and exit 0 for ANY leftover argument: `forge db bogus`, `forge env bogus`, a renamed verb typed with its old spelling — all printed a help page and exited 0. For a human that reads as "nothing happened"; for a scripted agent it reads as "the command succeeded", and the run continues on top of work that was never done. That is the same defect as a success message that proves nothing, just spelled with an exit code.
StrictGroup gives the group an Args validator (which makes Runnable() true, so cobra actually validates the leftover args) and a RunE that prints help for the bare form. Bare `forge db` still prints help and exits 0 — the user asked for the menu and got it. `forge db bogus` now exits non-zero naming the offending token, plus a "did you mean" line when one of the group's own subcommands is close.
Every group in the tree must go through here; TestEveryCommandGroupRejects UnknownSubcommands (internal/cli) walks the assembled tree and fails on any parent command that still carries cobra's permissive default.
func ValidateFrontendName ¶
ValidateFrontendName checks that a frontend name is filesystem-safe.
func ValidateIdentifier ¶
ValidateIdentifier checks that a name is valid for use as a service, worker, or operator name. Hyphens and underscores are allowed in the display name; templates use snakeCase/pascalCase helpers to convert when a Go identifier is needed (e.g. "admin-server" / "admin_server" -> package "admin_server" and field "AdminServer" — snake_case is the canonical on-disk form post-2026-06-08). The leading-character and reserved-word rules match ValidateProjectName so all top-level scaffold names share one shape.
func ValidateImportablePackageName ¶ added in v0.1.45
ValidateImportablePackageName rejects a component name whose Go package cannot be imported. A service, worker, operator or library becomes a package that other code imports — internal/app imports every component — and Go refuses to import package main ("is a program, not an importable package"). Without this check the name is accepted and the project fails at go build, far from the command that chose it. This is a Go language rule, not a forge reservation, so it is the only name checked here.
func ValidateProjectName ¶
ValidateProjectName checks that a project name is valid for use as a directory name and in Go module paths. Hyphens are allowed since they are valid in module paths and directory names; templates use snakeCase/pascalCase helpers to convert when a Go identifier is needed.
func ValidateServiceDirConsistency ¶ added in v0.1.45
ValidateServiceDirConsistency checks logical service identity, not Go casing. Connect identifiers are derived from the authoritative proto spelling; Forge's own Mount/NewTest names are independent, idiomatic Go identifiers. Separators, word boundaries and initialism casing therefore need not match.
func ValidateServiceName ¶
ValidateServiceName checks that a name is valid for a service and not a reserved service name. For background workers use 'forge scaffold worker <name>'.
Types ¶
This section is empty.