Skip to main content

Tasks

This document defines the task system for Morphir, modeled after mise tasks.

Overview

Morphir provides built-in tasks for common operations and allows user-defined tasks for custom workflows. Tasks are run via morphir run <task>.

Built-in Tasks

Morphir and its extensions provide intrinsic tasks that work out of the box:

TaskDescriptionEquivalent Command
buildCompile project to IRmorphir build
testRun testsmorphir test
checkLint and validatemorphir check
codegenGenerate code for targetsmorphir codegen
cleanRemove build artifactsmorphir clean
packCreate distributable packagemorphir pack
publishPublish to registrymorphir publish
# These all work without any configuration
morphir run build
morphir run test
morphir run codegen

User-Defined Tasks

Users can define custom tasks or override built-in tasks:

# morphir.toml
[tasks]
# Custom task
integration = "./scripts/integration-tests.sh"

# Override built-in task
test = "morphir test && ./scripts/integration-tests.sh"
morphir run integration    # Custom task
morphir run test # Runs overridden test task

Extension-Provided Tasks

Extensions (via WASM components) can register additional intrinsic tasks:

# Tasks provided by a TypeScript codegen extension
morphir run codegen:typescript

# Tasks provided by a Scala codegen extension
morphir run codegen:scala

See WASM Components for how extensions register tasks.

Task Definition

Inline Tasks

The simplest form is an inline command string:

[tasks]
lint = "morphir check --strict"
clean = "rm -rf .morphir dist/"

Detailed Tasks

For more control, use the detailed table syntax:

[tasks.build]
description = "Build the project and generate TypeScript"
run = "morphir build && morphir codegen typescript"

[tasks.test]
description = "Run all tests"
run = [
"morphir build",
"morphir test",
"./scripts/integration-tests.sh"
]
depends = ["lint"]

Task Options

OptionTypeDescription
runstring or string[]Command(s) to execute
descriptionstringDescription shown in morphir run --list
dependsstring[]Tasks to run before this task
envtableEnvironment variables for the task
dirstringWorking directory (default: project root)
sourcesstring[]File patterns that trigger rebuild
outputsstring[]Expected output files
hideboolHide from task listings

Task Dependencies

Tasks can depend on other tasks:

[tasks.lint]
run = "morphir check"

[tasks.test]
run = "morphir test"
depends = ["lint"]

[tasks.ci]
description = "Run full CI pipeline"
depends = ["lint", "test", "build"]
run = "echo 'CI complete'"

Dependencies run in order. If multiple dependencies have no interdependencies, they may run in parallel.

Environment Variables

Set environment variables for tasks:

[tasks.test]
run = "morphir test"
env = { MORPHIR_LOG_LEVEL = "debug", CI = "true" }

[tasks.deploy]
run = "./scripts/deploy.sh"
env = { ENVIRONMENT = "production" }

File-Based Tasks

For complex tasks, use external scripts in a tasks/ or .morphir/tasks/ directory:

my-project/
├── morphir.toml
├── tasks/
│ ├── build.sh
│ ├── deploy.sh
│ └── test.py

File-based tasks are automatically discovered and can include metadata:

#!/usr/bin/env bash
#MISE description="Build and package for release"
#MISE depends=["lint", "test"]

set -euo pipefail
morphir build --release
morphir pack

Reference file tasks in configuration:

[tasks.build]
file = "tasks/build.sh"

[tasks.deploy]
file = "tasks/deploy.sh"
env = { ENVIRONMENT = "production" }

Pre/Post Hooks

Conventional pre: and post: task prefixes allow extending built-in Morphir commands:

[tasks."pre:build"]
description = "Run before morphir build"
run = "echo 'Starting build...'"

[tasks."post:build"]
description = "Run after morphir build"
run = [
"cp .morphir-dist/morphir-ir.json dist/",
"echo 'Build complete'"
]

[tasks."pre:test"]
run = "morphir build"

[tasks."post:codegen"]
run = "prettier --write generated/"

Hook Execution Order

When running morphir build:

  1. pre:build task (if defined)
  2. Built-in morphir build command
  3. post:build task (if defined)

Available Hooks

HookTriggered By
pre:build / post:buildmorphir build
pre:test / post:testmorphir test
pre:codegen / post:codegenmorphir codegen
pre:pack / post:packmorphir pack
pre:publish / post:publishmorphir publish
pre:clean / post:cleanmorphir clean

Disabling Hooks

# Skip hooks for a single command
morphir build --no-hooks

# Skip specific hook
morphir build --skip-hook=pre:build

