template

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package template handles external templates: detect (spin.toml + _base/), load (clone or read pin), and render via text/template.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HasHooks

func HasHooks(t *Template) bool

HasHooks reports whether the template would run any shell commands: [[pre]]/[[post]] steps, or non-hidden files in _pre/ or _post/.

func RunPostHook

func RunPostHook(ctx context.Context, t *Template, values map[string]any, dir string, opts HookOptions) error

RunPostHook executes the template's [[post]] steps (if any) after the files have been written to disk. Each step's `run` is rendered against the resolved param + flag values (so `{{.project_name}}` interpolates correctly), then run via `sh -c` in dir. Steps run in order; the hook stops on the first failure and returns that error (with the failing command and its combined output).

An empty or missing post section is a no-op.

The post-hook runs AFTER files are written, BEFORE the spin.toml is removed from the output directory. This ordering lets the hook observe the full scaffolded state (including any spin.toml that might have been included in _base/) but ensures the project that the user sees has spin.toml deleted by the time the scaffolder returns.

func RunPreHook

func RunPreHook(ctx context.Context, t *Template, values map[string]any, dir string, opts HookOptions) error

RunPreHook executes the template's [[pre]] steps (if any) after params are resolved but before files are rendered. Each step's `run` is rendered against the resolved param + flag values, then run via `sh -c` in dir. Steps run in order; the hook stops on the first failure and returns that error.

An empty or missing pre section is a no-op.

func UnwrapValue

func UnwrapValue(v params.Value) any

UnwrapValue returns the underlying primitive held by a params.Value. The text/template engine wants raw Go types (string, int, bool, []string), not the multi-field struct. Exported because post_hook.go also needs it.

Types

type Author

type Author struct {
	Name  string `toml:"name"`
	Email string `toml:"email"`
	URL   string `toml:"url"`
}

Author identifies the template creator. All fields are optional; templates only need to fill what they want to publish.

type DestAction

type DestAction int

DestAction is the user's choice when a pre-existing clone is found at the destination.

const (
	DestReuse  DestAction = iota // use the existing clone as-is
	DestPin                      // reuse and persist the source for offline use
	DestWipe                     // remove the clone and re-clone
	DestCancel                   // abort without changes
)

type HookOptions

type HookOptions struct {
	// NoHooks skips execution entirely. Commands are still printed if
	// PrintCommands is true.
	NoHooks bool
	// PrintCommands prints each rendered command before running it.
	PrintCommands bool
	// Verbose streams hook output to the caller. When false, output is
	// captured and only returned on failure.
	Verbose bool
	// Output, when set, receives the echoed command lines and, when
	// Verbose is true, the live command output of each step. It is used
	// by the interactive TUI to stream hook execution into a viewport.
	// When Output is nil, PrintCommands falls back to the package logger.
	Output io.Writer
	// StepStart, when set, is called before each step runs so the caller
	// can print a styled header. Only used when PrintCommands is true;
	// falls back to a plain log line otherwise.
	StepStart func(kind, cmd string)
}

HookOptions controls how hooks are reported and whether they run.

type HookView

type HookView struct {
	// Phase is "pre" or "post".
	Phase string
	// Run is the inline shell command for [[pre]]/[[post]] steps.
	Run string
	// File is the absolute path to the script for _pre/_post hooks.
	File string
	// IsFile reports whether this entry refers to a script file.
	IsFile bool
}

HookView is one reviewable hook entry surfaced by the interactive TUI. A hook is either an inline [[pre]]/[[post]] step (Run set, File empty) or a script file discovered in _pre/ or _post/ (File set, IsFile true).

func CollectHooks

func CollectHooks(t *Template) []HookView

CollectHooks returns every hook the template would run, in execution order: inline [[pre]] steps, then _pre/ scripts, then inline [[post]] steps, then _post/ scripts. Missing _pre/_post directories are skipped.

type IncludeRule

type IncludeRule struct {
	Path string `toml:"path"`
	If   string `toml:"if"`
}

IncludeRule gates files or directories on a param-driven condition. Path is a glob relative to _base/. If is non-empty it is rendered as a Go template against the resolved values; the file/directory is included only when the result is truthy. An empty If always includes.

type Loader

type Loader struct {
	CacheDir string // where to store cloned templates; defaults to ~/.config/spin/templates
	// PromptInvalidPinned is called when a template exists on disk but
	// fails validation. It returns true to keep the clone, false to
	// remove it, or a non-nil error to surface directly. A nil hook
	// keeps the clone (used by non-interactive runs and tests).
	PromptInvalidPinned func(name, localPath string, detectErr error) (bool, error)
	// PromptExistingDest is called when cloneGit finds dest already
	// exists. A nil hook wipes and re-clones, which suits scripts/CI.
	PromptExistingDest func(name, localPath string) (DestAction, error)
}

Loader fetches a template from a local path, a git URL, or a name in ~/.config/spin/pinned.json, and returns it ready to render.

func NewLoader

func NewLoader(cacheDir string) *Loader

func (*Loader) Clear

func (l *Loader) Clear(ref string) error

Clear removes the cached clone of the given ref (the sanitised directory name produced by SanitiseRepoName). Used by tests to keep the cache clean between runs. No-op if the ref is not cached.

func (*Loader) Lister

func (l *Loader) Lister() ([]string, error)

Lister returns the basenames of all top-level entries in the loader's cache directory. Used by tests to assert cache behaviour without exposing the cache dir directly.

func (*Loader) Load

func (l *Loader) Load(spec string) (*Template, error)

Load fetches a template by source spec using a background context. Prefer LoadContext when a cancellable context is available.

func (*Loader) LoadContext

func (l *Loader) LoadContext(ctx context.Context, spec string) (*Template, error)

