Documentation
¶
Overview ¶
Package linker owns the decision half of registering a directory as a lerd site. Every caller that links a project — the CLI, the parked-directory watcher, the web UI and the MCP server — resolves the same plan here and differs only in the capabilities it grants through a Policy.
Index ¶
- func FilterConflictingDomains(desired []string, ownPath string, allSites []config.Site) (kept, removed []string)
- func FreeSiteName(desired, path string) string
- func HostProxyGate(command string, disabled, skipConfirm, approved, canPrompt bool) (proceed, ask bool, reason string)
- func IsReservedDomain(domain string) bool
- func OwningWorktree(dir string) (*config.Site, string, bool)
- func ResolveDomains(desired []string, baseName, ownPath, tld string) (kept, removed []string)
- func ResolveFramework(dir string, allowStoreFallback bool) (string, bool)
- type Deps
- type Mode
- type NopReporter
- type Plan
- type Policy
- type Prompter
- type Reporter
- type Result
- type Skip
- type Step
- type TextReporter
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FilterConflictingDomains ¶
func FilterConflictingDomains(desired []string, ownPath string, allSites []config.Site) (kept, removed []string)
FilterConflictingDomains splits desired into the domains ownPath may claim and those a different site already holds. The check is strict: a domain is a conflict regardless of TLS scheme, because DNS and browser caches don't disambiguate by scheme reliably. Order is preserved so a surviving preferred domain stays primary. Re-linking the same path is not a conflict.
func FreeSiteName ¶
FreeSiteName returns the first available site name for a path. An unused name is returned as-is, as is one already held by the same path (a re-link). A name held by a different path gets "-2", "-3", … until one is free.
func HostProxyGate ¶
func HostProxyGate(command string, disabled, skipConfirm, approved, canPrompt bool) (proceed, ask bool, reason string)
HostProxyGate is the decision for whether lerd may supervise a dev-server command on the host. It is pure so the policy is testable without a terminal or a config file. proceed reports that the command may run without asking; ask reports that a confirmation is still owed; reason explains a refusal. A site only ever runs a command the user has consented to.
func IsReservedDomain ¶
IsReservedDomain reports whether a domain is reserved for lerd's own use.
func OwningWorktree ¶
OwningWorktree returns the site dir is a git worktree of, so a checkout under a registered project is not registered again as a site of its own.
func ResolveDomains ¶
ResolveDomains filters the desired domain list against the live registry and returns the list to register. When every desired domain is conflicted it falls back to a freshly generated `<baseName>.<tld>`, suffixed until it is free in both the name and domain axes. The .lerd.yaml on disk is never touched; the discrepancy lives only in the registration, and the dropped domains come back in removed so the caller can report them.
func ResolveFramework ¶
ResolveFramework names the framework for dir. The store fallback asks the user which definition to install, so it is only reachable when the caller can put a question on screen. A project that names a framework no definition can be found for is still that framework, and the name is returned unresolved rather than dropped, so the registry records what the project committed instead of nothing at all. The false then says only that no definition backs it, which is what keeps the caller from taking a public dir or a PHP range from one that isn't there.
Types ¶
type Deps ¶
type Deps struct {
// EnsureFPMQuadlet builds a PHP version's image and unit.
EnsureFPMQuadlet func(version string) error
// ReconcileRuntimeQuadlets clears a per-site FrankenPHP or custom-FPM unit
// the site no longer uses.
ReconcileRuntimeQuadlets func(site config.Site)
// StartHostProxyWorker supervises a host-proxy site's dev command.
StartHostProxyWorker func(site config.Site, proxy *config.ProxyConfig)
// SyncIDEDataSource points a JetBrains project at the site's database and
// reports whether it wrote anything.
SyncIDEDataSource func(siteRoot string) bool
}
Deps are the side effects a link needs that live above this package. A nil field means the caller cannot do that thing, and the step is skipped.
type NopReporter ¶
type NopReporter struct{}
NopReporter discards everything, for callers that report on their own.
func (NopReporter) Line ¶
func (NopReporter) Line(string)
func (NopReporter) Step ¶
func (NopReporter) Step(string) Step
func (NopReporter) Val ¶
func (NopReporter) Val(s string) string
func (NopReporter) Warn ¶
func (NopReporter) Warn(string, ...any)
type Plan ¶
type Plan struct {
// Dir is the directory being linked, as given.
Dir string
// Site is the registration that will be written.
Site config.Site
// Mode is how Site will be served.
Mode Mode
// Skip is set when the directory will not be registered at all, and
// SkipDetail explains it in words the caller can print.
Skip Skip
SkipDetail string
// WorktreeParent and WorktreeBranch are set alongside SkipWorktree: the
// site that owns this checkout, and the branch it holds.
WorktreeParent *config.Site
WorktreeBranch string
// Project is the parsed .lerd.yaml, or nil when the project has none.
Project *config.ProjectConfig
// DroppedDomains lists domains another site already owns, which were
// filtered out of Site.Domains.
DroppedDomains []string
// PHPSuggestion names a PHP version that suits the framework better than
// the one Site carries. Empty when the chosen version is already the best
// installed one, or when the policy forbids building images.
PHPSuggestion string
// PHPMin and PHPMax are the framework's supported range, for the message
// that accompanies a clamped version. Both empty when unconstrained.
PHPMin, PHPMax string
// FrameworkLabel names the framework in user-facing messages, and is
// "unknown (public: <dir>)" when detection found none.
FrameworkLabel string
// FrankenPHPDeclined records that the project asked for FrankenPHP but no
// image exists for its PHP version, so the site falls back to FPM.
FrankenPHPDeclined bool
// ProxyCommand is the host dev-server command awaiting consent, empty when
// the site runs none.
ProxyCommand string
}
Plan is everything a link decided before it changed anything. Resolve produces it from the directory, the global config and the policy; Apply carries it out.
func Resolve ¶
Resolve decides everything about linking dir as a site without changing anything. It reads the directory, the project's .lerd.yaml and the site registry, and returns the plan Apply carries out. A plan whose Skip is set registers nothing.
func (*Plan) MatchesRegistration ¶ added in v1.33.0
MatchesRegistration reports whether the registry already holds the registration this plan would write, so carrying the plan out would change nothing. Callers that link only to apply a project's configuration use it to skip a link that has no work in it, rather than repeating every provisioning step and the summary for a site that is already serving.
Only the fields a link decides are compared. Everything else the registry carries is the site's operational state — paused, pinned, grouped, its LAN and worktree ports — which no link writes and which must never read as a change. A plan that registers nothing has no registration to compare and never matches.
func (*Plan) Registered ¶
Registered reports whether the plan will actually register a site.
type Policy ¶
type Policy struct {
// Name overrides the site name and primary domain. Empty derives both from
// the directory name.
Name string
// AssumeYes records consent given outside a prompt, such as `lerd link
// --yes` or the click that started a link from the web UI.
AssumeYes bool
// Prompt, when non-nil, may ask the user to resolve a decision. Nil means
// no question can be asked, whether or not a terminal is attached.
Prompt Prompter
// ProjectWrites allows writing into the project directory: the .php-version
// and .node-version pins, and the .lerd.yaml domain and framework writeback.
ProjectWrites bool
// Services allows installing and starting the services the project declares
// and the ones its framework requires.
Services bool
// Certs allows the site to be registered as secured, which issues a
// certificate for it.
Certs bool
// RepoCommands allows running commands the repository authored: a
// host-proxy dev server and inline service containers. Consent for these
// still goes through the usual gate; this only says the caller is a context
// where running them is on the table at all.
RepoCommands bool
// ImageBuild allows building or pulling a PHP image the host does not have.
ImageBuild bool
// SkipRegistered stops the link when the directory is already a registered
// site, rather than re-linking it.
SkipRegistered bool
// DeferPublish leaves the reloads that publish a link — systemd, the quadlet
// rewrite, the container hosts file and nginx — to the caller, so a batch of
// links does that work once at the end instead of once per project.
DeferPublish bool
}
Policy is the capability set a caller grants a link. The zero value is the most restrictive one: it registers a site and serves it, and does nothing else. Each field widens that.
func CLIPolicy ¶
CLIPolicy is the policy for a user-invoked `lerd link`: everything is permitted, and prompt decides whether questions can be asked.
func WatcherPolicy ¶
func WatcherPolicy() Policy
WatcherPolicy is the policy for `lerd park` and the parked-directory watcher. It runs unattended against every subdirectory of a parked tree, so it reads the project's committed configuration but never asks a question, never writes into the project, and never runs anything the repository authored.
type Prompter ¶
type Prompter interface {
// Confirm asks a yes/no question and reports the answer.
Confirm(question string, defaultYes bool) bool
// Choose asks the user to pick one of options and returns its index.
Choose(title string, options []string) (int, error)
}
Prompter resolves a question a link cannot answer on its own. Callers with a terminal supply one; every other caller passes nil, and the link takes the conservative branch instead of blocking on input that will never arrive.
type Reporter ¶
type Reporter interface {
// Step announces work that is starting and returns the handle that closes it.
Step(label string) Step
// Line reports something worth showing that is not a step.
Line(msg string)
// Warn reports a problem that did not stop the link.
Warn(format string, a ...any)
// Val styles a value inside a message, so the linker can compose detail
// strings without knowing how the caller renders them.
Val(s string) string
}
Reporter renders a link's progress. The CLI backs it with its feedback layer; a daemon or an assistant backs it with plain lines or nothing at all.
type Result ¶
type Result struct {
Plan *Plan
// Site is the registration as written, which can differ from the plan's
// when a newly installed PHP version was accepted.
Site config.Site
// WroteIDEDataSource records that a JetBrains data source was written.
WroteIDEDataSource bool
}
Result is what a link did.
func Apply ¶
Apply carries out a plan: it registers the site, provisions the runtime that serves it, and performs the project writes the policy allows. A plan that skips registers nothing and returns without error.
func (*Result) Registered ¶
Registered reports whether a site was actually registered.
type TextReporter ¶
TextReporter writes plain lines, for the watcher and other log-only callers.
func (TextReporter) Line ¶
func (t TextReporter) Line(msg string)
func (TextReporter) Step ¶
func (t TextReporter) Step(label string) Step
func (TextReporter) Val ¶
func (TextReporter) Val(s string) string
func (TextReporter) Warn ¶
func (t TextReporter) Warn(format string, a ...any)