Build separated development environments with direnv

Inhaltsverzeichnis

TL;DR

Working with Ionic sometimes means to handle different versions of Ionic/Cordova and Ionic/Cordova Plugins. My goal was, to setup an Ionic environment specify for each project. And I want do activate the environments automatically.

I started with autoenv, but unfortunately there is no unload/deactivate mechanism. So, finally, i switched to direnv. But i also tried different solutions with Vagrant or Docker.

Requirements

You will need a package manager to install the different software packages. There are a variety of package manager for the different platforms. 

For macOS, i will use Homebrew.

Things about my directory structure

Mostly, i try to group files in different directories, depending on their functionality.

Environment files goes to a directory .env. But, wait: there could be more environments (node.js, ruby, python), which all could be used in my project.

So, i decide to use subfolders for each possible environment. Here it looks like:

$ tree myproject
myproject
└── .env
    ├── nvm
    ├── pyenv
    └── ruby

Some small example

Suppose, you have this directory structure

.
├── home
│   ├── .envrc
│   └── bath
│       └── .envrc
└── office
    └── .envrc

If you walk between this different rooms, you will see how direnv follow you

~/tmp$ cd home
direnv: loading .envrc
You are at home
~/tmp/home$ cd bath
direnv: loading .envrc
You are in the bath
~/tmp/home/bath$ cd ..
direnv: loading .envrc
You are at home
~/tmp/home$ cd ..
direnv: unloading
~/tmp$ cd office
direnv: loading .envrc
You are at work
~/tmp/office$ cd ../home/bath
direnv: loading .envrc
You are in the bath
~/tmp/home/bath$

Installation

There is a detailed installation instruction here, but for macOS, here is the simple step

$ brew install direnv
==> Downloading https://homebrew.bintray.com/bottles/direnv-2.14.0.high_sierra.bottle.tar.gz
Already downloaded: /home/user/Library/Caches/Homebrew/direnv-2.14.0.high_sierra.bottle.tar.gz
==> Pouring direnv-2.14.0.high_sierra.bottle.tar.gz
  /usr/local/Cellar/direnv/2.14.0: 8 files, 3.7MB

Finally, check if installation has succeed

$ direnv version
2.14.0

Configuration

Directory specific configuration is made through the file .envrc. It’s like a normal Shell profile script, so mostly all commands are allowed and possible.

Entering the directory triggers this configuration file, so a normal cd into the directory does the environment setup.. pretty cool!

In my simple example, i used this configuration files

~/tmp$ find . -name .envrc
./home/.envrc
./home/bath/.envrc
./office/.envrc
~/tmp$ find . -name .envrc -exec cat {} \;
echo "You are at home"
echo "You are in the bath"
echo "You are at work"

Setting up a sample project

Ionic heavily depends on node.js So, the challenge is to setup different node.js environments. Luckily, there is already a solution for that: Node Version Manager nvm.

Install nvm

For macOS, this is quit simple (details could be found here)

$ brew install nvm

Or, manually

$ wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash

Setup directory structure

Create the desired directory structure

$ cd $HOME/tmp
$ mkdir -p myproject2/.env/nvm

Configure direnv

Add the following lines (with some additional lines) into your direnv configuration file .envrc

#!/bin/bash

export NVM_DIR=$HOME/tmp/.env/nvm

echo setup nvm with NVM_DIR=$NVM_DIR
. /usr/local/opt/nvm/nvm.sh

nvm --version

Hint: When you created the file (for example with vi), you get the error message, that the .envrc file is blocked

$ vi .envrc
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.

Follow the suggested solution: run direnv allow

$ direnv allow
direnv: loading .envrc
setup nvm with NVM_DIR=/home/user/tmp/myproject/.env/nvm
0.33.8
direnv: export +NVM_CD_FLAGS +NVM_DIR +NVM_RC_VERSION

Now, test the configuration: Leave the directory and enter it again

$ cd ..
direnv: unloading
$ cd myproject
direnv: loading .envrc
setup nvm with NVM_DIR=/home/user/tmp/myproject/.env/nvm
0.33.8
direnv: export +NVM_CD_FLAGS +NVM_DIR +NVM_RC_VERSION

