Skip to content

Instantly share code, notes, and snippets.

@nellshamrell
Last active September 18, 2017 22:24
Show Gist options
  • Save nellshamrell/4096658d4878cbbc9be009baa7d10946 to your computer and use it in GitHub Desktop.
Save nellshamrell/4096658d4878cbbc9be009baa7d10946 to your computer and use it in GitHub Desktop.

What do I need to know?

I need to know how core/scaffolding-node figures out what version of node to use.

Normally, I would expect core/scaffolding-node to have a direct dependency on core/node, but it does not appear to.

core-plans/scaffolding-node/plan.sh

pkg_deps=(core/tar core/jq-static core/rq)

Furthermore, core/node does not have a transitive dependency on core/scaffolding-node:

https://bldr.habitat.sh/#/pkgs/core/node/6.9.5/20170519005236

    core/acl/2.2.52/20170513213108
    core/attr/2.4.47/20170513213059
    core/coreutils/8.25/20170513213226
    core/gcc-libs/5.2.0/20170513212920
    core/glibc/2.22/20170513201042
    core/gmp/6.1.0/20170513202112
    core/libcap/2.24/20170513213120
    core/linux-headers/4.3/20170513200956

Hmmm...something's up here.

How does scaffolding-node work with Node versions?

Let's take a look at the core/scaffolding-node code, starting with the plan.sh.

core-plans/scaffolding-node/plan.sh

do_install() {
  find lib -type f | while read -r f; do
    install -D -m 0644 "$f" "$pkg_prefix/$f"
  done
}

Alright, looks like it finds files in the lib folder and installs them. Looking at the contents of core-plans/scaffolding-node/lib...there's just one file. Well, that makes it simpler at least!

14:59:51-nell~/projects/core-plans/scaffolding-node (update-node-version)$ ls lib/
scaffolding.sh

Let's take a look at the top of that file:

core-plans/scaffolding-node/lib/scaffolding.sh

# shellcheck shell=bash

scaffolding_load() {
  _setup_funcs
  _setup_vars

Let's checkout the _ setup_vars method:

core-plans/scaffolding-node/lib/scaffolding.sh

_setup_vars() {
  # The default Node package if one cannot be detected
  _default_node_pkg="core/node"
  # `$scaffolding_pkg_manager` is empty by default
  : "${scaffolding_pkg_manager:=}"
  # `$scaffolding_node_pkg` is empty by default
  : "${scaffolding_node_pkg:=}"
  # The install prefix path for the app
  scaffolding_app_prefix="$pkg_prefix/app"
  #
  : "${scaffolding_app_port:=8000}"
  # If `${scaffolding_env[@]` is not yet set, setup the hash
  if [[ ! "$(declare -p scaffolding_env 2> /dev/null || true)" =~ "declare -A" ]]; then
    declare -g -A scaffolding_env
  fi
  # If `${scaffolding_process_bins[@]` is not yet set, setup the hash
  if [[ ! "$(declare -p scaffolding_process_bins 2> /dev/null || true)" =~ "declare -A" ]]; then
    declare -g -A scaffolding_process_bins
  fi

  _jq="$(pkg_path_for jq-static)/bin/jq"
}

Hmm...this looks promising. Let's break it down into smaller pieces.

core-plans/scaffolding-node/lib/scaffolding.sh

  # The default Node package if one cannot be detected
  _default_node_pkg="core/node"
  # `$scaffolding_pkg_manager` is empty by default
  : "${scaffolding_pkg_manager:=}"
  # `$scaffolding_node_pkg` is empty by default
  : "${scaffolding_node_pkg:=}"

This reminds me of something I saw in the docs:

**core-plans/scaffolding-node/doc/reference.md

By default the latest version of the [`core/node`][] package will be injected into your Plan's `pkg_deps` array. To specify a non-default version of Node.js, there are three locations you can do this:

1. Use the [`engines.node`][package_json_engines] data structure in your app's `package.json`
2. Write an [`.nvmrc`][] in your app's root directory with the version of Node.js to use
3. Set the `scaffolding_node_pkg` variable in your Plan with a valid Habitat package identifier corresponding to a package with a `node` program

A set Plan variable will win over setting a version in your `package.json` and over an `.nvmrc` files (and the `package.json` will win over an `.nvmrc` file), however it is recommended to the `package.json` strategy first as this is most portable across other Node app build and deployment solutions.

Alright, that makes some sense. There are four ways to potentially set the node version:

  • don't do anything, you will get the default of core/node (assigned to the _default_node_pkg variable)
  • specify it in your app's package.json
  • add a .nvmrc file in your app's root directory
  • set the scaffolding_node_pkg variable in your plan - pointing to a Habitat package that condes a node program.

Looks like we do pull in core/node somehow, unless someone has overridden the version in package.json, .nvmrc, or as a scaffolding_node_pkg variable.

Seems reasonable to allow the user to specify which version of node they want to use in a variety of standardish ways. But we clearly do have a dependency on core/node - even though it is not specified in core-plans/scaffolding-node.

Let's dig into how we pull in and use core/node, when the user does not overwrite it.

How does scaffolding-node get core/node?

Looks like there is a _detect_node() function within scaffolding.sh

core-plans/scaffolding-node/lib/scaffolding.sh

_detect_node() {
  if [[ -n "$scaffolding_node_pkg" ]]; then
    _node_pkg="$scaffolding_node_pkg"
    build_line "Detected Node.js version in Plan, using '$_node_pkg'"
  else
    local val
    val="$(_json_val package.json .engines.node)"
    if [[ -n "$val" ]]; then
      # TODO fin: Add more robust packages.json to Habitat package matching
      case "$val" in
        *)
          _node_pkg="core/node/$val"
          ;;
      esac
      build_line "Detected Node.js version '$val' in package.json, using '$_node_pkg'"
    elif [[ -f .nvmrc && -n "$(cat .nvmrc)" ]]; then
      val="$(trim "$(cat .nvmrc)")"
      # TODO fin: Add more robust .nvmrc to Habitat package matching
      case "$val" in
        *)
          _node_pkg="core/node/$val"
          ;;
      esac
      build_line "Detected Node.js version '$val' in .nvmrc, using '$_node_pkg'"
    else
      _node_pkg="$_default_node_pkg"
      build_line "No Node.js version detected in Plan, package.json, or .nvmrc, using default '$_node_pkg'"
    fi
  fi
  pkg_deps=($_node_pkg ${pkg_deps[@]})
  debug "Updating pkg_deps=(${pkg_deps[*]}) from Scaffolding detection"
}

We are looking in various places for the node version to use. Let's zero in on these lines:

core-plans/scaffolding-node/lib/scaffolding.sh

    else
      _node_pkg="$_default_node_pkg"
      build_line "No Node.js version detected in Plan, package.json, or .nvmrc, using default '$_node_pkg'"
   fi

The big thing this function does is set the _node_pkg variable - which by default is $_default_node_pkg - which by default is 'core/node'.

Next thing, let's look at how that _node_pkg variable is used.

core-plans/scaffolding-node/lib/scaffolding.sh

pkg_deps=($_node_pkg ${pkg_deps[@]})
debug "Updating pkg_deps=(${pkg_deps[*]}) from Scaffolding detection"

So we're setting the pkg_deps - the node version (depending on where we find it) and the current pkg_deps. So this is where we add in that dependency on core/node!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment