initctx

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package initctx is the state and the screens of a bare `lo init`. Detect reads where the user stands: a project root, a subdirectory, a service directory, a submodule under an umbrella project, an empty or a bare directory; what git says; what the project has; whether a terminal is attached. Decide turns the state and the answers into the ordered actions the cli runs through the verbs. The screens (screen.go) and project mode (loop.go) are the huh forms.

Everything here reads the filesystem only. The three git reads go through the execx.Runner seam, so the tests script them.

Index

Constants

View Source
const (
	EntryCluster        = "cluster"
	EntryService        = "service"
	EntryTests          = "tests"
	EntryToolchain      = "toolchain"
	EntryActive         = "active"
	EntryEject          = "eject"
	EntryImplementation = "implementation"
	EntryExit           = "exit"
)

The keys of the project list.

View Source
const (
	MissingDomain = "lo init cluster: give a domain: lo init cluster <domain>"
	MissingName   = "lo init service: give a name: lo init service <name>"
)

The error lines of a verb without its value, on a terminal and off.

Variables

View Source
var (
	ErrAborted   = errors.New("lo init: interrupted")
	ErrCancelled = errors.New("lo init: cancelled")
	// ErrIncomplete means a value the screen needs stayed empty at the
	// end of the input (an accessible prompt at EOF): the screen cannot
	// run. A terminal never ends the input: a required field re-asks.
	ErrIncomplete = errors.New("lo init: a value is missing")
)

ErrAborted means the user pressed Ctrl-C: the run ends with rc 130 and nothing is written. ErrCancelled means Esc or the Cancel choice of a screen: the project loop returns to its list, the bootstrap ends with nothing written.

Functions

func Bootstrap added in v0.5.0

func Bootstrap(s State) bool

Bootstrap reports whether the state calls for the bootstrap screen (mode 1): no project here, or a project without a git repository (git installed, no repository). Everything else is project mode.

func DefaultDir

func DefaultDir(s State) string

DefaultDir is the project directory the bootstrap defaults to: the project's root inside one, the git root when the user stands below it, else the working directory.

func DefaultEnv

func DefaultEnv(s State, dir string) string

DefaultEnv is the environment file a directory suggests: the one it already has, else mise.

func DefaultName added in v0.5.0

func DefaultName(dir string) string

DefaultName is the project name a directory suggests: its base name, lowercased, every run of other characters one `-`, "project" when nothing is left.

func Next added in v0.5.0

func Next(s State, drift bool) string

Next is the step a project state calls for. The first that applies wins: the toolchain when a pinned tool is missing; a cluster spec when there is none; the active domain when none is set; the assets diff on drift; `lo up` while the active domain has no kubeconfig; else `lo status`. Outside a project: `lo init`.

func Welcome added in v0.5.0

func Welcome(w io.Writer, s State)

Welcome is the one line mode 1 opens with: what `lo init` is about to set up here.

func WriteCard

func WriteCard(w io.Writer, s State, extra ...row)

WriteCard prints the state card of s.Project (nothing outside a project); extra rows (the result of the last action) follow it in the same columns.

func WriteEntries added in v0.5.0

func WriteEntries(w io.Writer, entries []Entry, extra ...row)

WriteEntries prints the list as text (--plan): the labels, then the twins dim, then extra rows in the same columns.

func WriteNext added in v0.5.0

func WriteNext(w io.Writer, next string)

WriteNext prints the `next` line: the step the state calls for.

func WritePlan added in v0.5.0

func WritePlan(w io.Writer, s State)

WritePlan is `--plan` in project mode: the card, then the list.

func WriteScreen added in v0.5.0

func WriteScreen(w io.Writer, sc Screen)

WriteScreen prints the screen as text: the title, then the rows.

Types

type Action

type Action struct {
	Kind ActionKind
	// Files lists what the step writes (relative to Plan.Dir); Run names
	// what it runs, in the words of the screen ("" = nothing).
	Files []string
	Run   string
	// Command is the flag-twin command line, run from Plan.Dir.
	Command string
	// Network is whether the step needs the network.
	Network bool
	// The parameters the executor hands to the functions behind Command.
	Name, Env, Domain, Driver, Service, Path, Implementation string
	Groups                                                   []string
}

Action is one step of a Plan.

type ActionKind

type ActionKind string

ActionKind names one action of a Plan.

const (
	// ActionWriteProjectFiles — clusters/, lok8s.yaml, the .gitignore
	// entries and (with Env) the environment file, plus (with Domain) the
	// first cluster spec: one `lo init project` run.
	ActionWriteProjectFiles ActionKind = "project files"
	// ActionGitInit — `git init` in the project directory.
	ActionGitInit ActionKind = "git init"
	// ActionWriteClusterSpec — clusters/<domain>/cluster.lok8s.yaml into
	// an existing project.
	ActionWriteClusterSpec ActionKind = "cluster spec"
	// ActionToolchainInstall — `lo toolchain install` (network).
	ActionToolchainInstall ActionKind = "toolchain"
	// ActionUse — `lo use <domain>`.
	ActionUse ActionKind = "use"
	// ActionAddService — `lo init service <name>`.
	ActionAddService ActionKind = "service"
	// ActionAddTests — `lo init test`.
	ActionAddTests ActionKind = "tests"
	// ActionEjectBash — `lo assets eject bash`.
	ActionEjectBash ActionKind = "eject bash"
	// ActionSetImplementation — spec.implementation.default in lok8s.yaml.
	ActionSetImplementation ActionKind = "implementation"
)

type Answers

type Answers struct {
	// Dir is the project directory ("" = the situation's default: the
	// working directory, or the git root below which the user stands).
	Dir string
	// Name is metadata.name ("" = the directory name).
	Name string
	// Env is the environment file: mise, direnv or none ("" = the one the
	// directory already has, else mise).
	Env string
	// GitInit asks for `git init` (honoured only without a repository).
	GitInit bool
	// Domain and Driver describe the first cluster spec; "" = none.
	Domain, Driver string
	// Use makes Domain the active domain (`lo use`).
	Use bool
	// Toolchain runs `lo toolchain install` with Groups (nil = the
	// default groups).
	Toolchain bool
	Groups    []string
}

Answers is what the new-project screen holds (the defaults, or the details the user changed). Every field has a flag twin on `lo init project`, `git init`, `lo toolchain install` or `lo use`.

func DefaultAnswers

func DefaultAnswers(s State) Answers

DefaultAnswers are the bootstrap defaults the screen shows first. For a new project: the files where the situation suggests; the name from the directory; the first cluster `<name>.dev` on the lo driver, made active; the environment file the directory has, else mise; the toolchain; `git init` when git exists and there is no repository. For a project without a repository: its name and root; a first cluster only when it has none; the toolchain only when a pin is missing; `git init`.

type ClusterInput added in v0.5.0

type ClusterInput struct {
	Domain, Driver string
	Active         bool
	// Force is the verb's --force: an existing spec is replaced.
	Force       bool
	DomainGiven bool
	DriverGiven bool
	ActiveGiven bool
}

ClusterInput is `lo init cluster`'s values; a *Given flag marks a value from the command line (a fixed row).

type Domain

type Domain struct {
	Name string
	// Kind is the driver of cluster.lok8s.yaml (lowercase), "deploy" for
	// a deploy-only domain, "?" when unreadable.
	Kind string
}

Domain is one directory under clusters/ that carries a spec.

type Entry added in v0.5.0

type Entry struct {
	Key, Label, Twin string
}

Entry is one choice of the project list, with the verb a script runs instead (the twin).

func Entries added in v0.5.0

func Entries(s State) []Entry

Entries lists what the project state allows, in the order of the list. Always: a cluster, a service, Exit. When there is no tests/: the test suite. When a pinned tool is missing or the pin file is unreadable: the toolchain. With several clusters, or with clusters and no valid active domain: the active domain. Without a bash tree: the eject; with one: the implementation switch.

type Git

