Use Task Runners for Common Coding Tasks

speckx1 pts0 comments

Use Task Runners for Common Coding Tasks - Ham Vocke(Originally published in 2019. I pulled this one from the archives and gave it<br>a fresh makeover after a reader reached out to tell me they misses this article.)

In my day to day as a software developer I often switch between different code<br>repositories. Each repository can be written in a different tech stack but there are<br>common tasks I need to run in all of these repositories, regardless of the stack:<br>I need to install dependencies, build the source code, lint my code, format it, run tests,<br>run database migrations, deploy to a staging environment, create a new version, start dependent<br>services, you get the idea.

Depending on the exact tooling used in each repo, I’d have to remember<br>a few clunky incantations to invoke from my command line. Was it rust fmt to format?<br>mix deps.update to update dependencies? Was it ./gradlew build or did we switch<br>back to mvn again? Was it yarn or npm or pnpm after all these years? Do I<br>need to pass any arguments to npm run prettier? And what was that three-step<br>command to run all migrations from scratch again?

I don’t want to remember any of this.

I want to be able to say “build the code”, “lint it”, “format it”, “run migrations”,<br>“install dependencies”.

To make my life just a little bit easier, I often create convenience tooling that allows me to<br>run common tasks the same way no matter the repository I’m in. This allows me to<br>apply muscle memory to do the things I do a dozen times each day.

There are a few ways we can pull this off, from good old bash scripts and<br>make to more modern tools like mise and just. Some people have started<br>calling these tools “task runners” so I guess that’s the name I’m going to stick<br>with here. Let’s take a quick look at these options together.

A Simple Bash Script

We can write a simple shell script (or ask our LLM of choice for help) to act as a small wrapper for commonly run<br>commands we need for messing with our code.

Here’s an example you can use. In this example I’m assuming you’re using<br>node/npm/javascript. If you aren’t I’m sure you can fill in the blanks yourself.

#!/usr/bin/env bash<br>set -e

function usage {<br># print usage information

function install {<br># install dependencies<br>npm run ci

function build {<br># trigger build process<br>npm run build

function test {<br># run test suite

# you can even run multiple steps in one command, cool huh?<br>npm run test:unit<br>npx run playwright

function format {<br># it works well for hiding args we need to pass to underlying commands<br>npm run prettier --write

if [[ $# -lt 1 ]]; then<br>usage<br>exit 1<br>fi

TARGET=$1<br>case $TARGET in<br>"help" )<br>usage<br>;;<br>"install" )<br>install<br>;;<br>"build" )<br>build<br>;;<br>"test" )<br>test<br>;;<br>"format" )<br>format<br>;;<br>*)<br>fail "Unknown command '${TARGET}'"<br>usage<br>exit 1<br>;;<br>esac<br>Save that script under a file name you like at the root directory of your repository.<br>Pick a short and crisp name, you’ll type it many times (I tend to use run).<br>Oh, and don’t forget to make it executable with chmod +x run.

With this script in place, you can simply use run build, run lint, run test and add any other command you might need for your project.

The nice thing about using shell scripts is that you’ve got a lot of<br>flexibility. You can perform more complicated tasks and easily add more<br>sophisticated validations to prevent you from common gotchas, for example<br>if you want to avoid that someone nukes the PROD database by accident.

If your bash script ever becomes unwieldy, you can start extracting the individual<br>functions outlined in the script above into dedicated shell scripts. A common<br>convention is to store them in a bin/ directory in your repository, but that’s really<br>up to you.

Bash is a natural choice for simple automation tasks like this. It’s available on most<br>developer machines (and your CI/CD server) and is pretty flexible. If only it<br>weren’t for its questionable syntax…

Make

make is another classic tool at our<br>disposal. Hailing straight from the 1970s it stood the test of time as a build automation<br>tool and is installed pretty much on every developer’s machine already.

We can contort make just a little bit to turn it into a simple task runner and<br>ignore most of the more powerful features it offers.

make looks for a file called Makefile in your current directory to figure out what<br>it’s supposed to do. A Makefile is a plain text file that defines the different rules<br>you can execute.

A rule follows this pattern:

target: dependencies<br>system command to execute<br>The target defines how you call the rule from your command line. make test would look<br>for a rule called “test” and execute the commands you defined.

A simple Makefile comparable to our bash script above could look like this:

.PHONY: install build test format

install:<br>npm run ci

build:<br>npm run build

test:<br>npm run test:unit<br>npx run playwright

format:<br>npm run prettier --write

Note<br>Keep in mind that make is picky about indentation. You need to indent your commands with a tab , not spaces...

build test make install format from

Related Articles