For the past 5 years or so, I’ve added a group of scripts to every project I’ve worked on. These scripts are relatively simple and automate some very common tasks.

Why Automate?

Automation encodes the team’s decisions in committed code, reducing questions (such as “do we like to do git pull --rebase or just git pull?”) and reducing problems caused when people forget to perform a step (such as running tests before pushing code).

How To Automate

Because Bash is ubiquitous, I typically write the scripts in Bash. Sometimes I write them in a more powerful language if they are complex. I personally put them in the bin/dev directory (I’ll often have a bin/prod directory for performing production actions, like bin/prod/deploy or bin/prod/logs).


Update

The update script is run when the developer wants to get the latest code onto their computer. It typically:

Example:

#!/usr/bin/env bash

set -e

CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

step() {
  description=$1
  command=$2

  echo -e "\n${CYAN}${description}: ${YELLOW}${command}${NC}"
  eval "${command}"
}

step "Pulling" "git pull --rebase"
step "Updating deps" "MIX_QUIET=true mix deps.get"
step "Migrating" "mix ecto.migrate ecto.dump"
bin/dev/doctor

Test

The test script runs all the tests. A script is useful for this so that everyone runs tests the same way and that changes to the way the team runs tests are automatically shared by everyone.

Example:

#!/usr/bin/env bash

set -e

CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

step() {
  description=$1
  command=$2

  echo -e "\n${CYAN}${description}: ${YELLOW}${command}${NC}"
  eval "${command}"
}

step "Running JS tests" "(cd assets && yarn run mocha)"
step "Running Elixir tests" "mix test --color"

Shipit

The shipit script is run when the developer wants to push code to the git remote. It typically:

Example:

#!/usr/bin/env bash

set -e

CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

step() {
  description=$1
  command=$2

  echo -e "\n${CYAN}${description}: ${YELLOW}${command}${NC}"
  eval "${command}"
}

bin/dev/update
step "Recompiling" "mix compile --force --warnings-as-errors || (mix clean && false)"
bin/dev/test
step "Pushing to git" "git push"

Start

The start script starts the server or app. It’s usually not a complicated script but it makes it really easy to remember how to run the project.

Example:

#!/usr/bin/env bash
iex -S mix phx.server

Other Scripts

Besides the above scripts which I have in nearly every project, I also have other scripts that show up in some of my projects:

bin/
  dev/
    doctor    # ensures the development environment is set up correctly
    format    # formats code for consistent style
    outdated  # checks which dependencies are outdated
    psql      # opens psql with the right database
    shipit    # updates, runs tests, and pushes
    start     # starts the server or app
    test      # runs tests
    update    # updates from git remote, updates dependencies, runs migrations
  prod/
    deploy    # deploys to production, or prints instructions for how to do it
    log       # shows production logs
    restart   # restarts servers
    scale     # adds or removes servers
    shutdown  # stops servers
    ssh       # logs into servers

Related Reading