#!/usr/bin/env bash

####### Configure environment

set -e            # fail fast
set -o pipefail   # don't ignore exit codes when piping output
# set -x          # enable debugging

bp_version="63-rc"

# Configure directories
build_dir=$1
cache_dir=$2
env_dir=$3
bp_dir=$(cd $(dirname $0); cd ..; pwd)
heroku_dir=$build_dir/.heroku
logfile=/tmp/node-log.txt

# Load some convenience functions like status(), echo(), and indent()
source $bp_dir/bin/common.sh

# Output version
head "Node.js Buildpack v$bp_version"

# Avoid GIT_DIR leak from previous build steps
unset GIT_DIR

# Create directories & log file
mkdir -p $heroku_dir/node
echo "" > $logfile

trap build_failed ERR

# Load config vars into environment; start with defaults
export NPM_CONFIG_PRODUCTION=true
export NODE_MODULES_CACHE=true

if [ -d "$env_dir" ]; then
  export_env_dir $env_dir
fi

####### Determine current state

# Which version of the buildpack did we use last time?
bp_previous=$(file_contents "$cache_dir/node/bp-version")

# What's the requested semver range for node?
node_engine=$(package_json ".engines.node")
node_previous=$(file_contents "$cache_dir/node/node-version")

# What's the requested semver range for npm?
npm_engine=$(package_json ".engines.npm")
npm_previous=$(file_contents "$cache_dir/node/npm-version")

# How does this app start?
if test -f $build_dir/Procfile; then start_method="Procfile"
elif [[ $(package_json ".scripts.start") != "" ]]; then start_method="npm start"
elif [ -f $build_dir/server.js ]; then start_method="server.js"
else start_method=""
fi

# What's the source-of-truth for node_modules?
if test -d $build_dir/node_modules; then modules_source="prebuilt"
elif test -f $build_dir/npm-shrinkwrap.json; then modules_source="npm-shrinkwrap.json"
elif test -f $build_dir/package.json; then modules_source="package.json"
else modules_source=""
fi

# What does our cache look like?
test -d $cache_dir/node/node_modules && modules_cached=true || modules_cached=false

####### Provide debugging info and feedback

echo ""
info "Node engine:         ${node_engine:-unspecified}"
info "Npm engine:          ${npm_engine:-unspecified}"
info "Start mechanism:     ${start_method:-none}"
info "node_modules source: ${modules_source:-none}"
info "node_modules cached: $modules_cached"

echo ""

printenv | grep ^NPM_CONFIG_ | indent
info "NODE_MODULES_CACHE=$NODE_MODULES_CACHE"

source $bp_dir/bin/warnings.sh

####### Vendor in binaries

head "Installing binaries"

# Resolve non-specific node versions using semver.herokuapp.com
if ! [[ "$node_engine" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  info "Resolving node version ${node_engine:-(latest stable)} via semver.io..."
  node_engine=$(curl --silent --get --data-urlencode "range=${node_engine}" https://semver.herokuapp.com/node/resolve)
fi

# Download node from Heroku's S3 mirror of nodejs.org/dist
info "Downloading and installing node $node_engine..."
node_url="http://s3pository.heroku.com/node/v$node_engine/node-v$node_engine-linux-x64.tar.gz"
curl $node_url -s -o - | tar xzf - -C /tmp

# Move node (and npm) into .heroku/node and make them executable
mv /tmp/node-v$node_engine-linux-x64/* $heroku_dir/node
chmod +x $heroku_dir/node/bin/*
PATH=$heroku_dir/node/bin:$PATH

# Optionally bootstrap a different npm version
if [ "$npm_engine" != "" ]; then
  if ! [[ "$npm_engine" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    info "Resolving npm version ${npm_engine} via semver.io..."
    npm_engine=$(curl --silent --get --data-urlencode "range=${npm_engine}" https://semver.herokuapp.com/npm/resolve)
  fi
  info "Downloading and installing npm $npm_engine (replacing version `npm --version`)..."
  npm install --quiet -g npm@$npm_engine > /dev/null
fi

# Run subsequent commands from the build directory
cd $build_dir

####### Build the project's dependencies

head "Building dependencies"

# Did we bust the cache?
if ! $modules_cached; then
  use_cache=false
elif ! $NODE_MODULES_CACHE; then
  info "Cache disabled with NODE_MODULES_CACHE"
  use_cache=false
elif [ "$node_previous" != "" ] && [ "$node_engine" != "$node_previous" ]; then
  info "Node version changed ($node_previous => $node_engine); invalidating cache"
  use_cache=false
elif [ "$npm_previous" != "" ] && [ "$npm_engine" != "$npm_previous" ]; then
  info "Npm version changed ($npm_previous => $npm_engine); invalidating cache"
  use_cache=false
elif [ "$bp_version" != "$bp_previous" ]; then
  info "New buildpack version ($bp_version); invalidating cache"
  use_cache=false
else
  use_cache=true
fi

if [ "$modules_source" == "" ]; then
  info "Skipping dependencies (no source for node_modules)"

elif [ $modules_source == "prebuilt" ]; then
  info "Rebuilding any native modules for this architecture"
  npm rebuild 2>&1

elif $use_cache; then
  info "Restoring node modules from cache"
  cp -r $cache_dir/node/node_modules $build_dir/
  info "Pruning unused dependencies"
  npm prune 2>&1
  info "Installing any new modules"
  npm install --quiet --userconfig $build_dir/.npmrc 2>&1

else
  info "Installing node modules"
  touch $build_dir/.npmrc
  npm install --quiet --userconfig $build_dir/.npmrc 2>&1
  info "Deduping dependency tree"
fi

####### Create a Procfile if possible

head "Checking startup method"

if [ "$start_method" == "Procfile" ]; then
  info "Found Procfile"
elif [ "$start_method" == "npm start" ]; then
  info "No Procfile; Adding 'web: npm start' to new Procfile"
  echo "web: npm start" > $build_dir/Procfile
elif [ "$start_method" == "server.js" ]; then
  info "No Procfile; Adding 'web: node server.js' to new Procfile"
  echo "web: node server.js" > $build_dir/Procfile
fi

####### Create the runtime environment (profile.d)

head "Finalizing build"

# Runtime & Multi-buildpack exports
info "Creating runtime environment"
mkdir -p $build_dir/.profile.d
echo "export PATH=\"\$HOME/.heroku/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $build_dir/.profile.d/nodejs.sh
echo "export NODE_HOME=\"\$HOME/.heroku/node\"" >> $build_dir/.profile.d/nodejs.sh

info "Exporting binary paths"
echo "export PATH=\"$build_dir/.heroku/node/bin:$build_dir/node_modules/.bin:\$PATH\"" > $bp_dir/export
echo "export NODE_HOME=\"$build_dir/.heroku/node\"" >> $bp_dir/export

####### Clean up

info "Cleaning up build artifacts"

# Clean up after npm
rm -rf "$build_dir/.node-gyp"
rm -rf "$build_dir/.npm"

# Clear the cache
rm -rf $cache_dir/node_modules # (for apps still on the older caching strategy)
rm -rf $cache_dir/node

####### Build successful! Store results in cache

# Create the cache
mkdir -p $cache_dir/node

echo $node_engine > $cache_dir/node/node-version
echo $npm_engine > $cache_dir/node/npm-version
echo $bp_version > $cache_dir/node/bp-version

if test -d $build_dir/node_modules; then
  info "Caching node_modules for future builds"
  cp -r $build_dir/node_modules $cache_dir/node
fi

# Show the final dependency tree
info "Build successful!"
(npm ls --depth=0 || true) 2>/dev/null | indent
