Last active
October 11, 2016 19:38
-
-
Save jart/2c42b0e8b07503622df1a3ba32320267 to your computer and use it in GitHub Desktop.
NPM Transitive Deps Calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) | |
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) | |
except ValueError: | |
pass' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment