#!/usr/bin/env bash
# bin/compile <build-dir> <cache-dir> <env-dir>

### Configure environment

set -o errexit    # always exit on error
set -o pipefail   # don't ignore exit codes when piping output
unset GIT_DIR     # Avoid GIT_DIR leak from previous build steps

### Constants

DEFAULT_CACHE="node_modules bower_components"
BPLOG_PREFIX="buildpack.nodejs"

### Configure directories

BUILD_DIR=${1:-}
CACHE_DIR=${2:-}
ENV_DIR=${3:-}
BP_DIR=$(cd $(dirname ${0:-}); cd ..; pwd)
STDLIB_FILE=$(mktemp -t stdlib.XXXXX)

### Load dependencies

curl --silent --retry 5 --retry-max-time 15 'https://lang-common.s3.amazonaws.com/buildpack-stdlib/v7/stdlib.sh' > "$STDLIB_FILE"
source "$STDLIB_FILE"
source $BP_DIR/lib/output.sh
source $BP_DIR/lib/json.sh
source $BP_DIR/lib/failure.sh
source $BP_DIR/lib/environment.sh
source $BP_DIR/lib/binaries.sh
source $BP_DIR/lib/cache.sh
source $BP_DIR/lib/dependencies.sh

export PATH="$BUILD_DIR/.heroku/node/bin:$BUILD_DIR/.heroku/yarn/bin":$PATH

LOG_FILE=$(mktemp -t node-build-log.XXXXX)
echo "" > "$LOG_FILE"

### Handle errors

handle_failure() {
  header "Build failed"
  fail_yarn_lockfile_outdated "$LOG_FILE"
  fail_node_install "$LOG_FILE"
  fail_yarn_install "$LOG_FILE"
  fail_invalid_semver "$LOG_FILE"
  warn_untracked_dependencies "$LOG_FILE"
  warn_angular_resolution "$LOG_FILE"
  warn_missing_devdeps "$LOG_FILE"
  warn_econnreset "$LOG_FILE"
  warn_young_yarn "$LOG_FILE"
  failure_message | output "$LOG_FILE"
}
trap 'handle_failure' ERR

### Check initial state

[ -e "$BUILD_DIR/node_modules" ] && PREBUILD=true || PREBUILD=false
[ -f "$BUILD_DIR/yarn.lock" ] && YARN=true || YARN=false
[ -f "$BUILD_DIR/package-lock.json" ] && NPM_LOCK=true || NPM_LOCK=false

### Failures that should be caught immediately

fail_dot_heroku "$BUILD_DIR"
fail_dot_heroku_node "$BUILD_DIR"
fail_invalid_package_json "$BUILD_DIR"
fail_multiple_lockfiles "$BUILD_DIR"
warn_prebuilt_modules "$BUILD_DIR"
warn_missing_package_json "$BUILD_DIR"

### Compile

create_env() {
  write_profile "$BP_DIR" "$BUILD_DIR"
  write_export "$BP_DIR" "$BUILD_DIR"
  export_env_dir "$ENV_DIR"
  create_default_env
}

header "Creating runtime environment"

mkdir -p "$BUILD_DIR/.heroku/node/"
cd $BUILD_DIR
create_env # can't pipe the whole thing because piping causes subshells, preventing exports
list_node_config | output "$LOG_FILE"