LoadContext fetches a template by source spec. The spec can be a local path, a git URL, a `<alias>/<id>` registry shorthand, or a pinned name from ~/.config/spin/pinned.json. ctx bounds any git clone the loader performs.

type PostStep

type PostStep struct {
	Run string `toml:"run"`
}

PostStep is one command in the post-scaffold hook. The shell command is templated against the resolved param + flag values, then run via `sh -c` in the project root. Steps execute in order; the hook stops on the first failure.

This is intentionally a list, not a single string -- it matches the shape npm scripts, Taskfile.yml, and Just converged on, and gives a clean path to per-step metadata (env, cwd, on_error) without a breaking schema change.

type PreStep

type PreStep struct {
	Run string `toml:"run"`
}

PreStep is one command in the pre-scaffold hook. It runs after params are resolved but before files are rendered, via sh -c in the project root. Steps execute in order; the hook stops on the first failure.

type SpinToml

type SpinToml struct {
	Name           string                 `toml:"name"`
	Description    string                 `toml:"description"`
	Type           string                 `toml:"type"`     // "tui" | "cli" | "lib" | ...
	Language       string                 `toml:"language"` // "go" | "rust" | "ts" | ...
	Author         Author                 `toml:"author"`
	License        string                 `toml:"license"`
	Repository     string                 `toml:"repository"`
	MinSpinVersion string                 `toml:"min_spin_version"`
	Exclude        []string               `toml:"exclude"`
	Include        []IncludeRule          `toml:"include"`
	Params         map[string]params.Spec `toml:"params"`
	Pre            []PreStep              `toml:"pre"`
	Post           []PostStep             `toml:"post"`
	Tags           []string               `toml:"tags"`
}

SpinToml is the parsed manifest at the root of an external template.

Example:

name            = "rust-cli"
description     = "Minimal Rust CLI"
type            = "cli"
language        = "rust"
license         = "MIT"
repository      = "https://github.com/me/rust-cli-template"
min_spin_version = "0.2.0"

[author]
name  = "Sam"
email = "sam@example.com"
url   = "https://sam.example.com"

[params]
project_name = { type = "text", prompt = "Project name" }
edition      = { type = "select", options = ["2021", "2024"], default = "2021" }

[[post]]
run = "cargo init --name {{.project_name}}"

[[post]]
run = "git init && git add -A"

func ParseSpinToml

func ParseSpinToml(path string) (*SpinToml, error)

ParseSpinToml reads and parses a spin.toml file from disk.

func ParseSpinTomlBytes

func ParseSpinTomlBytes(b []byte) (*SpinToml, error)

type Template

type Template struct {
	Name        string    // dir name, e.g. "rust-cli"
	Source      string    // local path on disk (post-clone)
	Repo        string    // git URL, if any
	Spec        string    // original spec the user typed (may differ from Repo/Source when resolved via a registry shorthand)
	SpinToml    *SpinToml // parsed spin.toml
	BaseDir     string    // _base/ inside Source
	PreHookDir  string    // _pre/ inside Source (optional)
	PostHookDir string    // _post/ inside Source (optional)
}

Template is a loaded external template, ready to render.

func Detect

func Detect(dir string) (*Template, error)

A valid template has spin.toml and _base/.

func (*Template) BuildForm

func (t *Template) BuildForm(values map[string]any) (*huh.Form, error)

BuildForm constructs a huh.Form from the template's spin.toml params. The user fills the form; the resolved values are written back into the supplied map.

func (*Template) Hints

func (t *Template) Hints() []string

Hints returns a one-line-per-param summary, used by `spin new <template> --print-params` and the template README.

func (*Template) Render

func (t *Template) Render(values map[string]any) (map[string][]byte, error)

Render walks the template's _base/ tree, rendering each .tmpl file against the supplied values. The output is a rel-path → bytes map.

values is the resolved param + flag map. Keys ending in `.Name` are also available as `.Name` for backwards compat with the existing scaffold package.

Files whose path (relative to _base/, with the .tmpl extension stripped) matches any glob in t.SpinToml.Exclude are skipped - they never reach the output tree. This is how templates opt out of files (e.g. a CI badge, a contributor list) that should stay out of the generated project.

If t.SpinToml.Include rules exist, only files matching at least one true rule are included. A rule with an empty If always includes.

func (*Template) RenderTo

func (t *Template) RenderTo(ctx context.Context, dest string, values map[string]any) error

RenderTo writes the rendered files to dest. Same path-traversal guard as scaffold.emit.

func (*Template) RenderToWithPost

func (t *Template) RenderToWithPost(ctx context.Context, dest string, values map[string]any, opts HookOptions) error

RenderToWithPost is the full v2.0 template pipeline:

  1. Render the template to an in-memory file map.
  2. Write the files to dest (path-traversal-safe via writeFiles).
  3. Run the post-hook (if any) in dest.
  4. Walk dest and delete every spin.toml file found (TPL-16: "spin.toml is deleted from the output after a successful render").

Returns the first non-nil error encountered. The post-hook and the spin.toml deletion are best-effort cleanup operations: if the post-hook fails, the spin.toml deletion still runs.

func (*Template) ResolveForm

func (t *Template) ResolveForm(values map[string]any, interactive bool) (map[string]any, error)

ResolveForm runs the form (or applies defaults in non-interactive mode) and returns the resolved values ready for Render().

Returned values are unwrapped to raw Go primitives (string, int, bool, []string) so text/template rendering produces sensible output (e.g. `{{.project_name}}` interpolates as the name, not the params.Value struct dump).

Order of operations is significant: defaults are applied first, THEN any caller-supplied values are layered on top. This ensures explicit values from the CLI or pre-apply map win over the template's own defaults.

Jump to

Keyboard shortcuts

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