Skip to content

Task & Just

Language Overview

Task runners are tools for automating common development tasks like building, testing, linting, and deploying applications. This guide covers Task (Taskfile.yml) and Just as modern alternatives to Make, with emphasis on cross-platform compatibility and developer experience.

Key Characteristics

Tool Configuration File Language Cross-Platform Key Features
Task Taskfile.yml YAML Yes (Go binary) Dependencies, variables, includes
Just justfile Custom DSL Yes (Rust binary) Recipes, arguments, shell selection
Make Makefile Custom DSL Partial Widely available, dependencies

When to Use Each Tool

Use Task when:
├── Cross-platform compatibility is critical
├── Team prefers YAML configuration
├── Need complex variable interpolation
└── Want built-in task watching/live reload

Use Just when:
├── Need simple, readable command definitions
├── Want shell flexibility (bash, sh, powershell)
├── Prefer minimal syntax over YAML
└── Need command-line argument passing

Use Make when:
├── Project already uses Makefiles
├── Need file-based dependency tracking
├── Working in Unix-heavy environments
└── Team is familiar with Make syntax

Quick Reference

Category Task Just Make
File Taskfile.yml justfile Makefile
Install brew install go-task brew install just Built-in
Run task <name> just <name> make <name>
List task --list just --list make help
Dependencies deps: [task1, task2] Recipe ordering Prerequisites
Variables vars: block variable := value VAR = value
Comments # comment # comment # comment
Silence silent: true @command @command

Task (Taskfile.yml)

Installation

## macOS
brew install go-task

## Linux (script)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin

## Windows (scoop)
scoop install task

## Windows (chocolatey)
choco install go-task

## Go install
go install github.com/go-task/task/v3/cmd/task@latest

## Verify installation
task --version

Basic Structure

## Taskfile.yml
version: '3'

vars:
  BINARY_NAME: myapp
  BUILD_DIR: ./build

tasks:
  default:
    desc: Show available tasks
    cmds:
      - task --list

  build:
    desc: Build the application
    cmds:
      - go build -o {{.BUILD_DIR}}/{{.BINARY_NAME}} .

  test:
    desc: Run test suite
    cmds:
      - go test -v ./...

  clean:
    desc: Clean build artifacts
    cmds:
      - rm -rf {{.BUILD_DIR}}

Task Dependencies

## Taskfile.yml
version: '3'

tasks:
  deps:
    desc: Install dependencies
    cmds:
      - go mod download

  build:
    desc: Build application
    deps: [deps]  # Run deps first
    cmds:
      - go build -o bin/app .

  test:
    desc: Run tests
    deps: [build]  # Run build first
    cmds:
      - go test ./...

  all:
    desc: Build and test
    deps:
      - build
      - test

  ## Parallel execution
  lint-all:
    desc: Run all linters in parallel
    deps:
      - task: lint-go
      - task: lint-yaml
      - task: lint-markdown

  lint-go:
    cmds:
      - golangci-lint run

  lint-yaml:
    cmds:
      - yamllint .

  lint-markdown:
    cmds:
      - markdownlint "**/*.md"

Variables and Environment

## Taskfile.yml
version: '3'

## Global variables
vars:
  VERSION:
    sh: git describe --tags --always
  BUILD_TIME:
    sh: date -u +"%Y-%m-%dT%H:%M:%SZ"
  GOOS: linux
  GOARCH: amd64

## Environment variables
env:
  CGO_ENABLED: "0"
  GO111MODULE: "on"

## Dynamic variables from commands
dotenv: ['.env', '.env.local']

tasks:
  build:
    desc: Build with version info
    vars:
      LDFLAGS: >-
        -X main.Version={{.VERSION}}
        -X main.BuildTime={{.BUILD_TIME}}
    cmds:
      - go build -ldflags "{{.LDFLAGS}}" -o bin/app .

  print-vars:
    desc: Print all variables
    cmds:
      - echo "Version: {{.VERSION}}"
      - echo "Build Time: {{.BUILD_TIME}}"
      - echo "OS/Arch: {{.GOOS}}/{{.GOARCH}}"

  ## Task-specific variables
  deploy:
    desc: Deploy to environment
    vars:
      DEPLOY_ENV: '{{.DEPLOY_ENV | default "staging"}}'
    cmds:
      - echo "Deploying to {{.DEPLOY_ENV}}"
      - ./deploy.sh {{.DEPLOY_ENV}}

Conditional Execution

## Taskfile.yml
version: '3'

