Skip to content

Instantly share code, notes, and snippets.

@jart
Last active October 11, 2016 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jart/2c42b0e8b07503622df1a3ba32320267 to your computer and use it in GitHub Desktop.
Save jart/2c42b0e8b07503622df1a3ba32320267 to your computer and use it in GitHub Desktop.
NPM Transitive Deps Calculator
# NPM Dependency Calculator
#
# Author: Justine Tunney <jart@google.com>
# Copyright 2016 Google Inc. All Rights Reserved.
# Licensed under the Apache 2.0 license
# Last Updated on 2016-09-22
#
# This is a .bashrc addition that lets you inspect the transitive closure of
# dependencies for an NPM package. It does not require NPM or node.js to be
# installed on your system. It takes into consideration the fact that NPM
# does not resolve diamond dependencies. It goes super fast.
#
# This tool is useful because NPM packages have a large number of
# dependencies. It's not uncommon for a modern web app written with Node
# tooling to have thousands of dependencies. And they're all written by
# random people on the Internet. Even if these people had best intentions,
# NPM doesn't support two-factor authentication, checksums, or signatures.
# It only started using HTTPS a few months ago.
#
# Take for instance Express, which is a commonly used web framework in the
# Node.js community. It markets itself as the "Fast, unopinionated,
# minimalist web framework" but it actually has 42 dependencies:
#
# $ npm-transitive-deps express | wc -l
# 43
#
# If we were considering using Express for our project, we would also need
# to know what licenses all these dependencies have:
#
# $ npm-transitive-licenses express
# bsd-3-clause
# isc
# mit
npm-info() {
local usage="Usage: ${FUNCNAME} NAME [VERSION]"
local name="${1:?${usage}}"
local version="${2:-latest}"
local cachedir="${HOME}/.npm-json"
local cache=""
if [[ "${name}" =~ ^@ ]]; then
# TODO(jart): How do we fetch these weird scoped packages?
echo "{}"
return
fi
if [[ -e "${cachedir}/${name}-${version}.json" ]]; then
cat "${cachedir}/${name}-${version}.json"
else
mkdir -p "${cachedir}/tmp" || return 1
local tmp
tmp="$(mktemp ${cachedir}/tmp/${FUNCNAME}.XXXXXXXXXX)" || return 1
printf "\e[35mfetching https://registry.npmjs.org/%s/%s\e[0m\n" "${name}" "${version}" >&2
curl -s "https://registry.npmjs.org/${name}/${version}" \
| json-pretty >"${tmp}"
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
rm -f "${tmp}"
return 1
else
local real_version
real_version="$(json-get version <"${tmp}")" || return 1
cat "${tmp}"
mv -f "${tmp}" "${cachedir}/${name}-${real_version}.json"
if [[ "${version}" != "${real_version}" ]]; then
ln -sf "${name}-${real_version}.json" \
"${cachedir}/${name}-${version}.json"
fi
fi
fi
}
npm-transitive-deps() {
local usage="Usage: ${FUNCNAME} NAME [VERSION]"
local name="${1:?${usage}}"
local version="${2:-latest}"
version="$(npm-info "${name}" "${version}" | json-get version)" || return 1
(
echo "${name} ${version}"
while read iname iversion a b c; do
npm-transitive-deps "${iname}" "${iversion}${a}${b}${c}" &
done < <(npm-info "${name}" "${version}" \
| json-get dependencies \
| json-keyvalues)
wait
) | sort -u
}
npm-transitive-licenses() {
local usage="Usage: ${FUNCNAME} NAME [VERSION]"
local name="${1:?${usage}}"
local version="${2:-latest}"
(
while read name version; do
npm-info "${name}" "${version}" \
| json-get license \
| tr \ A-Z _a-z
done < <(npm-transitive-deps "${name}" "${version}")
) | sort -u
}
json-get() {
local usage="Usage: ${FUNCNAME} KEY"
local key="${1:?${usage}}"
python2 -c "
import json, sys
try:
blob = json.load(sys.stdin)
if blob and isinstance(blob, dict) and '${key}' in blob:
x = blob['${key}']
if isinstance(x, list) or isinstance(x, dict):
json.dump(x, sys.stdout)
print
else:
print x
except ValueError:
pass"
}
json-keys() {
python2 -c "
import json, sys
try:
blob = json.load(sys.stdin)
if blob and isinstance(blob, dict):
print '\n'.join(blob.keys())
except ValueError:
pass"
}
json-keyvalues() {
python2 -c "
import json, sys
try:
blob = json.load(sys.stdin)
if blob and isinstance(blob, dict):
for k, v in blob.items():
print k, v
except ValueError:
pass"
}
json-pretty() {
python2 -c '
import json, sys
try:
json.dump(json.load(sys.stdin), sys.stdout, indent=2)
print
except ValueError:
pass'
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment