lets Task Runner
Use this skill when working in a project that uses lets, usually indicated by a lets.yaml file.
Workflow
- Inspect
lets.yamlbefore changing or running tasks. Also check all files declared inmixins; those files extend or override the main config. Mixins prefixed with-are optional and may be gitignored. - Use
lets --helpto list commands and global flags. - Use
lets help <command>to inspect a command's description, options, and usage before running it. - Prefer project-defined
letscommands for build, test, lint, format, docs, and release workflows instead of invoking underlying tools directly. - After editing config, run the affected
letscommand or the repository's standard test/lint task.
Config Basics
The main config is usually lets.yaml. Create one with lets --init if the project does not have it.
Minimal config:
shell: bash
commands:
test:
description: Run tests
cmd: go test ./...
Top-level fields:
version: minimum required lets version.shell: default shell for commands; commonlybash.env: global environment available to every command.env_file: dotenv files loaded for every command.before: script prepended to every command and dependency invocation.init: script run once per lets invocation before the first command.mixins: additional lets config files or remote mixins.commands: map of task names to command definitions.
Writing Commands
Use short syntax for simple commands:
commands:
fmt: go fmt ./...
Use long syntax when you need descriptions, dependencies, env, options, checksums, or cleanup:
commands:
test:
description: Run unit tests
depends: [generate]
env:
GOFLAGS: -count=1
cmd: go test ./...
Command fields:
cmd: shell command to run.description: shown in command help.work_dir: run from a path relative to the config directory.shell: override shell for one command.after: cleanup script that runs even ifcmdfails.depends: commands that must run first.env: command-specific environment.options: docopt-style CLI options exposed asLETSOPT_*andLETSCLI_*environment variables.env_file: command-specific dotenv files.checksumandpersist_checksum: calculate file checksums and detect whether inputs changed.refandargs: reuse another command with arguments.group: organize commands in help output.
cmd can be a string, multiline string, array, or experimental map:
commands:
build:
cmd: |
echo "Building"
go build ./cmd/lets
test:
cmd:
- go
- test
- ./...
dev:
cmd:
api: go run ./cmd/api
web: npm run dev
With array cmd, extra CLI args are appended. For example lets test -run TestName runs go test ./... -run TestName.
For map cmd, users can select entries with global flags before the command name, such as lets --only api dev or lets --exclude web dev.
Options And Arguments
Use options for user-facing command arguments. It is a docopt usage block.
commands:
release:
description: Create a release
options: |
Usage: lets release <version> [--dry-run] [--message=<message>]
Options:
<version> Version to release
--dry-run Print actions without changing anything
--message=<message> Release message
cmd: |
if [[ -n "${LETSOPT_DRY_RUN}" ]]; then
echo "Dry run for ${LETSOPT_VERSION}"
fi
echo "Message: ${LETSOPT_MESSAGE}"
Rules:
- Positional args become
LETSOPT_<NAME>, for example<version>becomesLETSOPT_VERSION. - Long flags become uppercase with
-converted to_, for example--dry-runbecomesLETSOPT_DRY_RUN. LETSOPT_*contains parsed values such astrue,staging, or positional args.LETSCLI_*contains the raw CLI fragment, useful when forwarding flags to another command.- Use kebab-case for CLI flags, not snake_case.
Environment
Global env applies to all commands. Command env extends or overrides it.
env:
TARGET: dev
IMAGE:
sh: echo "app-${TARGET}"
commands:
build:
env:
TAG:
sh: git rev-parse --short HEAD
cmd: docker build -t "${IMAGE}:${TAG}" .
Environment entries are evaluated in declaration order, so later entries can reference earlier ones. Command env can also reference global env.
Use env_file to load dotenv files:
env:
TARGET: dev
env_file:
- .env
- -.env.local
commands:
up:
env_file:
- .env.${TARGET}
- name: .env.required
required: true
cmd: docker compose up
Rules:
-filenamemeans optional, equivalent torequired: false.env_filepaths are resolved relative to the config directory, notwork_dir.- Values from env files override values from
env. - File names are expanded after available env values are resolved.
Dependencies And Cleanup
Use depends for prerequisite commands. Dependencies run before the command.
commands:
build:
cmd: docker build -t app .
test:
depends: [build]
cmd: go test ./...
Dependencies can pass args or env to the dependency command:
commands:
test:
depends:
- name: build
args: [--verbose]
env:
TARGET: test
cmd: go test ./...
Use after for cleanup that must happen even when the command fails:
commands:
redis:
cmd: docker compose up redis
after: docker compose stop redis
Use init for setup that should run once per lets invocation. Avoid heavy before scripts because before runs before each command and dependency.
Checksums
Use checksum when command behavior depends on file contents. lets calculates SHA1 checksums and exposes them as env vars.
commands:
deps:
checksum:
deps:
- package.json
- package-lock.json
persist_checksum: true
cmd: |
if [[ "${LETS_CHECKSUM_DEPS_CHANGED}" == "true" ]]; then
npm install
fi
Rules:
- A list checksum exposes
LETS_CHECKSUM. - A named checksum map exposes
LETS_CHECKSUM_<NAME>plus combinedLETS_CHECKSUM. persist_checksum: trueexposesLETS_CHECKSUM_CHANGEDand namedLETS_CHECKSUM_<NAME>_CHANGED.- Persisted checksums update only after a successful command exit.
- Glob patterns are supported in checksum file lists.
Mixins
Use mixins to split large configs or share common commands.
shell: bash
mixins:
- lets.build.yaml
- -lets.my.yaml
commands:
test:
cmd: go test ./...
Rules:
- Local mixins are paths to other lets config files.
- A mixin name prefixed with
-is optional and may not exist; this is useful for gitignored personal config. - Remote mixins use
urland optionalversion; lets caches them under.lets/mixins. - When editing a command, search mixins too because commands may be defined, extended, or overridden there.
Reusing Commands
Use experimental ref and args to create aliases with predefined arguments.
commands:
hello:
cmd: echo Hello $@
hello-world:
ref: hello
args: World
ref is only compatible with args; do not combine it with normal command directives.
Use group to organize help output:
commands:
build:
group: Build
cmd: npm run build
Common Pitfalls
- Do not commit
.lets/, generated binaries, coverage files, dependency directories, or personal mixins such aslets.my.yamlunless the repository explicitly tracks them. - Prefer one clear command over several near-duplicate commands. Use
options,args, orrefwhen that keeps the config simpler. - Keep
descriptionfirst-line concise; help output uses the first line. - Use multiline
cmd: |for scripts with conditionals or multiple shell statements. - Quote env values that look like booleans or numbers when they must stay strings.
- Remember
work_dirchanges wherecmdruns, butenv_fileremains relative to the config directory. - Prefer project-defined
letstasks for validation, for examplelets test,lets lint, orlets fmtwhen present.
Source: lets-cli/lets — distributed by TomeVault.