Task organisation for dev projects,
based on a pure shell script.
Download
(Latest version of task runner)
chmod +x run
mv run /usr/local/bin/run
Needs Bash 3.2+, tested on Linux and macOS.
Place a task file called run.sh
in your project root and make it executable.
Let’s say your project is a
then your task file could look like this:
#!/bin/bash
# Compile binary
run::compile
() {
go
build
\
-o out/
\
src/main.go
}
# Install dependencies
run::install
() {
go
get -t ./...
go
mod tidy
}
# Create bundle
run::bundle
() {
./node_modules/.bin/esbuild
\
src/index.ts
\
--bundle
\
--outfile=dist/app.js
}
# Install dependencies
run::install
() {
npm
install --strict-peer-deps
}
# Start web server
run::server
() {
PORT=8000
\
CONFIG=/app/config.yml
\
python
src/server.py
}
# Install dependencies
run::install
() {
pip
install
\
-r requirements.txt
}
A run.sh
task file is a plain regular shell script.
It behaves exactly as you would expect from any other shell script.
There is no special magic to it, except that it adheres to the following convention:
run::
prefix.
These notation rules allow the run
task runner to recognise and process the tasks.
For example, if you want to execute the install
task:
$
run install
Under the hood, the task runner evaluates the task file in a bash subprocess and then invokes the task with the respective name – in this case, the bash function run::install
.
Any additional CLI arguments will be passed on to the task as is.
List all available tasks along with their title:
$
run --list
server Start web server
install Install dependencies
If a task has additional lines of commentary below the title, you can print that by running e.g.
run --info install
.
Without the task runner available (e.g. when in a CI or production environment), you can still
access your tasks by sourcing the
run.sh
task file and then directly calling the task commands by their original names.
$
. run.sh
$
run::install
(The task runner effectively does the same thing.)
run.sh
file structure is simple, discoverable, and self-documenting.
It’s also agnostic of the project’s main language.
Here are a few handy shell scripting snippets.
PORT=${PORT:-8080}
PORT=$([[ "$IS_PROD" ]] && echo 443 || echo 8080)
run::hello() {
echo "$1" # First arg
echo "$2" # Second arg
}
run::print() {
echo "$@"
}
run::hello() {
run::other-task
}
[[ -f file.txt ]] # … file exists
[[ -d folder/ ]] # … directory exists
[[ -z "$VAR" ]] # … var is empty/unset (“zero”)
[[ -n "$VAR" ]] # … var is not empty
CONTENTS=$(<file.txt)
source otherfile.sh
THIS_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
source .env
# You can also do that conditionally, e.g.:
[[ -f .env ]] && source .env
[[ -f .env ]] && source .env || source .env.dev
set -o errexit