Skip to content

Types and control flow

Type System

Primitive Types

  • string: Text values, support interpolation
  • number: Integer and floating-point numbers
  • boolean: true or false
  • duration: Time durations (e.g., "5m", "2h", "30s")

Collection Types

  • array: Ordered list of values [1, 2, 3]
  • object: Key-value pairs {name: "value", count: 42}

Special Types

  • command: Shell command that can be executed
  • path: File system path with validation
  • url: URL with protocol validation
  • regex: Regular expression pattern
  • secret: Secure value (not logged in plain text)

Type Inference

The compiler infers types based on context:

let count be 42                    # number
let name be "hello"                # string
let enabled be true                # boolean
let timeout be "5m"                # duration
let files be ["a.txt", "b.txt"]    # array of strings
let config be {port: 8080}         # object

Type Constraints

Parameters can specify type constraints:

requires port as number between 1000 and 9999
requires timeout as duration
requires files as list of paths
requires config as object

Control Flow

Conditional Execution

Simple Conditions

# Boolean conditions
if enabled:
  start service

if not maintenance_mode:
  accept traffic

# Comparison conditions
if replicas > 0:
  scale deployment

if version >= "2.0.0":
  use new features

# Empty/non-empty conditions
if $features is empty:
  info "No features specified"

if $features is not empty:
  info "Features: {$features}"

if $name is "":
  warn "Name is required"

# Stable semantic-version ordering
if $release_version is older than version "{$latest_version}":
  fail "Release {$release_version} is behind {$latest_version}"

if $candidate is newer than version "{$current_version}":
  info "An upgrade is available"

# Folder/directory empty conditions
if folder "build" is empty:
  info "Build directory is empty"

if folder "dist" is not empty:
  info "Distribution files exist"

if directory "/tmp/cache" is empty:
  run "rm -rf /tmp/cache"

if dir "{$output_path}" is not empty:
  warn "Output directory contains files"

# Exact file-content comparisons
if file "./generated.json" matches file "./vendored.json":
  info "The files contain the same bytes"

if file "./generated.json" not matches file "./vendored.json":
  fail "The vendored file is out of date"

older than version and newer than version compare numeric stable MAJOR.MINOR.PATCH values. Both operands may be parameters, captured variables, or interpolated strings. Values such as v1.2.3, release candidates, and partial versions are rejected; normalize or extract a stable version before comparing.

file … matches file … compares the complete contents byte-for-byte. It does not normalize line endings, whitespace, text encoding, or structured data. file … not matches file … negates that result. Both paths support variable interpolation, resolve using the task's current workdir, and must be readable; an unreadable or missing operand fails condition evaluation with a path-specific error.

When-Otherwise Conditions

The when-otherwise syntax provides a clean alternative to if-else for simple conditional logic. There are no meaningful differences between when-otherwise or if-else. Use whatever feels more semantic for your specific use case.

# Basic when-otherwise
when $platform is "windows":
  step "Building Windows binary with .exe extension"
otherwise:
  step "Building Unix binary without extension"

# When without otherwise (optional else clause)
when $environment is "production":
  step "Deploy with production settings"
  step "Enable monitoring"

# Nested when-otherwise
when $platform is "windows":
  info "Windows platform detected"
  when $arch is "amd64":
    step "Building for Windows x64"
  otherwise:
    step "Building for Windows ARM"
otherwise:
  info "Unix-like platform detected"
  when $platform is "mac":
    step "Building for macOS"
  otherwise:
    step "Building for Linux"

# When-otherwise in loops (matrix execution)
for each $platform in ["windows", "linux", "mac"]:
  when $platform is "windows":
    run "GOOS={$platform} go build -o app.exe"
  otherwise:
    run "GOOS={$platform} go build -o app"

Supported Condition Types:

  • String equality: $var is "value"
  • String inequality: $var is not "value"
  • Stable version ordering: $left is older than version "{$right}", $left is newer than version "{$right}"
  • Exact file comparison: file "a" matches file "b", file "a" not matches file "b"
  • Empty checks: $var is empty, $var is not empty
  • All condition types supported by if statements

Key Features:

  • Clean, readable syntax for simple conditions
  • Optional otherwise clause (equivalent to else)
  • Full nesting support
  • Works seamlessly with loops and matrix execution
  • Consistent variable scoping rules

Smart Detection Conditions

# Tool availability detection
if docker is available:
  build container
else:
  error "Docker is required"

if docker is not available:
  error "Docker is required for this task"
  fail "Missing dependency"

