Skip to content

Instantly share code, notes, and snippets.

@loklaan
Last active September 15, 2019 06:19
Show Gist options
  • Save loklaan/bc996796216f749a6699a00cdfd779ba to your computer and use it in GitHub Desktop.
Save loklaan/bc996796216f749a6699a00cdfd779ba to your computer and use it in GitHub Desktop.
Adding to Lerna managed packages

Adding to managed packages

lerna bootstrap --hoist + yarn add

When added to the preinstall script for managed packages, completely overrides the yarn add behaviour to accomplish two things:

  1. Adding the package deps to package.json
    eg. yarn add <package-a> <package-b> will add "package-a": "^1.0.0" & "package-b": "^1.0.0"
  2. Run lerna bootstrap --hoist at the monorepo root
  3. ...That's it - yarn is then sent a kill sig.
$ yarn add foo

yarn add v0.27.5
info No lockfile found.
$ scripts/yarn-add-lerna-override.sh

 > Overriding package management.
 >
 > Extracting dependency names & versions...
 > Adding to `dependencies`:
 >  foo@^1.0.0
 >
 > package.json updated
 >
 > Installing packages with lerna...

/path/to/lerna/monorepo
yarn install-deps v0.27.5
$ yarn bootstrap -- --noscripts
lerna info version 2.0.0
lerna info versioning independent
lerna info Bootstrapping 99 packages
lerna info Installing external dependencies
lerna info hoist Installing hoisted dependencies into root
lerna info hoist Pruning hoisted dependencies
lerna info hoist Finished pruning hoisted dependencies
lerna info hoist Finished installing in root
lerna info Symlinking packages and binaries
lerna success Bootstrapped 99 packages
Done in 10s.

 > Exiting from yarn lifecycles... (might see a kill signal)