install_bins() {
  local node_engine=$(read_json "$BUILD_DIR/package.json" ".engines.node")
  local iojs_engine=$(read_json "$BUILD_DIR/package.json" ".engines.iojs")
  local npm_engine=$(read_json "$BUILD_DIR/package.json" ".engines.npm")
  local yarn_engine=$(read_json "$BUILD_DIR/package.json" ".engines.yarn")

  if [ -n "$iojs_engine" ]; then
    echo "engines.iojs (package.json):  $iojs_engine (iojs)"
  else
    echo "engines.node (package.json):  ${node_engine:-unspecified}"
  fi
  echo "engines.npm (package.json):   ${npm_engine:-unspecified (use default)}"
  if $YARN; then
    echo "engines.yarn (package.json):  ${yarn_engine:-unspecified (use default)}"
  fi
  echo ""

  if [ -n "$iojs_engine" ]; then
    warn_node_engine "$iojs_engine"
    install_iojs "$iojs_engine" "$BUILD_DIR/.heroku/node"
    echo "Using bundled npm version for iojs compatibility: `npm --version`"
    mcount "version.iojs.$(node --version)"
  else
    warn_node_engine "$node_engine"
    install_nodejs "$node_engine" "$BUILD_DIR/.heroku/node"
    install_npm "$npm_engine" "$BUILD_DIR/.heroku/node" $NPM_LOCK
    mcount "version.node.$(node --version)"
  fi

  # Download yarn if there is a yarn.lock file or if the user
  # has specified a version of yarn under "engines". We'll still
  # only install using yarn if there is a yarn.lock file
  if $YARN || [ -n "$yarn_engine" ]; then
    install_yarn "$BUILD_DIR/.heroku/yarn" "$yarn_engine"
  fi

  if $YARN; then
    mcount "version.yarn.$(yarn --version)"
  else
    mcount "version.npm.$(npm --version)"
  fi

  warn_old_npm
}

header "Installing binaries"
install_bins | output "$LOG_FILE"

restore_cache() {
  local cache_status="$(get_cache_status)"

  if $YARN; then
    if [ -e "$BUILD_DIR/node_modules" ]; then
      warn "node_modules checked into source control" "https://blog.heroku.com/node-habits-2016#9-only-git-the-important-bits"
      rm -rf "$BUILD_DIR/node_modules"
    fi
  fi
  if [ "$cache_status" == "valid" ]; then
    local cache_directories=$(get_cache_directories)
    if [ "$cache_directories" == "" ]; then
      echo "Loading 2 from cacheDirectories (default):"
      restore_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$DEFAULT_CACHE"
    else
      echo "Loading $(echo $cache_directories | wc -w | xargs) from cacheDirectories (package.json):"
      restore_cache_directories "$BUILD_DIR" "$CACHE_DIR" $cache_directories
    fi
  else
    echo "Skipping cache restore ($cache_status)"
  fi

  mcount "cache.$cache_status"
}

header "Restoring cache"
restore_cache | output "$LOG_FILE"

build_dependencies() {
  run_if_present 'heroku-prebuild'

  local cache_status="$(get_cache_status)"
  local start=$(nowms)

  if $YARN; then
    yarn_node_modules "$BUILD_DIR"
  elif $PREBUILD; then
    echo "Prebuild detected (node_modules already exists)"
    npm_rebuild "$BUILD_DIR"
  else
    npm_node_modules "$BUILD_DIR"
  fi

  mtime "modules.time.cache.$cache_status" "${start}"

  run_if_present 'heroku-postbuild'
  # TODO: run_if_present 'build'
  log_build_scripts
}

header "Building dependencies"
build_dependencies | output "$LOG_FILE"

cache_build() {
  local cache_directories=$(get_cache_directories)

  echo "Clearing previous node cache"
  clear_cache
  if ! ${NODE_MODULES_CACHE:-true}; then
    echo "Skipping cache save (disabled by config)"
  elif [ "$cache_directories" == "" ]; then
    echo "Saving 2 cacheDirectories (default):"
    save_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$DEFAULT_CACHE"
  else
    echo "Saving $(echo $cache_directories | wc -w | xargs) cacheDirectories (package.json):"
    save_cache_directories "$BUILD_DIR" "$CACHE_DIR" $cache_directories
  fi
  save_signature
}

header "Caching build"
cache_build | output "$LOG_FILE"

summarize_build() {
  if $NODE_VERBOSE; then
    list_dependencies "$BUILD_DIR"
  fi

  mmeasure 'modules.size' "$(measure_size)"
}

header "Build succeeded!"
mcount "compile"
summarize_build | output "$LOG_FILE"

warn_no_start "$LOG_FILE"
warn_unmet_dep "$LOG_FILE"
warn_old_npm_lockfile $NPM_LOCK
