I recently watched Matt Stauffer’s talk “Patterns That Pay Off” from Laracon 2018. It’s packed with great advice that I’ve been bringing into my own projects.
One piece of advice I’d like to highlight is the use of project-based bin scripts.
Whats the problem?
Have you ever had a project that hasn’t seen active development in years but now needs a small update.
- “Where do I get Bower from these days?”
- “What is a PEAR package?”
- “How do I deploy to Rackspace?”
That quick CSS fix has become a much bigger headache.
Or, a typical PHP Laravel project a local setup might involve these steps:
- Switch to PHP 7.4
- Install PHP dependencies via Composer
- Switch to Node v12.7
- Install frontend dependencies via NPM
- Start local PHP web server
- Start webpack build system
- Start a MySQL service
- Copy
.env
variables - Run database migrations and seeders
Thats a lot of steps to remember or fit into your readme, and thats assuming the developer reads the readme at all!
Whats the solution?
What if all your projects had these steps encapsulated in a single bin/install.sh
script.
This script could perform the installation steps and check for project requirements. It could also handle the nuances between macOS, Linux or Windows dev environments.
It doesn’t matter if you are working on a PHP, Node or static web app – you cd
into the project and run ./bin/install.sh
.
Once you’re ready to commit, run ./bin/precommit.sh
to run your tests, static analysis, and formatting.
Finally, run ./bin/deploy.sh
to build production assets, update release notes and deploy. How you deploy that particular project doesn’t matter. Docker? Digital Ocean? Heroku? Who cares!
What are the alternatives? Docker? Composer?
You might be thinking “this is the sort of thing Docker and CI solves!” or “can I not just use composer/npm scripts?”.
Docker can standardise your build steps – but not every project needs Docker. I have several projects where Docker would be overkill. For example, sites built using static site generators.
A Docker-based app can still make use of bin scripts. If only to wrap docker-compose up
in the familiar bin/run.sh
script.
Similarly, a PHP project may not even use Composer – such as a typical WordPress website.
By embracing bin scripts, it doesn’t matter what an individual project’s tech stack is. The interface to install, run and deploy is the same across all your projects.
Most importantly, shell scripts will run out of the box on macOS, Linux and Windows in almost all cases.
Show me the code!
I have a bin
directory in my project root which contains my bin scripts. It looks something like this:
$ tree bin bin ├── install ├── run └── precommit
Each script is self-contained and includes everything it needs to run. Because of this, they’ll can work in many years from the first git clone
.
For example, one of my PHP Laravel apps has the following bin/install.sh
:
#!/usr/bin/env bash # bin/install # Install the application command_exists() { command -v "$@" > /dev/null 2>&1 } if ! command_exists fnm; then echo "fnm is not installed" echo "Visit https://github.com/Schniz/fnm to install" exit fi if ! command_exists composer; then echo "fnm is not installed" echo "Visit https://getcomposer.org/download/ to install" exit fi if ! command_exists yarn; then echo "fnm is not installed" echo "Visit https://yarnpkg.com/lang/en/docs/install/ to install" exit fi echo "Removing existing ./node_modules folder" rm -rf ./node_modules echo "Removing existing ./vendor folder" rm -rf ./vendor fnm use composer install yarn install cp .env.example .env php artisan key:generate php artisan migrate:fresh --seed
The bin/run.sh
script as as follows:
#!/usr/bin/env bash # bin/run # Run the local development environment if [["$OSTYPE" == "linux"*]]; then sudo service mysql start fi php -S 0.0.0.0:8000 -t public/ & yarn run watch
and finally, this is the bin/precommit.sh
:
#!/usr/bin/env bash # bin/precommit # Tasks to run pre-comment php ./vendor/bin/phpunit
The scripts do not need overcomplicating, and can simply be a list of commands. The bin/precommit.sh
script only runs my test suite and nothing more.
Crucially, it holds up across all my projects spanning years the tech stacks of their time.
Laravel? WordPress? Static? No problem.
Nice!
Creating your own
I can offer some final words of encouragement, if you’re interested in creating your own.
- Start small. Don’t worry about adding support for Windows environments if you only ever use macOS. Add as you need.
- Not everything needs scripting. A printed message to direct the user could be enough.
- Keep it simple. There’s a temptation to create abstractions from your scripts. Resist adding complexity, duplication is not always a bad thing.
- Create aliases. I’m a big fan of aliases and saving a keystroke. Once you’ve settled on your script names, you can alias them for even more convenience.
Top comments (0)