type Git struct {
	// Available is whether git ran at all.
	Available bool
	// Root is the repository root ("" = not a repository), in the path
	// form of State.Cwd (git resolves symlinks; the card prints one form).
	Root string
	// AtRoot is whether cwd is the repository root.
	AtRoot bool
	// Branch is the current branch ("" = detached HEAD).
	Branch string
	// Uncommitted counts the paths `git status --porcelain` lists.
	Uncommitted int
	// Submodule is whether Root carries a `.git` FILE (a submodule
	// checkout, or a worktree) rather than a directory.
	Submodule bool
}

Git is what git says about the working directory.

type IO

type IO struct {
	In  io.Reader
	Out io.Writer
	// Accessible runs the fields as line prompts instead of the
	// interactive forms (off a TTY; the tests).
	Accessible bool
}

IO is where a form reads and writes.

type Loop added in v0.5.0

type Loop struct {
	Out     io.Writer
	IO      IO
	Detect  func() (State, error)
	Execute func(Plan) error
}

Loop is project mode. Detect reads the state again after an action; Execute runs a plan (the cli's executor over the subcommands' own functions) and returns a *StepError for a failed step.

func (Loop) Run added in v0.5.0

func (l Loop) Run(s State, first []Result) error

Run prints the card and the list until Exit, Esc or Ctrl-C. first are the rows of what ran before the loop (the bootstrap; nil = none): they go under the first card. A cancelled action returns to the list; a failed step returns to the list with the failed and not-run rows; Ctrl-C returns ErrAborted (the cli maps it to rc 130).

type Plan

type Plan struct {
	// Dir is the project directory every action runs in (absolute).
	Dir string
	// Name is the project name the actions use.
	Name string
	// Force overwrites existing files (a verb's --force; a screen never
	// sets it).
	Force bool
	// Actions in execution order; empty = nothing to do.
	Actions []Action
}

Plan is the ordered list of actions for one `lo init` run.

func ClusterPlan added in v0.5.0

func ClusterPlan(dir, dom, driver string, active, force bool) Plan

ClusterPlan is `lo init cluster`: the spec, then `lo use` when active. force is the verb's --force: an existing spec is replaced, and the twin carries the flag.

func Decide

func Decide(s State, a Answers) Plan

Decide turns the state and the answers into the new-project plan: the project files (with the first cluster spec), `git init`, the toolchain, `lo use`. A `git init` inside a repository is ignored.

func EjectPlan added in v0.5.0

func EjectPlan(dir string) Plan

EjectPlan is `lo assets eject bash` for the project at dir.

func ImplementationPlan added in v0.5.0

func ImplementationPlan(dir, name, impl string) Plan

ImplementationPlan sets spec.implementation.default for the project name at dir.

func NewProject added in v0.5.0

func NewProject(s State, out io.Writer, tio IO) (Plan, error)

NewProject is the new-project flow: the screen with the defaults, the details on request, the plan on Create.

func Run added in v0.5.0

func Run(out io.Writer, tio IO, build func() Screen) (Plan, error)

Run shows the screen and asks. Create returns the plan. Change details runs the fields and shows the screen again. Cancel returns ErrCancelled; Esc and Ctrl-C return ErrAborted. build makes the screen from the current values, so the next render shows a changed detail. The screen goes to out, the forms to tio.

func ServicePlan added in v0.5.0

func ServicePlan(dir, name, path string) Plan

ServicePlan is `lo init service`: the service file, its catalog entry and the Tiltfile. path "" = ./<name>.

func TestsPlan added in v0.5.0

func TestsPlan(dir, path string) Plan

TestsPlan is `lo init test`: the Playwright suite. path "" = tests/.

func ToolchainPlan added in v0.5.0

func ToolchainPlan(dir string, groups []string) Plan

ToolchainPlan is `lo toolchain install` for the project at dir.

func UsePlan added in v0.5.0

func UsePlan(dir, dom string) Plan

UsePlan is `lo use <domain>` for the project at dir.

func (Plan) Commands

func (p Plan) Commands() []string

Commands lists the flag-twin command lines in order.

func (Plan) Files added in v0.5.0

func (p Plan) Files() []string

Files lists every file the plan writes, in order.

func (Plan) Network

func (p Plan) Network() bool

Network reports whether any action needs the network.

func (Plan) Result added in v0.5.0