if kubernetes is available:
  deploy to cluster

# Multiple tool availability check
# For "is available": ALL tools must be available (AND logic)
if docker,"docker-compose" is available:
  info "Docker and Docker Compose are both available"

# Alternative: use 'are' for better readability with multiple tools
if docker,"docker-compose" are available:
  info "Docker and Docker Compose are both available"

# For "is not available": ANY tool must be unavailable (OR logic)
if docker,"docker-compose",kubectl is not available:
  error "One or more required tools are missing"
else:
  info "All required tools are available"

# Alternative: use 'are not' for better readability
if docker,"docker-compose",kubectl are not available:
  error "One or more required tools are missing"

# For "is running": ALL tools must be running (AND logic)
if docker,"docker compose" are running:
  info "Docker CLI and daemon are ready"

# For "is not running": ANY tool may be down (OR logic)
if docker,"docker compose" are not running:
  fail "Docker is installed, but the runtime is not reachable"

# Availability can be chained with a version check
if "golangci-lint" is available and version >= "2.12":
  info "golangci-lint is installed and new enough"
else:
  fail "golangci-lint >= 2.12 is required"

# File/directory detection
if file "package.json" exists:
  install npm dependencies

if directory ".git" exists:
  commit changes

# Service detection
when symfony is detected:
  run symfony console commands

when node project exists:
  use npm or yarn

Filesystem condition paths expand the current user's home directory and process environment variables before they are resolved against the task workdir. The supported forms are ~, ~/path, $NAME/path, and ${NAME}/path. Undefined environment variables and named-user home paths such as ~generic/path remain literal.

if folder "~/Library/Audio/Plug-Ins/VST3" exists:
  info "The macOS VST3 folder exists"

if folder "$HOME/.config/example" not exists:
  warning "The example configuration folder is missing"

if directory "$HOME/.cache/example" does not exist:
  create folder "$HOME/.cache/example"

folder, directory, and dir are interchangeable. The negative existence forms not exists and does not exist are also equivalent.

Port Conditions

A port condition probes a TCP port natively (no external tools) and branches on whether something is listening:

if port 5432 is open on "localhost":
  info "PostgreSQL is already running"
else:
  start local database container

if port {redis_port} is not open on "{redis_host}" with timeout "2s":
  info "Redis is down - skipping cache warmup"

is open is true when a TCP dial succeeds, i.e. the port is in use. Host, port, and the optional with timeout value all support interpolation; the timeout accepts Go durations ("2s") or bare seconds and defaults to 5 seconds. In --dry-run mode no connection is opened and the condition evaluates as if the port were closed.

Docker Conditions

A Docker condition asks the Docker daemon whether a resource exists and branches on the answer. Networks are supported:

if docker network "proxy" exists:
  info "Reusing the existing proxy network"
else:
  call task "create-proxy-network"

when docker network "{app_network}" not exists:
  warn "Network {app_network} is missing - orchestration will create it"
otherwise:
  info "Network ready"

The network name supports interpolation. In --dry-run mode the daemon is not queried and the condition evaluates as if the network were missing. The docker <resource> "<name>" [not] exists shape is designed to grow further resource variants (containers, images, volumes) over time.

Compound Conditions

# Logical operators
if docker is running and kubernetes is available:
  deploy containerized application

if environment is "production" or environment is "staging":
  require approval

# Parentheses for grouping
if (environment is "production" and git repo is clean) or force_deploy:
  proceed with deployment

Iteration

Simple Iteration

for each $item in $collection:
  process item

# With index
for each item at index in collection:
  info "Processing item {index}: {item}"

Parallel Execution

for each region in ["us-east", "eu-west"] in parallel:
  deploy to {region}

# Parallel with synchronization
run in parallel:
  - unit_tests -> test_results.unit
  - integration_tests -> test_results.integration
  - security_scan -> test_results.security

wait for all to complete

Retry with Backoff

Pause between attempts with a fixed wait (seconds, minutes, or hours):

for each $attempt in ["1", "2", "3"]:
  try:
    run "./flaky-deploy.sh"
    break
  catch:
    warn "Attempt {$attempt} failed, backing off"
    wait {$attempt} seconds

Filtered Iteration

for each file in "src/**/*.js" where file is modified:
  lint {file}

for each container in docker containers where status is "running":
  check health of {container}

Loop Control

for each $service in $services:
  if service is healthy:
    continue

  try:
    restart service
  catch:
    error "Failed to restart {service}"
    break  # Exit loop on critical failure