tasks:
  build:
    desc: Build application
    sources:
      - "**/*.go"
      - go.mod
      - go.sum
    generates:
      - bin/app
    cmds:
      - go build -o bin/app .

  ## Only run if sources changed
  test:
    desc: Run tests if code changed
    sources:
      - "**/*.go"
    cmds:
      - go test ./...

  ## Platform-specific tasks
  install-deps:
    desc: Install system dependencies
    cmds:
      - task: install-deps-{{OS}}

  install-deps-darwin:
    internal: true
    cmds:
      - brew install golangci-lint

  install-deps-linux:
    internal: true
    cmds:
      - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh

  install-deps-windows:
    internal: true
    cmds:
      - choco install golangci-lint

  ## Conditional based on variable
  deploy:
    desc: Deploy application
    preconditions:
      - sh: test -f bin/app
        msg: "Binary not found. Run 'task build' first."
      - sh: test -n "$DEPLOY_TOKEN"
        msg: "DEPLOY_TOKEN environment variable required."
    cmds:
      - ./deploy.sh

Including Other Taskfiles

## Taskfile.yml
version: '3'

includes:
  docker: ./taskfiles/Docker.yml
  k8s: ./taskfiles/Kubernetes.yml
  ci:
    taskfile: ./taskfiles/CI.yml
    dir: ./ci
    optional: true

tasks:
  all:
    desc: Run all tasks
    cmds:
      - task: docker:build
      - task: k8s:deploy
## taskfiles/Docker.yml
version: '3'

vars:
  IMAGE_NAME: myapp
  IMAGE_TAG: latest

tasks:
  build:
    desc: Build Docker image
    cmds:
      - docker build -t {{.IMAGE_NAME}}:{{.IMAGE_TAG}} .

  push:
    desc: Push Docker image
    deps: [build]
    cmds:
      - docker push {{.IMAGE_NAME}}:{{.IMAGE_TAG}}

  run:
    desc: Run Docker container
    cmds:
      - docker run -p 8080:8080 {{.IMAGE_NAME}}:{{.IMAGE_TAG}}

Interactive and Watch Mode

## Taskfile.yml
version: '3'

tasks:
  ## Interactive prompts
  release:
    desc: Create a release
    prompt: Are you sure you want to release?
    cmds:
      - goreleaser release --clean

  ## Watch mode for development
  dev:
    desc: Run in development mode with hot reload
    watch: true
    sources:
      - "**/*.go"
    cmds:
      - go run .

  ## Run command in specific directory
  frontend:
    desc: Build frontend
    dir: ./frontend
    cmds:
      - npm ci
      - npm run build

  ## Silent output
  version:
    desc: Print version
    silent: true
    cmds:
      - echo "v1.0.0"

Complete Python Project Example

## Taskfile.yml
version: '3'

vars:
  PYTHON: python3
  VENV: .venv
  VENV_BIN: "{{.VENV}}/bin"
  SRC_DIR: src
  TEST_DIR: tests

env:
  PYTHONPATH: "{{.SRC_DIR}}"

tasks:
  default:
    desc: Show available tasks
    cmds:
      - task --list

  ## Environment setup
  venv:
    desc: Create virtual environment
    status:
      - test -d {{.VENV}}
    cmds:
      - "{{.PYTHON}} -m venv {{.VENV}}"

  install:
    desc: Install dependencies
    deps: [venv]
    sources:
      - requirements.txt
      - requirements-dev.txt
    cmds:
      - "{{.VENV_BIN}}/pip install -r requirements.txt"
      - "{{.VENV_BIN}}/pip install -r requirements-dev.txt"

  ## Code quality
  format:
    desc: Format code
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/black {{.SRC_DIR}} {{.TEST_DIR}}"
      - "{{.VENV_BIN}}/isort {{.SRC_DIR}} {{.TEST_DIR}}"

  lint:
    desc: Run linters
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/flake8 {{.SRC_DIR}} {{.TEST_DIR}}"
      - "{{.VENV_BIN}}/mypy {{.SRC_DIR}}"
      - "{{.VENV_BIN}}/bandit -r {{.SRC_DIR}}"

  ## Testing
  test:
    desc: Run tests
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/pytest {{.TEST_DIR}} -v"

  test-cov:
    desc: Run tests with coverage
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/pytest {{.TEST_DIR}} --cov={{.SRC_DIR}} --cov-report=html"

  ## Build and release
  build:
    desc: Build package
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/python -m build"

  publish:
    desc: Publish to PyPI
    deps: [build]
    preconditions:
      - sh: test -n "$PYPI_TOKEN"
        msg: "PYPI_TOKEN environment variable required"
    cmds:
      - "{{.VENV_BIN}}/twine upload dist/* -u __token__ -p $PYPI_TOKEN"

  ## Cleanup
  clean:
    desc: Clean build artifacts
    cmds:
      - rm -rf {{.VENV}} dist build *.egg-info
      - find . -type d -name __pycache__ -exec rm -rf {} +
      - find . -type f -name "*.pyc" -delete

  ## Development
  dev:
    desc: Run development server
    deps: [install]
    cmds:
      - "{{.VENV_BIN}}/uvicorn src.main:app --reload"

  ## All checks
  check:
    desc: Run all checks
    cmds:
      - task: format
      - task: lint
      - task: test

