A Rails 7 compatible bin/dev for heroku local

Rails 7 introduced a new bin/dev wrapper to launch and manage your Rails server, CSS watcher, and JS bundler into a single process, managed by foreman. This is quite handy for running everything with a single command. But what if you’re deploying to Heroku and using the Heroku CLI’s heroku local to run things locally? Or if you’re a fan of one of the other tools that manage processes based on your Procfile?

I’ve got you covered!

bin/dev for heroku local

The biggest, and most obvious change is swapping out the executable. Mostly this is to do with heroku local using the :<subcommand> style CLI interface. Let’s compare foreman, heroku local, and bin/dev doing similar things to see how their CLIs differ.

# start the web and css processes
$ foreman start web,css
$ heroku local:start web,css
$ bin/dev web,css

It turns out bin/dev explicitly calls the foreman start command. So a quick heroku local version of the same would be:

#!/usr/bin/env sh

if ! command -v heroku &> /dev/null; then
  printf "You must install the Heroku CLI on your system before setup can continue\n"
  printf "☁️  https://devcenter.heroku.com/articles/heroku-cli\n"
  exit 1
fi

exec heroku local:start "$@"

And this works… fine-ish. I mean, it totally works. But it’s only using a small part of the underlying heroku local CLI. And the same goes for the foreman version. If we want to leverage the run or version commands, we’re out of luck.

A more robust bin/dev

If we want to leverage the other CLI commands, we’re going to need to do some bash-ing to check and manipulate the passed arguments. Let’s start with the CLI we want to have.

$ bin/dev web,css         # (implicit) start the web and css processes
$ bin/dev start web,css   # (explicit) start the web and css processes
$ bin/dev run bin/rails c # start a rails console session
$ bin/dev version         # check the foreman version

For this we need to default to the start command, but allow overriding it. And we’ll need to pass other arguments on to heroku local.

#!/usr/bin/env bash

if ! command -v heroku &> /dev/null; then
  printf "You must install the Heroku CLI on your system before setup can continue\n"
  printf "☁️  https://devcenter.heroku.com/articles/heroku-cli\n"
  exit 1
fi

cmd='start'
heroku_cmds=('run' 'start' 'version')

# If we were given any positional args we might need to handle sub-commands
if [[ $# -gt 0 ]];then
  first_arg=${1}

  # Shift heroku local commands off the array so we can pass remaining arguments along
  if [[ " ${heroku_cmds[*]} " == *" ${first_arg} "* ]]; then
    cmd="${first_arg}"
    shift
  fi
fi

exec heroku "local:${cmd}" "$@"

🎉 And just like that, it works! We’ve got a bin/dev that works with heroku local and supports its other commands. You can even bin/dev --help to see heroku local’s CLI help text.

I’ll leave it as an exercise to the reader to adapt this for node-foreman or other tools.