stave
Stave is a Go-native, make-like command runner forked from mage. Targets are plain exported Go functions in a stavefile, with helpers for dependencies, shell commands, watch mode and git hooks.
Set up
Install and bootstrap7
- Install the latest stave into GOBIN
- Install with Homebrew, completion included
- Build and install from a stave source checkout
- Write a starter stavefile.go if no stavefiles exist
- Add the module so pkg/st and pkg/sh imports resolve
- Run direnv subcommands through the embedded direnv
- Reload the .envrc environment without a direnv binary
Everyday
Running targets11
- Run the Default target, or list targets when none is set
- Run one target, names are case-insensitive
- Run several targets one after another
- Pass positional arguments to a typed target
- Run a target inside a namespace
- Run the namespace's Default method
- Run a target through an entry in Aliases
- Verbose run, shows sh.Run output and dependency lines
- Cancel the target context after a timeout
- Read stavefiles from another directory
- Run targets with DIR as the working directory
Listing and help7
- List targets with synopses, [W] marks watch targets
- Show a target's full doc comment and usage line
- Keep line breaks from doc comments in the list
- Print the installed stave version
- Show CLI help and every flag
- Load target completion into the current fish shell
- Print a completion script for bash, zsh, fish or powershell
Workflows
Common recipes11
- Make plain stave run the build
- Aggregate target that runs checks in parallel
- Ordered pipeline for CI
- Static Go build
- Skip work when the output is up to date
- stave.yaml entry to lint before every commit
- Next vX.Y.Z tag from Conventional Commits
- Next version without the v prefix
- Check the changelog has the upcoming version
- Link version headings to tag diffs
- Constrained, hook-free CI run
Configure
Environment variables13
- Same as -v, accepts true, yes or 1
- Same as -d
- Request dry-run mode, same as --dryrun
- Same as --multiline
- Go binary used to compile stavefiles
- Override the compiled binary cache directory
- Hash stavefiles directly instead of relying on GOCACHE
- Ignore Default and list targets when none is given
- Enable color in a compiled stavefile's own output
- Color for target names, e.g. Cyan or HiCyan
- Disable colored output entirely
- Cap parallelism, also sets GOMAXPROCS for targets
- Suppress decorative hook output, automatic in CI
Git hooks and config13
- List configured hooks and install status
- Show setup instructions for stave.yaml hooks
- Write hook scripts to .git/hooks or core.hooksPath
- Overwrite existing hooks not installed by stave
- Remove every stave-managed hook
- Run a hook's targets by hand for debugging
- Skip all stave hooks for one command
- Trace hook scripts with set -x
- Sourced by every hook first, e.g. to fix PATH
- Show the effective merged configuration
- Create the default user config file
- Show config, cache and data directory paths
- Project config with go_cmd, verbose, target_color and hooks
Reference
Stavefile structure10
- Build tag every stavefile needs on its first line
- Stavefiles must be package main
- File directive that keeps doc comment line breaks in -l
- Subdirectory form, every .go file inside is included without a tag
- Target to run when stave is called with no arguments
- Extra names that map to targets
- Doc comment's first sentence becomes the -l synopsis
- Unexported functions are helpers, never targets
- Shell helper package
- Deps, errors, namespaces and runtime queries
Targets and arguments8
- Simplest target signature
- Return an error to fail the run
- Receive the target context, cancelled on timeout
- Typed args parsed from the command line
- Context first, then typed arguments
- Supported argument types
- Pass arguments positionally after the target
- Show the usage line, e.g. stave greet <name> <times>
Namespaces and imports7
- Declare a namespace type
- Method becomes the build:docker target
- Runs when only the namespace name is given
- Use namespaced targets as dependencies
- Namespaced target with arguments as a dependency
- Comment above an import exposes its targets at the root
- Expose an imported package's targets as ALIAS:target
Dependencies9
- Run dependencies in parallel, each once per invocation
- Run dependencies one after another
- Parallel dependencies sharing a context
- Sequential dependencies sharing a context
- Wrap a target with arguments for use in Deps
- Distinct arguments count as distinct dependencies
- Dependencies that restart with a watch rerun
- Clear the run-once record so dependencies can run again
- Clear the run-once record for specific targets
Shell helpers (pkg/sh)14
- Run a command, stdout shown only with -v
- Run a command and always print stdout
- Add env vars, second arg is the working dir or empty
- RunWith that always prints stdout
- Capture stdout, trailing newline removed
- Capture stdout with extra env and a working dir
- Reusable runner, call as goCmd("build", "./...")
- Reusable output capturer, call as git("status")
- Full I/O control, returns whether the command ran
- Run with explicit stdin, stdout and stderr
- Piper with extra env and a working dir
- Build an *exec.Cmd bound to the active target context
- Remove a path recursively, nil if it is missing
- Copy a file to dst, preserving permissions
Context and errors10
- Fail with a message and a specific exit status
- Formatted Fatal
- Exit code from an error, 0 for nil, 1 when unknown
- False when the command was not found or not executable
- Exit code of a failed shell command
- Context of the nearest running target
- True when -v or STAVEFILE_VERBOSE is set
- True when -d or STAVEFILE_DEBUG is set
- Go command in use, go unless STAVEFILE_GOCMD is set
- Name of the target requested on the command line
File targets and watch10
- True if dst is missing or older than any source
- Path check with glob sources
- Compare newest files in directory trees
- True if any source is newer than a time
- Newest modification time under paths
- Ignore .gitignore patterns up to the repo root
- Add a gitignore-style pattern to the ignore list
- Rerun this target when matching files change
- Run once, then rerun on each change until interrupted
- True when the run is in watch mode
Troubleshoot
Compile, cache and debug11
- Print debug messages for parsing, compiling and caching
- Print DRYRUN lines instead of running sh commands
- Force recompilation of the cached stavefile binary
- Keep the generated mainfile for inspection
- Remove old compiled binaries from the cache dir
- Compile with a different go binary
- Write a standalone binary of the stavefile
- Cross-compile the stavefile binary
- Pass linker flags to the compiled binary
- Run a compiled stavefile with no Go toolchain needed
- Compiled binaries accept -v, -d, -i and -t