Hint: If this does not work, try to start the configuration file manually for the first time, 

$ cd $HOME/tmp/myproject
$ . .envrc
setup nvm with NVM_DIR=/home/user/tmp/myproject/.env/nvm
0.33.8

Fix possibile warnings and errors

If you get this error message

nvm is not compatible with the npm config "prefix" option: currently set to ...

Then, issue this command

$ npm config delete prefix

Install node

Now, the real task: install node.js

$ cd $HOME/tmp/myproject
$ nvm --version
0.33.8
$ node --version
-bash: node: command not found
$ nvm install 9.4.0
Downloading and installing node v9.4.0...
Downloading https://nodejs.org/dist/v9.4.0/node-v9.4.0-darwin-x64.tar.xz...
######################################################################## 100,0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v9.4.0 (npm v5.6.0)
Creating default alias: default -> 9.4.0 (-> v9.4.0)

Test the installation

$ which node
/home/user/tmp/myproject/.env/nvm/versions/node/v9.4.0/bin/node
$ node --version
v9.4.0

If you want to use an additional node version (maybe the latest LTS version), just install it

$ nvm install lts/carbon
Downloading and installing node v8.9.4...
Downloading https://nodejs.org/dist/v8.9.4/node-v8.9.4-darwin-x64.tar.xz...
######################################################################## 100,0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v8.9.4 (npm v5.6.0)

You can switch between the different versions with nvm use

$ nvm use lts/carbon
Now using node v8.9.4 (npm v5.6.0)
$ node --version; npm --version
v8.9.4
5.6.0
$ nvm use default
Now using node v9.4.0 (npm v5.6.0)
$ node --version; npm --version
v9.4.0
5.6.0

Setting up Ionic Environment

After this required preparation, we can start setting up our Ionic Environment

For this example, I choose the following start path: ~/Workspace/Ionic3

Setup directory structure

$ mkdir -p $HOME/Workspace/Ionic3/.env/nvm
$ cd $HOME/Workspace/Ionic3
$ vi .envrc

Create the direnv configuration file

Configuration file

#!/bin/bash
#-------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------
  HERE="$PWD"

#-------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------
  _USE_NODE_VERSION=9.4.0

#-------------------------------------------------------------------------------
#
#-------------------------------------------------------------------------------
  export NVM_DIR="$HERE/.env/nvm"

  echo "setup nvm: $NVM_DIR"
  . /usr/local/opt/nvm/nvm.sh

  nvm use $_USE_NODE_VERSION --silent

  echo "enabled nvm :  $(nvm  --version) $(which nvm  | sed 's#'$NVM_DIR'/##')"
  echo "enabled node:  $(node --version) $(which node | sed 's#'$NVM_DIR'/##')"
  echo "enabled npm :  $(npm  --version) $(which npm  | sed 's#'$NVM_DIR'/##')"

Allow the configuration file for use

$ direnv allow
direnv: loading .envrc
setup nvm: /home/user/Workspace/Ionic3/.env/nvm
enabled nvm : 0.33.8
enabled node: v9.4.0 versions/node/v9.4.0/bin/node
enabled npm : 5.6.0 versions/node/v9.4.0/bin/npm
direnv: export +NVM_CD_FLAGS +NVM_DIR

Test the configuration

~$ cd
~$ cd Workspace/Ionic3
direnv: loading .envrc
setup nvm: /home/user/Workspace/Ionic3/.env/nvm
enabled nvm :  0.33.8
enabled node:  v9.4.0 versions/node/v9.4.0/bin/node
enabled npm :  5.6.0 versions/node/v9.4.0/bin/npm
direnv: export +NVM_CD_FLAGS +NVM_DIR

Well done!

Setup Ionic

Install required parts

$ npm -g install ionic@latest cordova@latest

Test your installation

~/Workspace/Ionic3$ ionic --version
3.19.1
~/Workspace/Ionic3$ cordova --version
8.0.0
~/Workspace/Ionic3$ $ which ionic
/home/user/Workspace/Ionic3/.env/nvm/versions/node/v9.4.0/bin/ionic

And, finally, work with Ionic

$ ionic start super super --cordova --no-link
$ cd super
$ ionic serve

Next steps

Happy with this, maybe you want to try other things.