func (p Plan) Result() (verb, what string)

Result is the one-line outcome of an executed plan, as the project loop shows it under the card: a verb and what changed.

func (Plan) Runs added in v0.5.0

func (p Plan) Runs() []string

Runs lists every command the plan runs, in the screen's words.

type Project

type Project struct {
	// Root is the project root (the marker walk from cwd).
	Root string
	// AtRoot is whether cwd is Root.
	AtRoot bool
	// Name is metadata.name of the project file ("" when the marker is
	// clusters/ alone).
	Name string
	// ProjectFile is whether Root/lok8s.yaml is a `kind: Project` file.
	ProjectFile bool
	// Clusters is whether Root/clusters exists.
	Clusters bool
	// Domains lists the domains under clusters/ that carry a spec.
	Domains []Domain
	// Active is clusters/.active ("" when unset or invalid).
	Active string
	// Kubeconfig is whether the active domain's cluster has a kubeconfig
	// under .kubeconfig/ (<metadata.name>.yaml: the cluster was
	// provisioned once).
	Kubeconfig bool
	// EnvFile is the environment file present: "mise" (mise.toml),
	// "direnv" (.envrc), "" (none). With both present, mise.
	EnvFile string
	// BYAML is whether .bin/b.yaml exists; BYAMLInvalid whether it exists
	// but cannot be read or parsed (then Tools is 0).
	BYAML, BYAMLInvalid bool
	// Tools counts the pinned tools: b itself plus every `binaries:`
	// entry of .bin/b.yaml. ToolsMissing lists the ones that do not
	// resolve under the project (by name); nil = all present.
	Tools        int
	ToolsMissing []string
	// BashTree is whether Root/.lok8s/lo exists (an ejected or vendored
	// bash tree).
	BashTree bool
	// Implementation is spec.implementation.default ("go" or "bash";
	// "go" when the block is absent), ImplementationErr the loader's
	// error text when the block is invalid.
	Implementation    string
	ImplementationErr string
	// Services is whether Root/services.yaml exists; Tests whether
	// Root/tests is a directory.
	Services, Tests bool
}

Project is what exists in the project the user stands in.

func (*Project) ActiveValid added in v0.5.0

func (p *Project) ActiveValid() bool

ActiveValid reports whether clusters/.active names a domain with a spec.

func (*Project) ToolchainMissing added in v0.5.0

func (p *Project) ToolchainMissing() bool

ToolchainMissing reports whether `lo toolchain install` is due: no pin file, an unreadable one, or a pinned tool that does not resolve.

type Result added in v0.5.0

type Result struct {
	Key, Value string
}

Result is one row under the card: what the last action did (`added`, `installed`, `failed`, ...).

func FailureRows added in v0.5.0

func FailureRows(e *StepError) []Result

FailureRows are the rows of a failed step: the command with its reason, and the commands that did not run.

func ResultOf added in v0.5.0

func ResultOf(p Plan) Result

ResultOf is the result row of an executed plan.

type Screen added in v0.5.0

type Screen struct {
	// Title heads the screen ("New project").
	Title string
	// Values are the rows above the writes/runs lines.
	Values []row
	// Plan is what Create executes.
	Plan Plan
	// Details builds the editable fields, bound to the values (nil = no
	// details: every value came from the command line).
	Details func() []huh.Field
	// Incomplete is whether a value the screen needs is still empty: the
	// details open before the screen is shown, and --plan refuses with
	// Missing.
	Incomplete bool
	// Missing is the error line for an incomplete screen off the details
	// (--plan, or a verb off a terminal).
	Missing string
}

Screen is what the user sees before a write.

func ActiveScreen added in v0.5.0

func ActiveScreen(s State, dom *string) Screen

ActiveScreen is the active-domain screen: the domain is chosen first.

func ClusterScreen added in v0.5.0

func ClusterScreen(dir string, in *ClusterInput) Screen

ClusterScreen is the cluster screen for the project at dir.

func EjectScreen added in v0.5.0

func EjectScreen(s State) Screen

EjectScreen is the bash tree screen: nothing to change.

func ImplementationScreen added in v0.5.0

