buildctl

package
v4.0.0-beta.4 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package buildctl exposes the Gothic build pipeline as a structured Go API: each tool runs one stage (templ, routes_gen, topic_gen, wasm, css, the app build), is incremental against the on-disk caches the pipeline already keeps, and returns a BuildResult carrying what it recompiled, how long it took, translated diagnostics for any failure, and the artifacts its output just invalidated ("now stale: routes_gen, wasm:/counter").

The tool layer never prints. Callers (the hot-reload command today, the MCP in the future) render results to their own output contract; the narration the underlying helpers print (Recompiling..., size lines) is theirs.

Concurrency: the generator stages and the app build share one build lock, which is also what an edit session holds across a multi-file edit; the WASM stage serialises on its own lock because the hot-reload session compiles it in the background while the foreground build proceeds.

Index

Constants

View Source
const (
	StageTempl     = "templ"
	StageRoutesGen = "routes_gen"
	StageTopicGen  = "topic_gen"
	StageWasm      = "wasm"
	StageCSS       = "css"
	StageGo        = "go"
	StageSync      = "sync"
)

Stage names — the stage field of every BuildResult.

View Source
const (
	StepConfig   = "config"
	StepEmbedded = "embedded"
)

Sub-step names a stage can fail at (the routes stage reads the config first and syncs the embedded public file last, and callers want to know which of the three failed without parsing the error text).

View Source
const (
	StatusFresh      = "fresh"
	StatusStale      = "stale"
	StatusMidRebuild = "mid-rebuild"
)

Status states for one artifact.

View Source
const BuildCachePath = ".gothicCli/wasm-cache.json"

BuildCachePath mirrors the build package's own per-unit cache. It is read here, never written, so the gate can tell that something outside this session rewrote the artifacts.

View Source
const DigestPath = ".gothicCli/wasm-digest.json"

DigestPath is the on-disk location of the WASM input digest.

Variables

This section is empty.

Functions

func CollectLocalDirs

func CollectLocalDirs(pages []helpers.WasmPage) []string

CollectLocalDirs is collectLocalDirs for the host layer (which owns the page scan through the same helper).

func ComputeDigest

func ComputeDigest(localDirs []string) string

ComputeDigest returns a hex-encoded SHA-256 digest of everything the WASM scan and generate stages consume.

func InputChanged

func InputChanged() bool

InputChanged reports whether any WASM-stage input changed since the last recorded build, for callers that only read the gate and never record.

func RecordDigestFor

func RecordDigestFor(snap Snapshot, localDirs []string)

RecordDigestFor persists the digest of what the build actually consumed.

It records the snapshot taken BEFORE the build, not a fresh reading. A save landing while the stage is running is the whole reason: recomputing here would store a digest describing content this build never compiled, and the next cycle would compare against it, see no change, and skip the stage forever, leaving a stale binary with no output saying so. Measured: an edit made during the 25s initial rebuild left the page running the previous logic until an unrelated file was touched.

A changed local-package set is the one case that has to recompute: the snapshot was taken over a different file set, so it does not describe these inputs either. That costs one extra rebuild, which is the safe direction.

func RecordDigestNow

func RecordDigestNow(localDirs []string)

RecordDigestNow records a digest read at call time. A build stage must NOT use this: it cannot tell a file the build compiled from one saved while it ran. Use RecordDigestFor there.

func SameDirs

func SameDirs(a, b []string) bool

func WriteDigest

func WriteDigest(digest string, localDirs []string)

Types

type BuildResult

type BuildResult struct {
	OK          bool         `json:"ok"`
	Stage       string       `json:"stage"`
	Step        string       `json:"step,omitempty"`
	Target      string       `json:"target,omitempty"`
	Recompiled  []Recompiled `json:"recompiled,omitempty"`
	MS          int64        `json:"ms"`
	Raw         string       `json:"raw,omitempty"`
	Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
	// NowStale lists the artifacts this build's output just invalidated, in
	// pipeline order: e.g. "routes_gen", "wasm:/counter". The names are the
	// stage/artifact names; a wasm entry may be per-route ("wasm:/counter")
	// or the whole inventory ("wasm").
	NowStale   []string     `json:"nowStale,omitempty"`
	Counts     *StageCounts `json:"counts,omitempty"`
	Suppressed bool         `json:"suppressed,omitempty"`
	// Err is the raw error text of a failed stage ("" when OK).
	Err string `json:"err,omitempty"`
}