#! /usr/bin/env bash
#/
#|-------------------------------------------------------------------------------
#| Util Lib for Bash
#|-------------------------------------------------------------------------------
#|
#| Includes:
#| - Shell color escapes
#| - Function to print pretty errors for missing bin's
#|
#/
reset='\e[0m'
bold='\e[1m'
dim='\e[2m'
underlined='\e[4m'
blink='\e[5m'
inverted='\e[7m'
hidden='\e[8m'
default='\e[39m'
black='\e[30m'
white='\e[97m'
red='\e[31m'
green='\e[32m'
yellow='\e[33m'
blue='\e[34m'
magenta='\e[35m'
cyan='\e[36m'
lightGray='\e[37m'
darkGray='\e[90m'
lightRed='\e[91m'
lightGreen='\e[92m'
lightYellow='\e[93m'
lightBlue='\e[94m'
lightMagenta='\e[95m'
lightCyan='\e[96m'
bgDefault='\e[49m'
bgBlack='\e[40m'
bgWhite='\e[107m'
bgRed='\e[41m'
bgGreen='\e[42m'
bgYellow='\e[43m'
bgBlue='\e[44m'
bgMagenta='\e[45m'
bgCyan='\e[46m'
bgLightGray='\e[47m'
bgDarkGray='\e[100m'
bgLightRed='\e[101m'
bgLightGreen='\e[102m'
bgLightYellow='\e[103m'
bgLightBlue='\e[104m'
bgLightMagenta='\e[105m'
bgLightCyan='\e[106m'
lib_cli_exists () {
_path=${2:-"$PATH"}
if ! PATH="$_path" which "$1" &> /dev/null; then
>&2 printf " ${bgRed}${white}${bold} Error: Could not find '$1' cli on PATH. ${reset}"
>&2 echo
PATH="$_path" which "$1" &> /dev/null
fi
}
#! /usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
#/
#|------------------------------------------------------------------------------
#| Add a Dependency to a Managed Package
#|------------------------------------------------------------------------------
#|
#| Note: Depends on `json` cli from npm!
#| eg. `npm i -g json`
#|
#| Override the default `yarn add` behaviour, to make sure that dependencies
#| are installed & hoisted by lerna.
#|
#| If dependencies are not hoisted by lerna, then we can get duplicate singleton
#| issues during js runtime.
#|
#| Note: This script must be run in the context of the 'preinstall' lifecycle hook
#| in yarn (not npm - it does not work in npm until v5).
#|
#/
if [ -L "$0" ]; then
_linked_dir=$(dirname "$0")
_link_dir=$(dirname "$(readlink $0)")
script_dir=$(cd "$_linked_dir"; cd "$_link_dir"; pwd)
else
script_dir=$(dirname "$0")
fi
source "$script_dir/bash-lib.sh"
package_dir=$(pwd)
package_json="$package_dir/package.json"
monorepo_root_dir=$(cd "$package_dir/../../"; pwd)
yarn_inner_pid="$(ps -p $$ -o ppid= | xargs)"
yarn_cli_pid="$(ps -p "$yarn_inner_pid" -o ppid= | xargs)"
yarn_cli_cmd="$(ps -p "$yarn_cli_pid" -o args=)"
PATH="$PATH:$script_dir/node_modules/.bin:$monorepo_root_dir/node_modules/.bin:$package_dir/node_modules/.bin"
function js () {
node -pe "${1:?}"
}
function pr () {
>&2 printf " > ${1:-""}"
>&2 echo
}
>&2 echo
package_name=`js "require('$package_dir/package.json').name"`
should_catch_cmd=`js "/yarn.+add/.test('$yarn_cli_cmd')"`
is_being_added=`js "'$yarn_cli_cmd'.includes('$package_name')"`
if [ "$should_catch_cmd" = "false" ] || [ "$is_being_added" = "true" ]; then
pr "Ignoring \`$npm_lifecycle_event\` initiator."
echo
exit 0
else
pr "${green}Overriding package management.${reset}"
pr
fi
function exit_yarn () {
# The extend of this script is to overload yarn behaviour - once run, we don't
# care about yarn continuing.
pr "Exiting from yarn lifecycles... (might see a kill signal)"
if [ $(js "'$(ps -p $$ -oargs=)'.includes('zsh')") = "false" ]; then echo; fi
kill -9 "$yarn_cli_pid" "$yarn_inner_pid" > /dev/null
tput cuu1
tput el
}
packages=""
# Go through the args from `yarn add`, extracting the package names to add
function get_packages_from_cmd () {
pr "Extracting dependency names & versions..."
has_reached_past_cmd="false"
for pkg in $(echo "$yarn_cli_cmd" | tr " " "\n"); do
if [ "$has_reached_past_cmd" = "true" ]; then
if [ ! $(grep '^-' <<<"$pkg") ]; then
pkg_split=`js "'$pkg'.split(/(?!^)@/)"`
pkg_name=`js "${pkg_split}[0]"`
pkg_version=`js "${pkg_split}[1] || ''"`
if [ "$pkg_version" = "" ]; then
pkg_ver_json="$(yarn info "$pkg_name" dist-tags.latest --json 2>&1)"
response_type=$(json <<<"$pkg_ver_json" "type")
response_data=$(json <<<"$pkg_ver_json" "data")
if [ "$response_type" != "inspect" ]; then
pr "Unexpected response for latest version of ${dim}\`$pkg_name\`${reset}."
pr "Error: $response_data"
exit_yarn
exit 1
fi
pkg_version="${npm_config_save_prefix}${response_data}"
fi
if [ "$packages" = "" ]; then
packages+="${pkg_name}:${pkg_version}"
else
packages+=";${pkg_name}:${pkg_version}"
fi
fi
else
if [ "$pkg" = "add" ]; then
has_reached_past_cmd="true"
fi
fi
done
}
# Find which kind of dependency to add the packages as
function get_dependency_type_from_cmd () {
dep_type="dependencies"
has_reached_past_cmd="false"
for arg in $(echo "$yarn_cli_cmd" | tr " " "\n"); do
if [ "$has_reached_past_cmd" = "true" ]; then
if [ $(grep '^-' <<<"$arg") ]; then
arg_name=`js "'$arg'.replace(/^-+/, '')"`
case "$arg_name" in
"D" | "dev") dep_type="devDependencies" ;;
"P" | "peer") dep_type="peerDependencies" ;;
"O" | "optional") dep_type="optionalDependencies" ;;
esac
break
fi
else
if [ "$arg" = "add" ]; then
has_reached_past_cmd="true"
fi
fi
done
}
# Create the js code to execute with the `json` cli
function add_packages_to_packagejson () {
dep_body=""
for pkg in $(echo "$packages" | tr ";" "\n"); do
dep_body+="$(js "
'$pkg'.replace(
/^(.*):(.*)$/,
'\'\$1\': \'\$2\','
)
")"
done
js_script_add_deps="
let { assign, keys } = Object;
let depsAll = assign({}, this.${dep_type}, {${dep_body}});
let depsKeys = keys(depsAll).sort();
this.${dep_type} = depsKeys.reduce((deps, key) => {
deps[key] = depsAll[key]
return deps;
}, {});
"
# Edit the package.json in-place, with the new dependencies
json -I -q -f "$package_json" -e "$js_script_add_deps"
}
function install_packages () {
# The crucial next step is to download/install the new packages with lerna
echo "$monorepo_root_dir"
cd "$monorepo_root_dir"
yarn install-deps
}
if ! lib_cli_exists json; then
exit_yarn
exit 1
fi
get_packages_from_cmd
get_dependency_type_from_cmd
pr "Adding to \`${dim}${dep_type}${reset}\`:"
for pkg in $(echo "$packages" | tr ";" "\n"); do
name="${bold}$(js "'$pkg'.replace(/:.*/, '')")${reset}"
ver="${dim}@$(js "'$pkg'.replace(/.*:/, '')")${reset}"
pr " ${name}${ver}"
done
pr
add_packages_to_packagejson
pr "${underlined}package.json${reset} updated"
pr
pr "Installing packages with ${bold}lerna${reset}..."
echo
install_packages
echo
exit_yarn
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment