#!/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
status "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 errors
trap tail_error_log ERR

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

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 ""
status "Node engine:         ${node_engine:-unspecified}"
status "Npm engine:          ${npm_engine:-default}"
status "Start mechanism:     ${start_method:-none}"
status "node_modules source: ${modules_source:-none}"
status "node_modules cached: $modules_cached"

echo ""

status "NPM_CONFIG_PRODUCTION=$NPM_CONFIG_PRODUCTION"
status "BUILD_CLEAN=$BUILD_CLEAN"

source $bp_dir/bin/warnings.sh

####### Vendor in binaries

echo ""

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

# Download node from Heroku's S3 mirror of nodejs.org/dist
status "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
    status "Resolving npm version ${npm_engine} via semver.io..."
    npm_engine=$(curl --silent --get --data-urlencode "range=${npm_engine}" https://semver.io/npm/resolve)
  fi
  status "Downloading and installing npm $npm_engine (replacing version `npm --version`)..."
  npm install -g npm@$npm_engine >> $logfile 2>&1
fi

# Run subsequent commands from the build directory
cd $build_dir

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

# Did we bust the cache?
if ! $modules_cached; then
  cache_busted=true
elif [ "$node_previous" != "" ] && [ "$node_engine" != "$node_previous" ]; then
  status "Node version changed ($node_previous => $node_engine); invalidating cache"
  cache_busted=true
elif [ "$npm_previous" != "" ] && [ "$npm_engine" != "$npm_previous" ]; then
  status "Npm version changed ($npm_previous => $npm_engine); invalidating cache"
  cache_busted=true
elif [ "$bp_version" != "$bp_previous" ]; then
  status "New buidpack version ($bp_version); invalidating cache"
  cache_busted=true
else
  cache_busted=false
fi

if [ "$modules_source" == "" ]; then
  status "Skipping dependencies"

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

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

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

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

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

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

status "Building runtime environment"
mkdir -p $build_dir/.profile.d

# Runtime & Multi-buildpack exports
export_path="export PATH=\"\$HOME/.heroku/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\""
export_home="export NODE_HOME=\"\$HOME/.heroku/node\""
echo $export_path > $build_dir/.profile.d/nodejs.sh
echo $export_home >> $build_dir/.profile.d/nodejs.sh
echo $export_path > $bp_dir/export
echo $export_home >> $bp_dir/export

####### Clean up

status "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
  status "Caching node_modules for future builds"
  cp -r $build_dir/node_modules $cache_dir/node
fi

####### Summary output

# Show the final dependency tree
echo ""
npm ls | indent
