🧼 Reduce your boilerplate
As mentioned in Part 1, we’ve moved away from Makefiles and bash scripts in favor of mise. In this final post of the series, I’ll walk you through how we’ve taken things further by sharing tasks across projects and environments—speeding up onboarding and keeping things consistent between dev machines and CI/CD.
⚙️ How tasks work in mise
mise offers multiple ways to define and run tasks. After experimenting, we’ve decided to use a mix of both TOML-based and file-based tasks to get the best of both worlds.
TOML-based tasks
The most straightforward way is to define tasks directly in your mise.toml using the TOML format:
[tasks.serve]description = "Serve the site"run = "hugo serve"That’s easy and declarative—but if you need more logic or control, it gets limiting fast.
File-based tasks
For more flexibility, we use file tasks. These are regular scripts that live in files. You can use any scripting language, but we’ve chosen to stick with bash. Why? Because a lot of our previous tooling was already in bash and Makefiles, so migrating was mostly a copy-paste job.
Example:
#!/usr/bin/env bash#MISE description="Serve the site"
hugo serve💼 In the real world
Now that we’ve got the basics down, let’s look at how we’ve applied this setup in our actual day-to-day work.
📦 Centralized task repository
To encourage reusability, we created a centralized repository with a clear structure for all shared tasks:
.├── shared-tasks│ ├── download-tasks│ ├── aws│ │ ├── check-prerequisites│ │ └── login│ ├── azure│ ├── docker│ ├── poetry│ ├── precommit│ ├── python│ ├── rust│ ├── terraform│ └── terragrunt└── tasks └── lintWe grouped related tasks (e.g. aws, azure, etc.) into folders and added a standard check-prerequisites task in each group to ensure required tools are installed.
Example:
#!/usr/bin/env bash#MISE description="✅ Check prerequisites for aws"
set -e
if ! command -v aws &> /dev/null; then echo "❌ AWS CLI is not installed" exit 1fiThen, other tasks in that group can declare a dependency on this check:
#!/usr/bin/env bash#MISE description="🔑 Login to AWS"#MISE depends=["aws:check-prerequisites"]
set -e
if [ -z "$AWS_PROFILE" ]; then echo "AWS_PROFILE env variable is not set" exit 1fi
echo "🔑 Logging in to AWS..."aws sso login --profile $AWS_PROFILE📥 The download-tasks script
The backbone of our shared task system is a special script: download-tasks. Think of it as our little automation butler—it fetches selected task groups from the shared repository and installs them in your local project directory.
Here’s why it’s a game-changer:
- ✅ Versioning: Tasks are pulled from a specific tag or branch, ensuring consistency across teams and CI environments.
- 📦 Selective installation: You only download what you need. This keeps your project tidy and avoids unnecessary bloat.
- 🔄 Reproducibility: By pinning the task version, your tasks won’t suddenly change under your feet.
- 🧹 Clean environment: The script deletes and re-creates the target folder every time, so you’re always working with a fresh copy.
- 🧩 Plug-and-play: Once tasks are in place,
miserecognizes them immediately. No need for extra config.
Let’s look at the script:
#!/usr/bin/env bash#MISE description="📥 Download and install tasks"#USAGE arg "<version>" help="Version of the tasks to download and install"#USAGE arg "<tasks>" help="List of tasks to download and install (comma separated)"#USAGE flag "-d --directory <directory>" help="Directory where tasks will be installed (default: shared-tasks)"
REPO_URL="git@github.com:owner/mise-shared-tasks.git"TMP_DIR=$(mktemp -d)TASK_DIR="${usage_directory:-shared-tasks}"TASKS=${usage_tasks}VERSION=${usage_version}
# Infoecho "========================================================================================================================"echo "Downloading tasks from $REPO_URL"echo "Repo version: $VERSION"echo "Installing tasks in $TASK_DIR"echo "Tasks to install: $TASKS"echo "========================================================================================================================"
# Clean existing tasksif [ -d "$TASK_DIR" ]; then echo "🧹 Cleaning tasks directory" rm -rf "$TASK_DIR"fi
# Create tasks directoryecho "📁 Creating tasks directory"mkdir -p "$TASK_DIR"
# Clone repoecho "📥 Cloning repository"git clone "$REPO_URL" "$TMP_DIR" --branch "$VERSION" --depth 1 > /dev/null 2>&1
# Copy selected tasksIFS=',' read -r -a TASK_ARRAY <<< "$TASKS"
for TASK in "${TASK_ARRAY[@]}"; do if [ -d "$TMP_DIR/shared-tasks/$TASK" ]; then cp -r "$TMP_DIR/shared-tasks/$TASK" "$TASK_DIR/" echo "🖨️ Copied $TASK to $TASK_DIR" else echo "🚨 Directory $TASK does not exist in the repository." fidone
# Add .gitignoreecho "🤷 Creating .gitignore file"cat <<EOF > "$TASK_DIR/.gitignore"*!.gitignoreEOF
# Cleanupecho "🧹 Cleaning temporary directory"rm -rf "$TMP_DIR"This simple but powerful tool ensures that all your teams, tools, and pipelines speak the same language—without requiring everyone to reinvent the wheel.
📁 Project integration
Using the shared tasks in your own project is easy. Just update your mise.toml like this:
[task_config]includes = ["shared-tasks", "tasks"]
[tasks.download-shared-tasks]description = "Download shared tasks"hide = truefile = "git::ssh://git@github.com/owner/mise-tasks.git//shared-tasks/download-tasks?ref=<branch/tag>"
[tasks.apply-shared-tasks]description = "📩 Download shared tasks in the current workspace"wait_for = "download-shared-tasks"run = "mise download-shared-tasks <branch/tag> aws,terraform,terragrunt"Then simply run:
mise apply-shared-tasksAnd voilà—you’ve got consistent, versioned, ready-to-use tasks in your project.