BuildResult is the structured outcome of one build tool. OK is false only when a stage failed; Suppressed marks a call that ran during an edit session, which performs no work by design.

type Controller

type Controller struct {

	// Executor seams. Nil = the production default bound over the CLI helpers;
	// tests replace them with fakes.
	TemplRun  func(file string) (string, error) // "" = all dirty files
	RoutesRun func(goModName string) error
	CssRun    func() error
	GoRun     func(binary string) (string, error)                     // combined captured output
	WasmRun   func(target string, snap Snapshot) (BuildResult, error) // the WASM stage incl. gate
	// contains filtered or unexported fields
}

Controller owns the build pipeline for one session (a hot-reload command today; the MCP server in the future). All build calls are serialised against the caches they share.

func New

func New(cli *gothic_cli.GothicCli, opts Options) *Controller

New builds a Controller over the CLI's build helpers.

func (*Controller) BuildCSS

func (c *Controller) BuildCSS(page string) BuildResult

BuildCSS compiles the Tailwind output. Tailwind scans every template for classes, so the build is project-wide; an optional page name is recorded as the target. The output file's mtime gates the build: fresh CSS is a no-op.

func (*Controller) BuildGo

func (c *Controller) BuildGo() BuildResult

BuildGo compiles the app server binary, capturing its output for diagnostics while echoing it to the terminal.

func (*Controller) BuildRoutes

func (c *Controller) BuildRoutes() BuildResult

BuildRoutes runs the routes_gen stage: config read, the file-based route registry, and (when wired) the EMBEDDED-mode embed file. The stage's output feeds the app build, so a rewrite declares it stale.

func (*Controller) BuildStatus

func (c *Controller) BuildStatus() StatusResult

func (*Controller) BuildTempl

func (c *Controller) BuildTempl(file string) BuildResult

BuildTempl runs the templ generate stage. An empty file compiles every dirty .templ file (the incremental cached path); a named file regenerates just that one. Templ output becomes the pipeline's routes_gen and WASM inputs, so a run that compiled anything declares them stale.

func (*Controller) BuildTopics

func (c *Controller) BuildTopics() BuildResult

BuildTopics regenerates src/topics/topic_gen.go from the topic declarations. The generated accessors are inlined into every page's WASM entry point and compiled into the server, so a rewrite declares both stale.

func (*Controller) BuildWasm

func (c *Controller) BuildWasm(target string) BuildResult

BuildWasm runs the WASM stage: the digest gate, topic-stub ordering (stubs before the scan), the page scan, and the incremental per-symbol compile. A target (route path, component path, or topic name) scopes the reporting to the matching units; the stage itself always runs the full inventory because the per-symbol cache makes everything else instant.

func (*Controller) EditBegin

func (c *Controller) EditBegin()

EditBegin suspends watcher-driven rebuilds and holds the build lock across a multi-file edit sequence. While the session is open, every build call short-circuits with a Suppressed result instead of racing the edit. A second Begin while a session is open is a no-op.

func (*Controller) EditEnd

func (c *Controller) EditEnd() BuildResult

EditEnd closes the edit session and runs exactly one Sync over the changed state. Returns the sync's BuildResult, and fires the host's AfterSync hook so it can re-arm its own rebuild cycle for the app binary and the reload. When no session is open, it is a no-op.

func (*Controller) Editing

func (c *Controller) Editing() bool

Editing reports whether an edit session currently holds the build lock. The hot-reload watcher consults this to suspend its own rebuilds.

func (*Controller) Sync

func (c *Controller) Sync() BuildResult