Workspace Tasks

In workspaces, tasks can be defined at workspace or project level:

# workspace/morphir.toml
[workspace]
members = ["packages/*"]

[tasks]
# Workspace-level tasks available from anywhere
build-all = "morphir workspace build"
test-all = "morphir workspace test"
# workspace/packages/core/morphir.toml
[project]
name = "my-org/core"

[tasks]
# Project-specific tasks
benchmark = "./scripts/benchmark.sh"

Running Workspace Tasks

# From workspace root
morphir run build-all

# From project directory (runs project task)
cd packages/core
morphir run benchmark

# Run workspace task from project directory
morphir run --workspace build-all

Built-in Variables

Tasks have access to these environment variables:

VariableDescription
MORPHIR_PROJECT_ROOTProject root directory
MORPHIR_WORKSPACE_ROOTWorkspace root (if in workspace)
MORPHIR_PROJECT_NAMECurrent project name
MORPHIR_TASK_NAMEName of the current task
MORPHIR_CONFIG_DIRDirectory containing morphir.toml
[tasks.info]
run = "echo Building $MORPHIR_PROJECT_NAME from $MORPHIR_PROJECT_ROOT"

Incremental Tasks

Use sources and outputs for incremental execution:

[tasks.codegen]
description = "Generate TypeScript (incremental)"
run = "morphir codegen typescript --output generated/"
sources = ["src/**/*.morphir", ".morphir-dist/**/*.json"]
outputs = ["generated/**/*.ts"]

The task only runs if sources are newer than outputs.

CLI Reference

# List available tasks
morphir run --list

# Run a task
morphir run <task>

# Run with arguments (passed to task)
morphir run test -- --verbose

# Run multiple tasks
morphir run lint test build

# Dry run (show what would execute)
morphir run --dry-run build

# Force run (ignore incremental check)
morphir run --force codegen

Examples

CI Pipeline with Hooks

Since build, test, and check are built-in tasks, you only need to configure custom behavior:

# Use pre/post hooks to extend built-in tasks
[tasks."pre:test"]
description = "Ensure build is fresh before testing"
run = "morphir build"

[tasks."post:test"]
description = "Generate coverage report"
run = "./scripts/coverage-report.sh"

# Custom CI task that chains built-in tasks
[tasks.ci]
description = "Full CI pipeline"
depends = ["check", "test", "build", "pack"]
run = "echo 'CI passed'"
# Built-in tasks work immediately
morphir run build # Runs pre:build -> build -> post:build
morphir run test # Runs pre:test -> test -> post:test
morphir run ci # Runs the full pipeline

Development Workflow

[tasks.dev]
description = "Start development with watch mode"
run = "morphir workspace watch"

[tasks."post:build"]
description = "Auto-format generated code"
run = "prettier --write .morphir-dist/"

[tasks."post:codegen"]
description = "Format generated TypeScript"
run = "prettier --write generated/"

[tasks.release]
description = "Create a release"
depends = ["test", "build", "pack"]
run = "morphir publish --backend github"
env = { MORPHIR_RELEASE = "true" }

Monorepo Tasks

# workspace/morphir.toml
[tasks.bootstrap]
description = "Initialize workspace after clone"
run = [
"morphir deps resolve",
"morphir workspace build"
]

[tasks.release-all]
description = "Release all packages"
depends = ["test"] # Built-in test runs for all projects
run = "./scripts/release-all.sh"

Custom Integration Tests

# Add integration tests after the built-in test task
[tasks."post:test"]
run = "./scripts/integration-tests.sh"

# Or define a separate task
[tasks.integration]
description = "Run integration tests"
depends = ["build"]
run = "./scripts/integration-tests.sh"

Migration from Toolchain Config

If you previously used [toolchain] configuration, migrate to tasks:

# Before (deprecated)
[toolchain.morphir-elm]
enabled = true
[toolchain.morphir-elm.tasks.make]
exec = "morphir-elm"
args = ["make"]

# After (tasks)
[tasks.elm-make]
description = "Build with morphir-elm"
run = "morphir-elm make"

Design Notes

  1. Simplicity: Tasks are just shell commands, no special DSL
  2. Composability: Dependencies allow building complex workflows from simple tasks
  3. Convention: Pre/post hooks follow predictable naming
  4. Compatibility: File-based tasks work with any scripting language
  5. Incremental: Source/output tracking avoids unnecessary work

For extension points beyond tasks (custom commands, protocol extensions), see WASM Components for the WASM Component Model approach.