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.
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 anode
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.
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!