Complete Node.js Project Example

## Taskfile.yml
version: '3'

vars:
  NODE_ENV: development
  NPM: npm

tasks:
  default:
    desc: Show available tasks
    cmds:
      - task --list

  ## Setup
  install:
    desc: Install dependencies
    sources:
      - package.json
      - package-lock.json
    generates:
      - node_modules/.package-lock.json
    cmds:
      - "{{.NPM}} ci"

  ## Development
  dev:
    desc: Start development server
    deps: [install]
    cmds:
      - "{{.NPM}} run dev"

  ## Code quality
  format:
    desc: Format code
    deps: [install]
    cmds:
      - "{{.NPM}} run format"

  lint:
    desc: Run linters
    deps: [install]
    cmds:
      - "{{.NPM}} run lint"

  lint-fix:
    desc: Fix linting errors
    deps: [install]
    cmds:
      - "{{.NPM}} run lint -- --fix"

  typecheck:
    desc: Run type checker
    deps: [install]
    cmds:
      - "{{.NPM}} run typecheck"

  ## Testing
  test:
    desc: Run tests
    deps: [install]
    cmds:
      - "{{.NPM}} test"

  test-watch:
    desc: Run tests in watch mode
    deps: [install]
    cmds:
      - "{{.NPM}} test -- --watch"

  test-cov:
    desc: Run tests with coverage
    deps: [install]
    cmds:
      - "{{.NPM}} test -- --coverage"

  ## Build
  build:
    desc: Build application
    deps: [install]
    env:
      NODE_ENV: production
    cmds:
      - "{{.NPM}} run build"

  ## Docker
  docker-build:
    desc: Build Docker image
    cmds:
      - docker build -t myapp:latest .

  docker-run:
    desc: Run Docker container
    cmds:
      - docker run -p 3000:3000 myapp:latest

  ## Cleanup
  clean:
    desc: Clean build artifacts
    cmds:
      - rm -rf node_modules dist coverage .next

  ## All checks
  check:
    desc: Run all checks
    cmds:
      - task: lint
      - task: typecheck
      - task: test

Just Command Runner

Just Installation

## macOS
brew install just

## Linux (prebuilt binary)
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin

## Arch Linux
pacman -S just

## Windows (scoop)
scoop install just

## Windows (chocolatey)
choco install just

## Cargo (Rust)
cargo install just

## Verify installation
just --version

Just Basic Structure

## justfile

## Set shell for all recipes
set shell := ["bash", "-euo", "pipefail", "-c"]

## Variables
binary_name := "myapp"
build_dir := "./build"

## Default recipe (runs when just is called without arguments)
default:
    @just --list

## Build the application
build:
    go build -o {{build_dir}}/{{binary_name}} .

## Run tests
test:
    go test -v ./...

## Clean build artifacts
clean:
    rm -rf {{build_dir}}

Recipe Dependencies

## justfile

## Recipe with dependencies
all: build test

## Dependencies run first
build: deps
    go build -o bin/app .

## Install dependencies
deps:
    go mod download

## Run tests after build
test: build
    go test ./...

## Parallel execution (dependencies run in parallel by default)
lint-all: lint-go lint-yaml lint-markdown

lint-go:
    golangci-lint run

lint-yaml:
    yamllint .

lint-markdown:
    markdownlint "**/*.md"

Variables and Arguments

## justfile

## Define variables
version := `git describe --tags --always`
build_time := `date -u +"%Y-%m-%dT%H:%M:%SZ"`
goos := "linux"
goarch := "amd64"

## Recipe with positional arguments
deploy env:
    @echo "Deploying to {{env}}"
    ./deploy.sh {{env}}

## Recipe with default argument
greet name="World":
    @echo "Hello, {{name}}!"

## Recipe with variadic arguments
run *args:
    go run . {{args}}

## Recipe with optional argument
build target="":
    #!/usr/bin/env bash
    if [ -z "{{target}}" ]; then
        go build -o bin/app .
    else
        go build -o bin/{{target}} ./cmd/{{target}}
    fi

## Using environment variables
push:
    @if [ -z "$DOCKER_REGISTRY" ]; then \
        echo "DOCKER_REGISTRY not set"; \
        exit 1; \
    fi
    docker push $DOCKER_REGISTRY/myapp:latest

## Export variables to environment
export CGO_ENABLED := "0"
export GO111MODULE := "on"

build-static:
    go build -ldflags="-s -w" -o bin/app .

Conditional Logic

## justfile

## Platform detection
os := os()
arch := arch()

## Platform-specific installation
install-deps:
    #!/usr/bin/env bash
    set -euo pipefail
    case "{{os}}" in
        macos)
            brew install golangci-lint
            ;;
        linux)
            curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh
            ;;
        windows)
            choco install golangci-lint
            ;;
        *)
            echo "Unsupported OS: {{os}}"
            exit 1
            ;;
    esac

