factory

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package factory carries the shared dependency set ("the factory") threaded through forge's own CLI command tree, plus the command REGISTRY that lets dir-nested command-group subpackages (internal/cli/add, internal/cli/lint, ...) attach to the root without a group↔root import cycle.

This mirrors the devspace/argo-cd idiom we ship in generated apps (internal/templates/project/cmd-tree-root.go.tmpl): a small package that owns Deps + a registry of CmdFactory funcs. Group subpackages import THIS package for Deps and self-register their command constructor via init(); the root package (internal/cli) blank-imports the groups so their init() runs, then ranges the registry to assemble the tree. The indirection is what keeps the dependency one-directional (group → factory), breaking the cycle that a direct group→root import would create.

Why a dedicated package (not internal/cli itself): the group subpackages need to import the factory/registry, and they ALSO need shared logic helpers that live in internal/cli (loadProjectStore, runGeneratePipeline, ...). If the registry lived in internal/cli, the groups importing it would import internal/cli — and internal/cli blank-imports the groups, a cycle. Pulling Deps + registry into their own leaf package breaks it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LoadProjectStore

func LoadProjectStore() (*projectstore.Store, error)

LoadProjectStore invokes the registered project-store loader directly. Most group commands take the loader off their *Factory (the testable path), but the lint group has ~80 free helper functions that read project config without a Factory in scope; this package-level entry lets them reach the one registered loader without threading a Factory through every signature. It is the same loader SetProjectStoreLoader installs.

func Register

func Register(c CmdFactory)

Register adds a top-level command factory to the registry. Group subpackages call this from their init() so a blank import of the group is enough to attach the command to the root.

func SetAuditAPI

func SetAuditAPI(a AuditAPI)

SetAuditAPI registers the audit surface. internal/cli calls this from an init() so it is set before any Factory is built.

func SetGenAPI

func SetGenAPI(g GenAPI)

SetGenAPI registers the codegen + registry surface. internal/cli calls this from an init() so it is set before any Factory is built.

func SetProjectStoreLoader

func SetProjectStoreLoader(load func() (*projectstore.Store, error))

SetProjectStoreLoader registers the project-store loader. internal/cli calls this from an init() so the loader is set before any Factory is built.

Types

type AuditAPI

type AuditAPI struct {
	// Ingress computes the ingress category: renders the dev-env KCL and
	// cross-checks routes against forge.yaml backends.
	Ingress func(cfg *config.ProjectConfig, projectDir string) audittype.Category

	// ExternalBuilds computes the external-builds category: renders dev-env
	// KCL, enumerates build_cmd services, cross-checks cwd / env-key
	// conflicts / recorded build state.
	ExternalBuilds func(cfg *config.ProjectConfig, projectDir string) audittype.Category

	// Prerequisites computes the declared-external-prerequisites category:
	// renders dev-env KCL, enumerates forge.ExternalSecret / forge.DNSRecord
	// declarations, and surfaces the checklist + the cross-secret byte-match
	// group consistency.
	Prerequisites func(cfg *config.ProjectConfig, projectDir string) audittype.Category

	// Friction computes the friction category (auditFriction lives in
	// internal/cli/friction.go alongside the `forge friction` command).
	Friction func(projectDir string) audittype.Category

	// LoadServiceRegistry / IsConnectServiceConfig / ServiceRegistryRelPath
	// mirror the GenAPI fields — the shape inventory + unregistered-service
	// findings read the registration view.
	LoadServiceRegistry    func(projectDir string) (ServiceRegistry, error)
	IsConnectServiceConfig func(c config.ComponentConfig) bool
	ServiceRegistryRelPath string

	// ListEnvs lists the project's declared environments (deploy/kcl/<env>).
	ListEnvs func(projectDir string) ([]string, error)

	// ProjectDefinesConnectServices reports whether the project declares any
	// Connect service (drives whether the shape category parses RPC counts).
	ProjectDefinesConnectServices func(projectDir string) bool

	// ScanProjectDriftPaths returns the project-relative paths of forge-
	// certified files whose embedded hash no longer matches (user-edited gen
	// files), for the codegen category.
	ScanProjectDriftPaths func(projectDir string, cs *generator.FileChecksums) []string

	// DisownFrictionReasons maps disowned-file path → recorded reason from
	// the friction log, for the codegen category's rationale backfill.
	DisownFrictionReasons func(projectDir string) map[string]string

	// LoadProjectStoreFrom loads a ProjectStore from an explicit forge.yaml
	// path (audit resolves the path itself rather than walking up).
	LoadProjectStoreFrom func(path string) (*projectstore.Store, error)
}

AuditAPI is the exported surface the `forge audit` command group depends on. The KCL-entity-typed categories return a finished audittype.Category computed cli-side, so the group never touches the KCL entity structs (kept in internal/cli where build/deploy/dev/doctor also use them).

type CmdFactory

type CmdFactory func(f *Factory) *cobra.Command

CmdFactory builds one top-level command from the shared factory. Group subpackages register a CmdFactory (their newXCmd) via Register; the root ranges the registry to assemble the tree.

func Registered

func Registered() []CmdFactory

