Documentation
¶
Overview ¶
Package scaffold implements the `harbor scaffold` engine — the template registry, the renderer, and the on-disk writer.
The package is BINARY-INTERNAL. Although it lives at `cmd/harbor/scaffold/` (so `cmd/harbor/cmd_scaffold.go` can import it), nothing outside `cmd/harbor` consumes it; in particular no `internal/...` package depends on this surface. The decision to live here rather than under `cmd/harbor/internal/scaffold/` is stylistic — Go's `internal/` rules already restrict reachability to the parent's import tree, which for `cmd/harbor/` is just the binary itself.
Public entry point ¶
scaffold.Scaffold(opts Options) (Result, error) takes:
- Options.Name — the project name (validated via validateName).
- Options.Template — the template to render (default DefaultTemplate = "minimal-react").
- Options.OutputDir — where to write the project tree. The directory MUST NOT exist; Scaffold creates it.
Returns Result{Name, OutputDir (absolute path), Files (relative paths)} on success; otherwise one of the package's sentinel errors:
- ErrInvalidName — Options.Name failed validateName.
- ErrOutputDirExists — Options.OutputDir already exists.
- ErrUnknownTemplate — Options.Template is not in Templates().
- ErrRender — a template render or filesystem write failed (wrapped with the offending file path).
Template registry ¶
Templates are embedded at compile time via Go's embed package (templates/*). The registry is a write-once package-level map populated in init(); Templates() returns the keys in deterministic order. Harbor ships exactly one template (`minimal-react`).
Concurrency ¶
Scaffold is a pure function — no shared state, no goroutines, no long-lived artifacts. The concurrent-reuse contract is vacuous here; the package is safe for concurrent invocation by construction.
Index ¶
Constants ¶
const DefaultTemplate = "minimal-react"
DefaultTemplate is the template `harbor scaffold` selects when the operator omits --template. Harbor ships only minimal-react.
Variables ¶
var ( // ErrInvalidName signals Options.Name failed validateName. The // wrapped message names the offending name + the rule that // rejected it. ErrInvalidName = errors.New("scaffold: invalid project name") // ErrOutputDirExists signals Options.OutputDir already exists. // Scaffold refuses to overwrite — operators delete the directory // or pick a fresh path. This is the §13 fail-loud posture for an // operator-facing seam. ErrOutputDirExists = errors.New("scaffold: output directory already exists") // ErrUnknownTemplate signals Options.Template is not in Templates(). // The wrapped message lists every known template. ErrUnknownTemplate = errors.New("scaffold: unknown template") // ErrRender signals a template execution or filesystem write // failed. The wrapped message names the offending file path. ErrRender = errors.New("scaffold: render failed") // ErrUpstreamConfigInvalid signals the operator-supplied yaml at // FromConfigPath failed to load or validate. // Wraps the underlying `internal/config.ErrConfigInvalid`. ErrUpstreamConfigInvalid = errors.New("scaffold: upstream harbor.yaml is invalid") )
Sentinel errors. Callers (cmd/harbor/cmd_scaffold.go) compare via errors.Is and map onto CLIError{Code} values.
Functions ¶
Types ¶
type Options ¶
type Options struct {
// Name is the project name. Required. Used as the rendered
// `go.mod` module name's last component, the `harbor.yaml`
// service-name, and the README title.
Name string
// Template selects which embedded template tree to render. Empty
// defaults to DefaultTemplate. The set of allowed values comes
// from Templates().
Template string
// OutputDir is the directory Scaffold creates and writes the
// rendered project into. Required. Without `Patch`, the path is
// resolved with `filepath.Abs` and MUST NOT exist on disk; with
// `Patch`, an existing directory is accepted and existing files
// are skipped.
OutputDir string
// FromConfigPath is the optional path to an operator-edited
// `harbor.yaml`. When set, Scaffold loads +
// validates it and uses its `tools.custom[]` entries to generate
// per-tool Go stubs under `OutputDir/tools/`. Empty + no
// `./harbor.yaml` in the cwd ⇒ Scaffold falls back to the
// template-only behavior (the existing scaffold-without-init
// path stays valid).
FromConfigPath string
// Patch relaxes the refuse-overwrite default.
// When true, an existing OutputDir is accepted and Scaffold writes
// only files that do NOT already exist; existing files are
// skipped and listed under `Result.Skipped`. The operator-edit
// survival invariant.
Patch bool
// WithServer opts into emitting a `cmd/<name>/main.go` that serves
// the Harbor Protocol via `sdk/server` — an external Protocol server
// carrying this project's compiled in-process tools. Purely
// additive: the default (false) output is unchanged, and the
// generated `agent.go` gains a `RegisterTools` seam the main.go
// passes to `server.Open`.
WithServer bool
}
Options is the input to Scaffold.
type Result ¶
type Result struct {
Name string `json:"name"`
OutputDir string `json:"output_dir"`
Files []string `json:"files"`
Skipped []string `json:"skipped,omitempty"`
}
Result reports what Scaffold wrote. Files are paths RELATIVE to OutputDir, in deterministic (lexicographic) order — a smoke script or scripted consumer can rely on the ordering.
`Skipped` lists files that were already on disk at scaffold time and were therefore NOT overwritten. Only populated when `Options.Patch == true`; non-patch runs reject existing output dirs outright.
func Scaffold ¶
Scaffold materialises the named template at opts.OutputDir.
Validation order (fail-loud, no partial writes):
- opts.Name is validated via validateName.
- opts.Template (empty → DefaultTemplate) must be a registered template.
- opts.OutputDir is resolved via filepath.Abs and (in default mode) MUST NOT exist. With `opts.Patch == true`, an existing directory is accepted; existing files are skipped.
Upstream config: when `opts.FromConfigPath` is set OR `./harbor.yaml` exists in the cwd, Scaffold loads + validates it via `internal/config.Load`. Its `tools.custom[]` entries drive per-tool stub generation; the yaml file itself is copied verbatim into OutputDir/harbor.yaml (overriding the template-rendered yaml).
On success, every template file is rendered through text/template and written to OutputDir/<rel-path>. The returned Result.Files is the lexicographic-order list of relative paths written; Result.Skipped lists files left untouched in patch mode.
On any failure during a NON-patch run after the output dir has been created, Scaffold removes the dir before returning so the operator never sees a half-scaffolded project. Patch runs do not roll back — the operator's pre-existing files are sacred.