## Conditional recipe selection
build-all: (_build os)

_build target:
    @echo "Building for {{target}}"

## Check preconditions
deploy: _check-deploy
    ./deploy.sh

_check-deploy:
    @test -f bin/app || (echo "Binary not found. Run 'just build' first." && exit 1)
    @test -n "$DEPLOY_TOKEN" || (echo "DEPLOY_TOKEN required" && exit 1)

Multi-line Scripts

## justfile

## Multi-line bash script
setup:
    #!/usr/bin/env bash
    set -euo pipefail

    echo "Setting up development environment..."

    # Create directories
    mkdir -p bin logs tmp

    # Install dependencies
    go mod download

    # Setup git hooks
    if [ -d .git ]; then
        cp scripts/pre-commit .git/hooks/
        chmod +x .git/hooks/pre-commit
    fi

    echo "Setup complete!"

## Python script
analyze:
    #!/usr/bin/env python3
    import json
    import sys

    with open('coverage.json') as f:
        data = json.load(f)

    coverage = data.get('total', 0)
    if coverage < 80:
        print(f"Coverage {coverage}% is below 80%")
        sys.exit(1)
    print(f"Coverage {coverage}% meets threshold")

## PowerShell script (Windows)
[windows]
setup-windows:
    #!powershell
    Write-Host "Setting up Windows environment..."
    New-Item -ItemType Directory -Force -Path bin, logs, tmp
    go mod download
    Write-Host "Setup complete!"

Just Python Project Example

## justfile

## Shell configuration
set shell := ["bash", "-euo", "pipefail", "-c"]
set dotenv-load

## Variables
python := "python3"
venv := ".venv"
venv_bin := venv / "bin"
src_dir := "src"
test_dir := "tests"

## Export Python path
export PYTHONPATH := src_dir

## Default recipe
default:
    @just --list

## Create virtual environment
venv:
    {{python}} -m venv {{venv}}

## Install dependencies
install: venv
    {{venv_bin}}/pip install -r requirements.txt
    {{venv_bin}}/pip install -r requirements-dev.txt

## Format code
format: install
    {{venv_bin}}/black {{src_dir}} {{test_dir}}
    {{venv_bin}}/isort {{src_dir}} {{test_dir}}

## Run linters
lint: install
    {{venv_bin}}/flake8 {{src_dir}} {{test_dir}}
    {{venv_bin}}/mypy {{src_dir}}
    {{venv_bin}}/bandit -r {{src_dir}}

## Run tests
test: install
    {{venv_bin}}/pytest {{test_dir}} -v

## Run tests with coverage
test-cov: install
    {{venv_bin}}/pytest {{test_dir}} --cov={{src_dir}} --cov-report=html

## Build package
build: install
    {{venv_bin}}/python -m build