Registered returns the registered command factories in registration order. The root command builder ranges this to AddCommand each one.

type Factory

type Factory struct {
	// Out is the writer for user-facing stdout output.
	Out io.Writer
	// Err is the writer for diagnostics / stderr output.
	Err io.Writer
	// In is the reader for interactive prompts.
	In io.Reader

	// LoadProjectStore loads forge.yaml (walking up from cwd) into a
	// ProjectStore — the single read+mutate surface. The heavy config-parsing
	// logic lives in internal/cli (config.go); the factory carries it as a
	// function value so group subpackages can read project config without
	// importing internal/cli (which would cycle: internal/cli blank-imports
	// the groups). internal/cli wires this in factory.New's caller via
	// SetProjectStoreLoader.
	LoadProjectStore func() (*projectstore.Store, error)

	// Gen is the clean, narrow surface over the generate pipeline + service
	// registry that the dir-nested `add` group calls. The heavy
	// implementation (the ~80-file generate pipeline, the services.go AST
	// parser) lives in internal/cli; the factory carries it as function
	// values so internal/cli/add can trigger codegen and read the
	// registration view without importing internal/cli (which would cycle —
	// internal/cli blank-imports the groups). internal/cli registers the
	// concrete implementation via SetGenAPI from its init().
	Gen GenAPI

	// Audit is the narrow surface the dir-nested `audit` group calls for the
	// categories it cannot compute without package-cli internals — the
	// KCL-entity-typed ingress / external-builds categories (which depend on
	// the KCL render + entity structs shared by ~12 cli files), the friction
	// roll-up (auditFriction lives in friction.go), plus the
	// service-registry / env-discovery / drift / connect-service helpers.
	// Each field keeps the heavy impl in internal/cli; the group gets
	// neutral results (audittype.Category, []string, …). internal/cli
	// registers the concrete implementation via SetAuditAPI from its init().
	Audit AuditAPI
}

Factory is the shared dependency set carried into command constructors so commands are testable (writers can be redirected) and root setup lives in one place. It is intentionally small: forge's command logic calls package-level helpers in internal/cli directly, so the factory carries the I/O surface and is the vehicle for the registration pattern rather than a god-object of injected collaborators.

func New

func New() *Factory

New returns a Factory wired to the real process streams and the registered project-store loader. Tests construct a Factory literal with bytes.Buffer fields (and their own loader) instead.

type GenAPI

type GenAPI struct {
	// RunPipeline runs the FULL generate pipeline for the project rooted at
	// projectDir (the equivalent of `forge generate`), serialized under the
	// internal/cli generate mutex. Used by `add service` / `add operator`.
	RunPipeline func(projectDir string) error

	// RunPipelineBootstrapOnly runs the generate pipeline narrowed to the
	// "bootstrap-only" step preset (regenerates pkg/app/{bootstrap,testing,
	// migrate}.go and nothing else), serialized under the generate mutex.
	// Used by `add worker`.
	RunPipelineBootstrapOnly func(projectDir string) error

	// LoadServiceRegistry parses the user-owned pkg/app/services.go and
	// returns the registration view. Used by `add service` / `add webhook` /
	// `add rpc` to print accurate registration nudges and to gate the
	// types-only (tombstoned) case.
	LoadServiceRegistry func(projectDir string) (ServiceRegistry, error)

	// ServiceRegistryRelPath is the project-relative path of the user-owned
	// registration file (pkg/app/services.go), surfaced so the group's
	// messages name it without duplicating the constant.
	ServiceRegistryRelPath string

	// IsConnectServiceConfig reports whether a component is a Connect
	// service (vs worker/cron/operator/binary). Used by `add webhook` to
	// gate the registration nudge to serving binaries.
	IsConnectServiceConfig func(c config.ComponentConfig) bool

	// WriteScenariosIndex regenerates the frontend mock-scenario barrel
	// (scenarios/index). Used by `add scenario`.
	WriteScenariosIndex func(scenariosDir string) error

	// RunPackageNew is the `forge package new` RunE, reused verbatim by
	// `add package` and `add adapter` (which pre-sets --type).
	RunPackageNew func(cmd *cobra.Command, args []string) error
}

GenAPI is the exported codegen + service-registry surface the `add` command group depends on. Each field is a narrow, well-named entry into logic that stays in internal/cli; the group never reaches package cli internals directly. Mutex serialization of the pipeline (generateMu) is owned by the internal/cli-side closures, so callers here never touch a lock.

type ServiceRegistry

type ServiceRegistry interface {
	// Exists reports whether the registration file is present. When false,
	// every service reads as registered (fail-open pre-migration behavior).
	Exists() bool
	// Registered reports whether this binary serves the named service.
	Registered(name string) bool
	// Tombstoned reports whether the named service is deliberately retired
	// (mentioned in a comment but with no serviceRow reference).
	Tombstoned(name string) bool
}

ServiceRegistry is the narrow registration view the `add` group reads. It mirrors the relevant subset of internal/cli's serviceRegistry: a service is REGISTERED (this binary serves it), TOMBSTONED (deliberately retired — types-only), or neither (unlisted). The concrete value is adapted on the internal/cli side.

Jump to

Keyboard shortcuts

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