recipe

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package recipe loads, validates, and resolves recipes: saved `session` scripts with a small header.

A recipe is deliberately not a language. Every step's `run` is exactly the argv array a `session` stdin line carries, so anything valid in `session` is valid here and vice versa. This package never touches Chrome and never runs anything: it reads a file, validates it, substitutes declared inputs into argv elements, and hands back a Plan of argv lines for the caller to feed through the existing `session` execution path.

The split is the point. Everything a recipe can get wrong statically — malformed YAML, an undeclared `{{placeholder}}`, a missing required input, an unknown --set key, an out-of-range --from-step — is an error from this package, raised before the CLI connects to a browser.

Index

Constants

View Source
const (
	OnErrorAbort    = "abort"
	OnErrorContinue = "continue"
)

Error abort/continue values for a step's on_error.

View Source
const MaxSteps = 200

MaxSteps caps a recipe's length. A bound removes a whole category of design questions (and of runaway shared files) for the price of one constant.

Variables

View Source
var ErrNotFound = errors.New("recipe not found")

ErrNotFound reports a name that matched no file in any search directory.

Functions

func Find

func Find(name string, dirs []Dir) (path, source string, err error)

Find locates a recipe file by name, returning the first match in the search path along with the source that provided it.

func Template

func Template(name string) string

Template returns the scaffold `recipe new` writes. It is a working recipe, not a sketch: it loads and validates as-is, because a scaffold the validator rejects is the worst possible introduction to the format.

func ValidName

func ValidName(name string) error

ValidName reports whether a name is usable as a recipe name. It is enforced wherever a name reaches the filesystem: the name is joined into a path, so separators and `..` must never survive this check.

Types

type Dir

type Dir struct {
	Path   string
	Source string // "project" | "user" | "--dir"
}

Dir is one entry in the recipe search path.

func SearchPath

func SearchPath(cwd, userConfigDir, extra string) []Dir

SearchPath returns the ordered recipe search path; the first match wins.

Project-local beats user-global on purpose: a repo that carries .chrome-cdp/recipes/ hands its automations to everyone who clones it, and a teammate's checkout must win over whatever they happen to have in their own config dir. Empty arguments are skipped, so a caller that cannot determine a working directory simply gets a shorter path.

type Entry

type Entry struct {
	Name   string
	Path   string
	Source string
	Recipe *Recipe
	Err    error
}

Entry is one row of a recipe listing: a loaded recipe, or the error that stopped it loading. One malformed file must not hide every other recipe.

func List

func List(dirs []Dir) []Entry

List enumerates recipes across the search path, first match per name winning so a project-local recipe shadows a user-global one of the same name.

type Input

type Input struct {
	Required    bool   `json:"required"`
	Default     string `json:"default,omitempty"`
	Description string `json:"description,omitempty"`
}

Input declares one parameter. That is the whole schema — no types, no validation expressions; a recipe that needs them should be a program calling `session`.

type Opts

type Opts struct {
	Set      map[string]string // values from --set
	Target   string            // overrides the recipe's own `target:` when set
	FromStep int               // 1-based start step; 0 means "from the beginning"
	Split    Splitter          // optional; see Splitter
}

Opts are the run-time knobs Resolve applies.

type Plan

type Plan struct {
	Recipe   *Recipe
	Inputs   map[string]string
	Steps    []PlanStep
	FromStep int // 1-based; 1 unless --from-step moved it
	Target   string
}

Plan is a fully resolved recipe: nothing left to decide, nothing left to look up.

func Resolve

func Resolve(r *Recipe, opts Opts) (*Plan, error)

Resolve turns a validated Recipe into a Plan: it checks the supplied inputs against the declared ones, substitutes them into argv elements, injects the effective --target, and applies --from-step.

Substitution is a single pass, so a value that itself contains `{{...}}` is carried through literally rather than re-expanded — an input can never smuggle in another input's value.

type PlanStep

type PlanStep struct {
	// Index is the 1-based position in the recipe file, preserved across
	// --from-step so a summary points at the step the author would recognise.
	Index   int      `json:"step"`
	Label   string   `json:"label,omitempty"`
	Argv    []string `json:"run"`
	OnError string   `json:"on_error"`
}

PlanStep is one resolved step: argv with every placeholder substituted, ready to hand to the `session` execution path unchanged.

type Recipe

type Recipe struct {
	Name        string           `json:"name"`
	Description string           `json:"description,omitempty"`
	Inputs      map[string]Input `json:"inputs,omitempty"`
	Target      string           `json:"target,omitempty"`
	Steps       []Step           `json:"steps"`

	// Path is where the recipe was read from; Source names the search-path
	// entry that provided it ("project", "user", or "--dir").
	Path   string `json:"path"`
	Source string `json:"source"`
}

Recipe is a loaded, schema-valid recipe. Inputs are unresolved at this stage: Run still carries `{{placeholder}}` text, and every placeholder in it is guaranteed to name a declared input.

func Load

func Load(path, source string) (*Recipe, error)

Load reads and validates one recipe file. source labels which search-path entry provided it and is carried through to the envelope.

func (*Recipe) InputNames

func (r *Recipe) InputNames() []string

InputNames returns the declared input names in sorted order, so listing and showing a recipe is deterministic despite the underlying map.

type Splitter

type Splitter func(argv []string) (flagIdx, posIdx []int, ok bool)

Splitter classifies one step's argv AS WRITTEN — before any substitution — into the elements the command tree may parse (the command path and the step's own flags, with their values) and the elements that must arrive as data. Indices are into the argv it was given; an index in neither slice is dropped, which is how a step's own `--` is absorbed and re-emitted in the one place it belongs. ok is false when the argv names no command it can resolve, in which case Resolve leaves the argv alone and lets the command tree produce its ordinary usage error.

It reads the argv as written because that is the only text the recipe's AUTHOR wrote. What is a flag has to be decided there and never by an input value: `--set sel=--target=@2` substituted into a step's positional is data, not a second target.

The recipe package cannot implement this — flag arity lives in the command tree — so the CLI supplies it. Resolve without one keeps the argv in written order, which is what the pure tests here use.

type Step

type Step struct {
	Label   string   `json:"label,omitempty"`
	Run     []string `json:"run"`
	OnError string   `json:"on_error"`
}

Step is one validated step: an argv array plus its failure policy.

Jump to

Keyboard shortcuts

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