## Publish to PyPI
publish token: build
    {{venv_bin}}/twine upload dist/* -u __token__ -p {{token}}

## Run development server
dev: install
    {{venv_bin}}/uvicorn src.main:app --reload

## Clean build artifacts
clean:
    rm -rf {{venv}} dist build *.egg-info
    find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
    find . -type f -name "*.pyc" -delete

## Run all checks
check: format lint test

Just Node.js Project Example

## justfile

## Shell configuration
set shell := ["bash", "-euo", "pipefail", "-c"]

## Variables
npm := "npm"
node_env := "development"

## Default recipe
default:
    @just --list

## Install dependencies
install:
    {{npm}} ci

## Start development server
dev: install
    {{npm}} run dev

## Format code
format: install
    {{npm}} run format

## Run linters
lint: install
    {{npm}} run lint

## Fix linting errors
lint-fix: install
    {{npm}} run lint -- --fix

## Run type checker
typecheck: install
    {{npm}} run typecheck

## Run tests
test: install
    {{npm}} test

## Run tests in watch mode
test-watch: install
    {{npm}} test -- --watch

## Run tests with coverage
test-cov: install
    {{npm}} test -- --coverage

## Build for production
build: install
    NODE_ENV=production {{npm}} run build

## Build Docker image
docker-build:
    docker build -t myapp:latest .

## Run Docker container
docker-run:
    docker run -p 3000:3000 myapp:latest

## Clean build artifacts
clean:
    rm -rf node_modules dist coverage .next

## Run all checks
check: lint typecheck test

## Start fresh
reset: clean install

Complete Go Project Example

## justfile

## Shell configuration
set shell := ["bash", "-euo", "pipefail", "-c"]

## Variables
binary := "myapp"
build_dir := "bin"
version := `git describe --tags --always 2>/dev/null || echo "dev"`
commit := `git rev-parse --short HEAD 2>/dev/null || echo "unknown"`
build_time := `date -u +"%Y-%m-%dT%H:%M:%SZ"`

## Build flags
ldflags := "-s -w -X main.Version=" + version + " -X main.Commit=" + commit + " -X main.BuildTime=" + build_time

## Export for static builds
export CGO_ENABLED := "0"

## Default recipe
default:
    @just --list

## Install dependencies
deps:
    go mod download
    go mod verify

## Generate code
generate:
    go generate ./...

## Build application
build: deps generate
    go build -ldflags "{{ldflags}}" -o {{build_dir}}/{{binary}} .

## Build for all platforms
build-all: deps generate
    GOOS=linux GOARCH=amd64 go build -ldflags "{{ldflags}}" -o {{build_dir}}/{{binary}}-linux-amd64 .
    GOOS=darwin GOARCH=amd64 go build -ldflags "{{ldflags}}" -o {{build_dir}}/{{binary}}-darwin-amd64 .
    GOOS=darwin GOARCH=arm64 go build -ldflags "{{ldflags}}" -o {{build_dir}}/{{binary}}-darwin-arm64 .
    GOOS=windows GOARCH=amd64 go build -ldflags "{{ldflags}}" -o {{build_dir}}/{{binary}}-windows-amd64.exe .

## Run tests
test:
    go test -v -race ./...

## Run tests with coverage
test-cov:
    go test -v -race -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out -o coverage.html

## Run benchmarks
bench:
    go test -bench=. -benchmem ./...

## Run linters
lint:
    golangci-lint run

## Format code
format:
    gofmt -s -w .
    goimports -w .

## Run application
run *args: build
    ./{{build_dir}}/{{binary}} {{args}}

## Clean build artifacts
clean:
    rm -rf {{build_dir}} coverage.out coverage.html

## Install binary
install: build
    cp {{build_dir}}/{{binary}} $(go env GOPATH)/bin/

## Run all checks
check: format lint test

## Docker build
docker-build:
    docker build -t {{binary}}:{{version}} .

## Docker run
docker-run:
    docker run -p 8080:8080 {{binary}}:{{version}}

Tool Comparison

Feature Comparison Matrix

┌─────────────────────────┬─────────────┬─────────────┬─────────────┐
│ Feature                 │    Make     │    Task     │    Just     │
├─────────────────────────┼─────────────┼─────────────┼─────────────┤
│ Cross-platform          │ Partial     │ Yes         │ Yes         │
│ File dependencies       │ Yes         │ Yes         │ No          │
│ Task dependencies       │ Yes         │ Yes         │ Yes         │
│ Parallel execution      │ Yes (-j)    │ Yes         │ Yes         │
│ Variables               │ Yes         │ Yes (YAML)  │ Yes         │
│ Arguments               │ Limited     │ Yes         │ Yes         │
│ Watch mode              │ No          │ Yes         │ No          │
│ Include files           │ Yes         │ Yes         │ Yes         │
│ Shell selection         │ Limited     │ Yes         │ Yes         │
│ Checksum-based rebuild  │ Timestamp   │ Checksum    │ No          │
│ Interactive prompts     │ No          │ Yes         │ No          │
│ Tab required            │ Yes         │ No          │ No          │
│ Syntax highlighting     │ Good        │ Good (YAML) │ Limited     │
│ IDE support             │ Good        │ Good        │ Growing     │
│ Installation required   │ No          │ Yes         │ Yes         │
└─────────────────────────┴─────────────┴─────────────┴─────────────┘

Syntax Comparison

## Makefile - Build target
.PHONY: build
build:
 go build -o bin/app .
## Taskfile.yml - Build task
version: '3'

tasks:
  build:
    desc: Build the application
    cmds:
      - go build -o bin/app .
## justfile - Build recipe
build:
    go build -o bin/app .

Argument Handling Comparison

## Makefile - Arguments via environment
deploy:
 ./deploy.sh $(ENV)

## Usage: make deploy ENV=production
## Taskfile.yml - Arguments via variables
version: '3'

tasks:
  deploy:
    desc: Deploy to environment
    vars:
      ENV: '{{.CLI_ARGS | default "staging"}}'
    cmds:
      - ./deploy.sh {{.ENV}}

## Usage: task deploy -- production
## justfile - Native argument support
deploy env="staging":
    ./deploy.sh {{env}}

## Usage: just deploy production

Conditional Execution Comparison

## Makefile - OS detection
UNAME_S := $(shell uname -s)

install-deps:
ifeq ($(UNAME_S),Linux)
 apt-get install -y build-essential
endif
ifeq ($(UNAME_S),Darwin)
 brew install coreutils
endif
## Taskfile.yml - OS detection
version: '3'

tasks:
  install-deps:
    cmds:
      - task: install-deps-{{OS}}

  install-deps-linux:
    internal: true
    cmds:
      - apt-get install -y build-essential

  install-deps-darwin:
    internal: true
    cmds:
      - brew install coreutils
## justfile - OS detection
install-deps:
    #!/usr/bin/env bash
    case "{{os()}}" in
        linux) apt-get install -y build-essential ;;
        macos) brew install coreutils ;;
    esac

Cross-Platform Compatibility

Task Cross-Platform Configuration

## Taskfile.yml
version: '3'

vars:
  ## Cross-platform path separator
  SEP: '{{if eq OS "windows"}}\{{else}}/{{end}}'

  ## Executable extension
  EXE: '{{if eq OS "windows"}}.exe{{else}}{{end}}'

  ## Null device
  NULL: '{{if eq OS "windows"}}NUL{{else}}/dev/null{{end}}'

tasks:
  build:
    desc: Cross-platform build
    cmds:
      - go build -o bin{{.SEP}}app{{.EXE}} .

  clean:
    desc: Cross-platform clean
    cmds:
      - cmd: rm -rf bin
        platforms: [linux, darwin]
      - cmd: rmdir /s /q bin
        platforms: [windows]

  ## Platform-specific tasks
  install:
    desc: Install dependencies
    cmds:
      - task: '{{if eq OS "windows"}}install-windows{{else}}install-unix{{end}}'

  install-unix:
    internal: true
    cmds:
      - chmod +x scripts/*.sh
      - ./scripts/install.sh

  install-windows:
    internal: true
    cmds:
      - powershell -ExecutionPolicy Bypass -File scripts/install.ps1

Just Cross-Platform Configuration

## justfile

## Platform detection
os := os()
arch := arch()

## Platform-specific variables
exe_ext := if os == "windows" { ".exe" } else { "" }
path_sep := if os == "windows" { "\\" } else { "/" }

## Cross-platform build
build:
    go build -o bin{{path_sep}}app{{exe_ext}} .

## Platform-specific clean
clean:
    #!/usr/bin/env bash
    if [[ "{{os}}" == "windows" ]]; then
        rmdir /s /q bin 2>/dev/null || true
    else
        rm -rf bin
    fi

## Platform-specific scripts
[linux]
[macos]
install-unix:
    chmod +x scripts/*.sh
    ./scripts/install.sh

[windows]
install-windows:
    #!powershell
    powershell -ExecutionPolicy Bypass -File scripts/install.ps1

## Dispatch to platform-specific recipe
install: (if os == "windows" { "install-windows" } else { "install-unix" })

Portable Script Pattern

## Taskfile.yml
version: '3'

tasks:
  ## Use interpreter directive for portability
  setup:
    desc: Portable setup script
    cmds:
      - |
        #!/usr/bin/env bash
        set -euo pipefail

        # Works on macOS, Linux, and Windows (Git Bash, WSL)
        echo "Setting up environment..."

        # Create directories
        mkdir -p bin logs

        # Install dependencies
        go mod download

        echo "Setup complete!"

  ## Python for complex cross-platform logic
  analyze:
    desc: Cross-platform analysis
    cmds:
      - |
        #!/usr/bin/env python3
        import os
        import platform

        system = platform.system().lower()
        print(f"Running on {system}")

        # Cross-platform path handling
        bin_dir = os.path.join(".", "bin")
        os.makedirs(bin_dir, exist_ok=True)

Common Development Tasks

Standard Task Interface

## Taskfile.yml - Standard development tasks
version: '3'

tasks:
  ## Environment setup
  setup:
    desc: Set up development environment
    cmds:
      - task: deps
      - task: install

  deps:
    desc: Install system dependencies
    cmds:
      - echo "Installing system dependencies..."

  install:
    desc: Install project dependencies
    cmds:
      - echo "Installing project dependencies..."

  ## Code quality
  format:
    desc: Format code
    cmds:
      - echo "Formatting code..."

  lint:
    desc: Run linters
    cmds:
      - echo "Running linters..."

  typecheck:
    desc: Run type checker
    cmds:
      - echo "Type checking..."

  ## Testing
  test:
    desc: Run tests
    cmds:
      - echo "Running tests..."

  test-unit:
    desc: Run unit tests
    cmds:
      - echo "Running unit tests..."

  test-integration:
    desc: Run integration tests
    cmds:
      - echo "Running integration tests..."

  test-e2e:
    desc: Run end-to-end tests
    cmds:
      - echo "Running e2e tests..."

  test-cov:
    desc: Run tests with coverage
    cmds:
      - echo "Running tests with coverage..."

  ## Build
  build:
    desc: Build application
    cmds:
      - echo "Building application..."

  build-dev:
    desc: Build for development
    cmds:
      - echo "Building for development..."

  build-prod:
    desc: Build for production
    cmds:
      - echo "Building for production..."

  ## Run
  run:
    desc: Run application
    cmds:
      - echo "Running application..."

  dev:
    desc: Start development server
    cmds:
      - echo "Starting dev server..."

  ## Deployment
  deploy:
    desc: Deploy application
    cmds:
      - echo "Deploying application..."

  deploy-staging:
    desc: Deploy to staging
    cmds:
      - echo "Deploying to staging..."

  deploy-prod:
    desc: Deploy to production
    cmds:
      - echo "Deploying to production..."

  ## Docker
  docker-build:
    desc: Build Docker image
    cmds:
      - echo "Building Docker image..."

  docker-push:
    desc: Push Docker image
    cmds:
      - echo "Pushing Docker image..."

  docker-run:
    desc: Run Docker container
    cmds:
      - echo "Running Docker container..."

  ## Cleanup
  clean:
    desc: Clean build artifacts
    cmds:
      - echo "Cleaning..."

  reset:
    desc: Reset to clean state
    cmds:
      - task: clean
      - task: install

  ## CI/CD
  ci:
    desc: Run CI pipeline locally
    cmds:
      - task: lint
      - task: typecheck
      - task: test
      - task: build

  check:
    desc: Run all checks
    cmds:
      - task: format
      - task: lint
      - task: typecheck
      - task: test

Standard Just Interface

## justfile - Standard development tasks

## Environment setup
setup: deps install

deps:
    @echo "Installing system dependencies..."

install:
    @echo "Installing project dependencies..."

## Code quality
format:
    @echo "Formatting code..."

lint:
    @echo "Running linters..."

typecheck:
    @echo "Type checking..."

## Testing
test:
    @echo "Running tests..."

test-unit:
    @echo "Running unit tests..."

test-integration:
    @echo "Running integration tests..."

test-e2e:
    @echo "Running e2e tests..."

test-cov:
    @echo "Running tests with coverage..."

## Build
build:
    @echo "Building application..."

build-dev:
    @echo "Building for development..."

build-prod:
    @echo "Building for production..."

## Run
run:
    @echo "Running application..."

dev:
    @echo "Starting dev server..."

## Deployment
deploy env="staging":
    @echo "Deploying to {{env}}..."

## Docker
docker-build:
    @echo "Building Docker image..."

docker-push:
    @echo "Pushing Docker image..."

docker-run:
    @echo "Running Docker container..."

## Cleanup
clean:
    @echo "Cleaning..."

reset: clean install

## CI/CD
ci: lint typecheck test build

check: format lint typecheck test

Testing Task Runners

Testing Task Configuration

## Taskfile.yml
version: '3'

tasks:
  ## Test task execution
  test-tasks:
    desc: Test all tasks work
    cmds:
      - task --dry build
      - task --dry test
      - task --dry lint

  ## Validate Taskfile syntax
  validate:
    desc: Validate Taskfile
    cmds:
      - task --list > /dev/null
#!/bin/bash
## tests/test_taskfile.sh

set -euo pipefail

echo "Testing Taskfile configuration..."

## Test task list works
if ! task --list > /dev/null 2>&1; then
  echo "FAIL: task --list failed"
  exit 1
fi
echo "PASS: task --list works"

## Test build task exists
if ! task --list | grep -q "build"; then
  echo "FAIL: build task not found"
  exit 1
fi
echo "PASS: build task exists"

## Test dry run
if ! task --dry build > /dev/null 2>&1; then
  echo "FAIL: build task dry run failed"
  exit 1
fi
echo "PASS: build task dry run works"

echo "All Taskfile tests passed!"

Testing Just Configuration

## justfile

## Test all recipes work
test-recipes:
    @just --dry-run build
    @just --dry-run test
    @just --dry-run lint

## Validate justfile syntax
validate:
    @just --list > /dev/null && echo "Justfile is valid"
#!/bin/bash
## tests/test_justfile.sh

set -euo pipefail

echo "Testing justfile configuration..."

## Test recipe list works
if ! just --list > /dev/null 2>&1; then
  echo "FAIL: just --list failed"
  exit 1
fi
echo "PASS: just --list works"

## Test build recipe exists
if ! just --list | grep -q "build"; then
  echo "FAIL: build recipe not found"
  exit 1
fi
echo "PASS: build recipe exists"

## Test dry run
if ! just --dry-run build > /dev/null 2>&1; then
  echo "FAIL: build recipe dry run failed"
  exit 1
fi
echo "PASS: build recipe dry run works"

echo "All justfile tests passed!"

CI Integration

## .github/workflows/task-runner-test.yml
name: Test Task Runners

on: [push, pull_request]

jobs:
  test-taskfile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Task
        uses: arduino/setup-task@v2
        with:
          version: 3.x

      - name: Validate Taskfile
        run: task --list

      - name: Test dry run
        run: task --dry build

      - name: Run tests
        run: task test

  test-justfile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Just
        uses: extractions/setup-just@v2

      - name: Validate justfile
        run: just --list

      - name: Test dry run
        run: just --dry-run build

      - name: Run tests
        run: just test

Security Best Practices

Secure Variable Handling

## Taskfile.yml
version: '3'

## Load environment from .env (gitignored)
dotenv: ['.env']

tasks:
  deploy:
    desc: Secure deployment
    preconditions:
      - sh: test -n "$DEPLOY_TOKEN"
        msg: "DEPLOY_TOKEN required"
      - sh: test -n "$AWS_ACCESS_KEY_ID"
        msg: "AWS credentials required"
    cmds:
      ## Don't echo sensitive commands
      - cmd: echo "Deploying..."
        silent: true
      - cmd: ./deploy.sh
        silent: true

  ## Never log secrets
  secure-cmd:
    desc: Run without logging
    silent: true
    cmds:
      - curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com
## justfile

## Set for quiet mode
set quiet

## Secure deployment
deploy:
    #!/usr/bin/env bash
    set -euo pipefail

    # Validate required secrets
    : "${DEPLOY_TOKEN:?DEPLOY_TOKEN is required}"
    : "${AWS_ACCESS_KEY_ID:?AWS credentials required}"

    # Deploy without echoing commands
    set +x
    ./deploy.sh

Input Validation

## Taskfile.yml
version: '3'

tasks:
  deploy:
    desc: Deploy to environment
    vars:
      ENV: '{{.CLI_ARGS}}'
      VALID_ENVS: "dev staging production"
    preconditions:
      - sh: echo "{{.VALID_ENVS}}" | grep -wq "{{.ENV}}"
        msg: "Invalid environment '{{.ENV}}'. Must be one of: {{.VALID_ENVS}}"
    cmds:
      - ./deploy.sh {{.ENV}}
## justfile

## Validate environment argument
deploy env:
    #!/usr/bin/env bash
    set -euo pipefail

    valid_envs="dev staging production"
    if ! echo "$valid_envs" | grep -wq "{{env}}"; then
        echo "Invalid environment '{{env}}'. Must be one of: $valid_envs"
        exit 1
    fi

    ./deploy.sh {{env}}

Tool Configuration

EditorConfig

## .editorconfig

[Taskfile.yml]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[justfile]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

VS Code Settings

{
  "files.associations": {
    "Taskfile.yml": "yaml",
    "Taskfile.yaml": "yaml",
    "justfile": "just",
    "Justfile": "just"
  },
  "yaml.schemas": {
    "https://taskfile.dev/schema.json": ["Taskfile.yml", "Taskfile.yaml"]
  },
  "[yaml]": {
    "editor.tabSize": 2,
    "editor.insertSpaces": true
  }
}

Pre-commit Hooks

## .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml

  - repo: local
    hooks:
      - id: validate-taskfile
        name: Validate Taskfile
        entry: task --list
        language: system
        files: Taskfile\.ya?ml$
        pass_filenames: false

      - id: validate-justfile
        name: Validate justfile
        entry: just --list
        language: system
        files: justfile$
        pass_filenames: false

Anti-Patterns

Task Anti-Patterns

## Bad - No descriptions
version: '3'
tasks:
  build:
    cmds:
      - go build .

## Good - Always add descriptions
version: '3'
tasks:
  build:
    desc: Build the application binary
    cmds:
      - go build -o bin/app .
## Bad - Hardcoded paths
version: '3'
tasks:
  deploy:
    cmds:
      - scp bin/app user@192.168.1.100:/opt/app/

## Good - Use variables
version: '3'
vars:
  DEPLOY_HOST: '{{.DEPLOY_HOST | default "localhost"}}'
  DEPLOY_PATH: /opt/app
tasks:
  deploy:
    desc: Deploy to remote server
    cmds:
      - scp bin/app {{.DEPLOY_HOST}}:{{.DEPLOY_PATH}}/
## Bad - Missing preconditions
version: '3'
tasks:
  publish:
    cmds:
      - npm publish

## Good - Validate before running
version: '3'
tasks:
  publish:
    desc: Publish to npm
    preconditions:
      - sh: test -f package.json
        msg: "package.json not found"
      - sh: test -n "$NPM_TOKEN"
        msg: "NPM_TOKEN required"
    cmds:
      - npm publish

Just Anti-Patterns

## Bad - No default recipe
build:
    go build .

## Good - Always have a default
default:
    @just --list

build:
    go build -o bin/app .
## Bad - No argument validation
deploy env:
    ./deploy.sh {{env}}

## Good - Validate arguments
deploy env:
    #!/usr/bin/env bash
    set -euo pipefail
    case "{{env}}" in
        dev|staging|production) ;;
        *) echo "Invalid env: {{env}}" && exit 1 ;;
    esac
    ./deploy.sh {{env}}
## Bad - Secrets in justfile
deploy:
    curl -H "Authorization: Bearer sk_live_abc123" https://api.example.com

## Good - Use environment variables
deploy:
    #!/usr/bin/env bash
    : "${API_TOKEN:?API_TOKEN required}"
    curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com

References

Task Resources

Just Resources


Status: Active