Sync brings every generated artifact up to date in pipeline order — templ → routes_gen → topic_gen → wasm → css — incrementally and idempotently: when nothing changed, no stage recompiles and the call returns immediately. It does NOT run BuildGo; the caller compiles the app when it wants the server binary fresh.

type Diagnostic

type Diagnostic struct {
	Raw       string `json:"raw"`
	Diagnosis string `json:"diagnosis"`
	Fix       string `json:"fix"`
	Skill     string `json:"skill,omitempty"`
}

Diagnostic is a translated build failure. Raw carries the tool's own compiler/generator text verbatim; Diagnosis and Fix are the plain-language translation; Skill, when set, names a bundled skill document covering the area (see internal/skills).

func DiagnoseAll

func DiagnoseAll(raw string) []Diagnostic

DiagnoseAll translates raw tool output into diagnostics. The raw text is preserved verbatim inside every diagnostic; an output no entry recognizes still comes back untranslated, so no signal is ever dropped.

type DigestData

type DigestData struct {
	Digest           string   `json:"digest"`
	LocalPackageDirs []string `json:"localPackageDirs,omitempty"`
}

DigestData is the on-disk format under DigestPath.

func LoadDigest

func LoadDigest() *DigestData

LoadDigest reads the stored digest. Returns nil on any error.

type Options

type Options struct {
	// MainBinary is the app build target ("tmp/main"); derived from the CLI
	// runtime when empty.
	MainBinary string
	// SyncEmbedded syncs the EMBEDDED-mode gothic_embed_gen.go — a host-layer
	// concern (the file's shape is decided by the command layer). Nil skips
	// the step.
	SyncEmbedded func(config *gothic_cli.Config) error
	// WasmLogf / WasmErrorf narrate the WASM stage the way the hosting
	// command's stream expects. No-ops when nil.
	WasmLogf   func(format string, args ...any)
	WasmErrorf func(format string, args ...any)
	// WasmInventory fires right before the WASM generate phase with the scan
	// inventory (pages, components, topics); the host prints its own line.
	WasmInventory func(pages, components, topics int)
	// BeginEdit suspends the host's watcher-driven rebuilds (stops arming
	// them) for the duration of an edit session; the returned function is
	// called when the session ends.
	BeginEdit func() (end func())
	// AfterSync fires after EditEnd's one Sync, so the host can re-arm its
	// rebuild cycle for the app binary and the reload.
	AfterSync func(res BuildResult)
}

Options wire the controller to its host. Every field is optional.

type Recompiled

type Recompiled struct {
	Path  string `json:"path"`
	Bytes int64  `json:"bytes"`
}

Recompiled is one artifact a build tool actually wrote.

type Snapshot

type Snapshot struct {
	Digest  string
	Dirs    []string
	Changed bool
}

Snapshot is the gate reading taken BEFORE a build: the digest of the inputs the build is about to consume, the dirs it was computed over, and whether it differs from the last recorded build.

func TakeSnapshot

func TakeSnapshot() Snapshot

TakeSnapshot reads the gate. Fail-open: on any error the stage runs.

type StageCounts

type StageCounts struct {
	Rebuilt  int `json:"rebuilt"`
	UpToDate int `json:"upToDate"`
}

StageCounts mirror the WASM helper's own counters after a stage.

type StatusArtifact

type StatusArtifact struct {
	Name string `json:"name"`
	// State is one of the Status* constants.
	State  string `json:"state"`
	Detail string `json:"detail"`
}

StatusArtifact is the freshness of one build artifact.

type StatusResult

type StatusResult struct {
	// Rebuilding is true while any stage (foreground or the background WASM
	// compile) is running.
	Rebuilding bool `json:"rebuilding"`
	// Editing is true while an edit session holds the build lock.
	Editing   bool             `json:"editing"`
	Artifacts []StatusArtifact `json:"artifacts"`
}

StatusResult is the freshness report of the whole artifact graph.

Jump to

Keyboard shortcuts

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