Documentation
¶
Overview ¶
Package cli implements the moth binary's remote-client mode: named contexts (server URL + personal access token) stored in ~/.config/moth/config.toml, connect clients for the moth.admin.v1 services authenticated by that token, and the declarative dump/apply logic built on ProjectSpec.
Index ¶
- Variables
- func ConfigPath() (string, error)
- func MarshalJSON(m proto.Message) ([]byte, error)
- func MergeSettings(current, desired *adminv1.ProjectSettings) *adminv1.ProjectSettings
- func MonetizationSpecFromCatalog(ents []*adminv1.Entitlement, prods []*adminv1.Product) *adminv1.MonetizationSpec
- func SaveConfig(path string, cfg Config) error
- func SpecFromYAML(data []byte) (*adminv1.ProjectSpec, error)
- func SpecToYAML(spec *adminv1.ProjectSpec) ([]byte, error)
- type ApplyPlan
- type Client
- type Config
- type Context
- type InitAnswers
- type MonetizationPlan
Constants ¶
This section is empty.
Variables ¶
var ErrInitAborted = errors.New("aborted — nothing was created")
ErrInitAborted is returned when the operator abandons the wizard at the final confirmation. Nothing has been created at that point: the wizard holds every answer client-side and only writes after the confirm.
Functions ¶
func ConfigPath ¶
ConfigPath returns the CLI config file location: $XDG_CONFIG_HOME/moth/config.toml, falling back to ~/.config/moth/config.toml.
func MarshalJSON ¶
MarshalJSON renders a proto message as stable, indented JSON for --json output. protojson deliberately randomizes its whitespace, so the output is re-indented through encoding/json to stay byte-stable for scripts and golden tests (field order is protojson's, i.e. schema order).
func MergeSettings ¶
func MergeSettings(current, desired *adminv1.ProjectSettings) *adminv1.ProjectSettings
MergeSettings returns desired with its "unset" fields (zero numerics, empty timezone, nil optional/sub-messages, absent redirect_schemes / redirect_origins lists) filled from current, i.e. the full settings object an UpdateProject must send. proto3 booleans cannot express "unset", so false always means false — the plan's SettingsChanges makes an accidental reset visible.
func MonetizationSpecFromCatalog ¶
func MonetizationSpecFromCatalog(ents []*adminv1.Entitlement, prods []*adminv1.Product) *adminv1.MonetizationSpec
MonetizationSpecFromCatalog renders the live entitlements and products as a MonetizationSpec — the block `moth project dump` emits so a re-applied dump converges to an empty plan. Product entitlement grants are emitted as stable entitlement identifiers (not server ids). A project with no catalog yields a non-nil empty spec so the round-trip stays lossless (absent == untouched; empty == an explicitly empty catalog, which for an already-empty project is a no-op).
func SaveConfig ¶
SaveConfig writes the config file (0600 — it holds credentials), creating parent directories as needed.
func SpecFromYAML ¶
func SpecFromYAML(data []byte) (*adminv1.ProjectSpec, error)
SpecFromYAML parses a `moth project dump` document (unknown fields are rejected, so typos fail loudly instead of silently applying nothing).
func SpecToYAML ¶
func SpecToYAML(spec *adminv1.ProjectSpec) ([]byte, error)
SpecToYAML serializes a ProjectSpec as the YAML document `moth project dump` emits and `moth project apply -f` consumes. The field names are the proto ones (snake_case); the conversion goes through protojson so the document stays a faithful, versionable rendering of the proto message.
Types ¶
type ApplyPlan ¶
type ApplyPlan struct {
Slug string `json:"slug"`
Create bool `json:"create"`
UpdateName bool `json:"update_name"`
UpdateSettings bool `json:"update_settings"`
UpdateTheme bool `json:"update_theme"`
ResetTheme bool `json:"reset_theme"`
// SettingsChanges names the settings fields an update will change
// (proto field names, in field order), so an accidental boolean reset
// is visible in the plan before it is applied.
SettingsChanges []string `json:"settings_changes,omitempty"`
// Notes flags operations the diff cannot verify, e.g. write-only
// provider secrets that are re-sent on every apply.
Notes []string `json:"notes,omitempty"`
}
ApplyPlan lists what one `moth project apply` run will change. An empty plan is the idempotency signal: the live state already matches the spec.
func PlanApply ¶
func PlanApply(spec *adminv1.ProjectSpec, current *adminv1.Project, theme *adminv1.GetThemeResponse) (ApplyPlan, *adminv1.ProjectSettings, error)
PlanApply diffs a spec against the live state (current == nil when no project has the spec's slug; theme is the project's current theme, nil on create) and returns the plan plus the settings message an update must send. The sent settings are the spec's merged over the current ones — zero numeric fields, an empty timezone, absent redirect_schemes / redirect_origins lists and absent sub-messages mean "keep what the server has" — so partial hand-written specs stay idempotent and never clobber unrelated fields. Plain proto3 booleans are the exception (omitted means false); the plan's SettingsChanges lists every field about to change.
type Client ¶
type Client struct {
Sessions adminv1connect.SessionServiceClient
Projects adminv1connect.ProjectServiceClient
Users adminv1connect.UserServiceClient
Account adminv1connect.AdminAccountServiceClient
Settings adminv1connect.InstanceSettingsServiceClient
Analytics adminv1connect.AnalyticsServiceClient
Themes adminv1connect.ThemeServiceClient
Entitlements adminv1connect.EntitlementServiceClient
Products adminv1connect.ProductServiceClient
Monetization adminv1connect.MonetizationServiceClient
BillingCreds adminv1connect.BillingCredentialsServiceClient
Push adminv1connect.PushServiceClient
Profiles adminv1connect.ProfileServiceClient
}
Client bundles one connect client per moth.admin.v1 service, all sharing an http.Client that authenticates with a personal access token. The CLI command groups are thin wrappers over these — the same generated clients the admin SPA uses, so the two surfaces cannot diverge in capability.
type Config ¶
type Config struct {
// CurrentContext is the context used when --context/MOTH_CONTEXT is
// not given.
CurrentContext string `toml:"current-context,omitempty"`
Contexts map[string]Context `toml:"contexts,omitempty"`
}
Config is the on-disk CLI configuration, kubectl-style.
func LoadConfig ¶
LoadConfig reads the config file; a missing file is an empty config.
func (Config) Resolve ¶
Resolve picks the context to use: the named one when name is not empty (--context / MOTH_CONTEXT), the current-context otherwise. The returned errors tell the operator how to fix an unconfigured CLI.
func (*Config) SetContext ¶
SetContext adds or replaces a named context and makes it current.
type Context ¶
type Context struct {
// URL is the server base URL ("https://auth.example.com").
URL string `toml:"url"`
// Token is a personal access token (moth_pat_...).
Token string `toml:"token"`
}
Context is one named server + credential pair.
type InitAnswers ¶
type InitAnswers struct {
// Spec carries name, slug (may be empty: derived server-side), settings
// including provider credentials entered in-flow, and the monetization
// catalog. It is the document the finishing `moth project apply` spec is
// rendered from.
Spec *adminv1.ProjectSpec
// Profile records the answers themselves — platforms and feature intent
// — for UpdateProfile; the derived checklist keys off it.
Profile *adminv1.Profile
// Push is the milestone-20 settings to install, nil when the backend
// will not send pushes.
Push *adminv1.PushSettings
// Deferred lists what the wizard honestly did not finish, one human
// line each ("Google sign-in credentials — run 'moth setup google'").
Deferred []string
}
InitAnswers is everything `moth project init` collected: the desired state (the same ProjectSpec `moth project apply` consumes, plus the push settings and profile the spec cannot carry) and the work the operator explicitly deferred.
func RunInitWizard ¶
func RunInitWizard(p *setup.Prompter) (*InitAnswers, error)
RunInitWizard runs the interactive ask-configure-defer flow of `moth project init` over the prompter and returns the collected answers. It performs no RPC and writes nothing: creation is atomic after the final confirmation, so abandoning at any prompt (including answering "n" at the review step, which returns ErrInitAborted) leaves no project behind.
type MonetizationPlan ¶
type MonetizationPlan struct {
CreateEntitlements []string `json:"create_entitlements,omitempty"`
UpdateEntitlements []string `json:"update_entitlements,omitempty"`
DeleteEntitlements []string `json:"delete_entitlements,omitempty"`
CreateProducts []string `json:"create_products,omitempty"`
UpdateProducts []string `json:"update_products,omitempty"`
DeleteProducts []string `json:"delete_products,omitempty"`
}
MonetizationPlan lists the catalog changes one apply will make, by stable identifier (entitlement/product identifiers, not server ids). An empty plan is the idempotency signal: the live catalog already matches the spec. Unlike settings (which merge partial specs), the catalog is full desired state — entitlements/products absent from the spec are deleted — so a dump re-applied converges to an empty plan.
func PlanMonetization ¶
func PlanMonetization(spec *adminv1.MonetizationSpec, currentEntitlements []*adminv1.Entitlement, currentProducts []*adminv1.Product) MonetizationPlan
PlanMonetization diffs a monetization spec against the live catalog (the project's current entitlements and products, as the admin services return them) and returns the create/update/delete plan, keyed on identifiers. A nil spec means the spec omitted the monetization block entirely and the catalog is left untouched (empty plan) — distinct from an explicit empty catalog, which deletes everything. Product entitlement grants are compared by entitlement identifier (resolved through currentEntitlements) so the diff is stable across the id/identifier boundary.
func (MonetizationPlan) Empty ¶
func (p MonetizationPlan) Empty() bool
Empty reports whether the monetization apply is a no-op.
func (MonetizationPlan) Summary ¶
func (p MonetizationPlan) Summary() []string
Summary renders the plan as human lines.