func ImplementationScreen(s State) Screen

ImplementationScreen switches spec.implementation.default to the other implementation: nothing to change.

func NewProjectScreen added in v0.5.0

func NewProjectScreen(s State, a *Answers) Screen

NewProjectScreen is the new-project screen for the answers a: the values, the plan, the details bound to a.

func ServiceScreen added in v0.5.0

func ServiceScreen(dir string, in *ServiceInput) Screen

ServiceScreen is the service screen for the project at dir.

func TestsScreen added in v0.5.0

func TestsScreen(dir string, in *TestsInput) Screen

TestsScreen is the test-suite screen for the project at dir.

func ToolchainScreen added in v0.5.0

func ToolchainScreen(dir string, groups *[]string) Screen

ToolchainScreen is the toolchain install screen for the project at dir: the groups are the details.

type ServiceInput added in v0.5.0

type ServiceInput struct {
	Name, Path string
	NameGiven  bool
	PathGiven  bool
}

ServiceInput is `lo init service`'s values.

type Situation

type Situation int

Situation is where a bare `lo init` stands. The first three take the bootstrap screen for a new project; the last two are a project (project mode, or the bootstrap screen when the project has no git repository).

const (
	// SituationUnknown is the zero value; Detect never returns it.
	SituationUnknown Situation = iota
	// SituationEmptyDir: an empty directory (a `.git` entry does not
	// count) and no project above. The project goes here.
	SituationEmptyDir
	// SituationGitBelowRoot: inside a git repository, below its root,
	// and no project above. The project goes to the repository root by
	// default.
	SituationGitBelowRoot
	// SituationBareDir: a non-empty directory, no project above, and
	// either no git or cwd is the git root. The project goes here.
	SituationBareDir
	// SituationProjectRoot: cwd is a project root.
	SituationProjectRoot
	// SituationInsideProject: cwd is inside a project (a subdirectory, a
	// service directory, a submodule under the umbrella project). The
	// actions act on the project root.
	SituationInsideProject
)

type State

type State struct {
	// Cwd is the working directory, absolute.
	Cwd string
	// Empty is whether Cwd has no entries besides `.git`.
	Empty bool
	// Entries counts the entries of Cwd besides `.git`.
	Entries int
	// ServiceDir is whether Cwd holds a kind-less lok8s.yaml (a service
	// directory).
	ServiceDir bool
	// Git is the git state; Project the project state (nil = no project
	// above Cwd).
	Git     Git
	Project *Project
	// Terminal is set by the caller (DetectTerminal); Detect leaves it
	// zero.
	Terminal Terminal
}

State is what Detect found.

func Detect

func Detect(ctx context.Context, cwd string, r execx.Runner) (State, error)

Detect reads the state from cwd. r runs git (nil = no git: the state reports it unavailable).

func (State) Situation

func (s State) Situation() Situation

Situation classifies the state.

type StepError added in v0.5.0

type StepError struct {
	Command string
	Err     error
	NotRun  []string
}

StepError is a failed action of a plan: the command, the cause and the commands that did not run.

func (*StepError) Error added in v0.5.0

func (e *StepError) Error() string

func (*StepError) Unwrap added in v0.5.0

func (e *StepError) Unwrap() error

type Terminal

type Terminal struct {
	// StdinTTY and StdoutTTY report whether the two streams are terminals.
	StdinTTY, StdoutTTY bool
	// CI is whether the CI environment variable is set (any value).
	CI bool
	// Yes is the --yes flag.
	Yes bool
}

Terminal is what decides between the wizard and the help text.

func DetectTerminal

func DetectTerminal(yes bool) Terminal

DetectTerminal reads the two streams through internal/ui (so the test override ui.ForceTTY covers the screens and the card alike) and the CI variable.

func (Terminal) Interactive

func (t Terminal) Interactive() bool

Interactive reports whether the wizard may run: both streams are terminals, CI is unset and --yes was not given.

type TestsInput added in v0.5.0

type TestsInput struct {
	Path      string
	PathGiven bool
}

TestsInput is `lo init test`'s values.

Jump to

Keyboard shortcuts

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