snyk-resolve-deps
Version:
Resolves a node package tree with combined support for both npm@2 and npm@3.
238 lines • 11.1 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const depTypes = require("./dep-types");
const fs = require("then-fs");
const lodash_1 = require("lodash");
const lodash_2 = require("lodash");
const lodash_3 = require("lodash");
const lodash_4 = require("lodash");
const lodash_5 = require("lodash");
const debugModule = require("debug");
const path = require("path");
const semver = require("semver");
const resolve = require("snyk-resolve");
const try_require_1 = require("./try-require");
const debug = debugModule('snyk:resolve:deps');
function applyExtraFields(src, dest, extraFields) {
if (!extraFields || !extraFields.length) {
return;
}
extraFields.forEach(function applyExtraField(field) {
(0, lodash_2.set)(dest, field, (0, lodash_1.get)(src, field) || null);
});
}
// FIXME: only supports dependencies & dev deps not opt-deps
function loadModules(root, depType, options) {
return __awaiter(this, void 0, void 0, function* () {
try_require_1.cache.reset(); // reset the package cache on re-run
let opt = (0, lodash_3.clone)(options || {});
let pkgRoot = root;
if (opt.file) {
const pathInfo = path.parse(opt.file);
pkgRoot = path.resolve(pkgRoot, pathInfo.dir);
opt.file = pathInfo.base;
}
const tree = yield loadModulesInternal(pkgRoot, depType || null, null, opt);
// ensure there's no missing packages our known root deps
let missing = [];
if (tree.__dependencies) {
Object.keys(tree.__dependencies).forEach(function (name) {
if (!tree.dependencies[name]) {
missing.push(resolve(name, pkgRoot).then(function (dir) {
return loadModulesInternal(dir, depTypes.PROD, {
__from: [tree.name + '@' + tree.version, name],
});
}).catch(function (e) {
if (e.code === 'NO_PACKAGE_FOUND') {
return false;
}
}));
}
});
}
if (missing.length) {
return Promise.all(missing).then(function (packages) {
packages.filter(Boolean).forEach(function (pkg) {
pkg.dep = tree.__dependencies[pkg.name];
tree.dependencies[pkg.name] = pkg;
});
return tree;
});
}
return tree;
});
}
function loadModulesInternal(root, rootDepType, parent, options) {
options = options || {};
if (!rootDepType) {
rootDepType = depTypes.EXTRANEOUS;
}
if (typeof root !== 'string') {
return Promise.reject(new Error('module path must be a string'));
}
let modules = {};
let dir = path.resolve(root, options.file || 'package.json');
// 1. read package.json for written deps
let promise = (0, try_require_1.tryRequirePackageJson)(dir).then(function (pkg) {
// if there's a package found, collect this information too
if (pkg) {
let full = pkg.name + '@' + (pkg.version || '0.0.0');
modules = {};
applyExtraFields(pkg, modules, options.extraFields);
(0, lodash_4.assign)(modules, {
name: pkg.name,
version: pkg.version || null,
license: pkg.license || 'none',
depType: rootDepType,
hasDevDependencies: !!pkg.devDependencies,
full: full,
__from: (parent || { __from: [] }).__from,
__devDependencies: pkg.devDependencies,
__dependencies: pkg.dependencies,
__optionalDependencies: pkg.optionalDependencies,
__bundleDependencies: pkg.bundleDependencies,
__filename: pkg.__filename,
});
// allows us to add to work out the full path that the package was
// introduced via
pkg.__from = modules.__from.concat(full);
pkg.full = modules.full;
// flag and track where a shrinkwrapped package comes from
if (!pkg.shrinkwrap && parent && parent.shrinkwrap) {
pkg.shrinkwrap = parent.shrinkwrap;
}
else if (pkg.shrinkwrap) {
pkg.shrinkwrap = pkg.full;
}
// this is a special case for the root package to get a consistent
// __from path, so that the complete path (including it's own pkg name)
if (modules.__from.length === 0) {
modules.__from.push(full);
}
}
else {
throw new Error(dir + ' is not a node project');
}
modules.dependencies = {};
// 2. check actual installed deps
return fs.readdir(path.resolve(root, 'node_modules')).then(function (dirs) {
let res = dirs.map(function (directory) {
// completely ignore `.bin` npm helper dir
// ~ can be a symlink to node_modules itself
// (https://www.npmjs.com/package/link-tilde)
if (['.bin', '.DS_Store', '~'].indexOf(directory) >= 0) {
return null;
}
// this is a scoped namespace, and we'd expect to find directories
// inside *this* `dir`, so treat differently
if (directory.indexOf('@') === 0) {
debug('scoped reset on %s', directory);
directory = path.resolve(root, 'node_modules', directory);
return fs.readdir(directory).then(function (directories) {
return Promise.all(directories.map(function (scopedDir) {
return (0, try_require_1.tryRequirePackageJson)(path.resolve(directory, scopedDir, 'package.json'));
}));
});
}
// otherwise try to load a package.json from this node_module dir
directory = path.resolve(root, 'node_modules', directory);
return fs.realpath(directory).then(function (realDirectory) {
if (realDirectory === root) {
return null;
}
else {
return (0, try_require_1.tryRequirePackageJson)(path.resolve(directory, 'package.json'));
}
});
});
return Promise.all(res).then(function (response) {
response = (0, lodash_5.flatten)(response).filter(Boolean);
// if res.length === 0 we used to throw MISSING_NODE_MODULES but we're
// not doing that now, and I think it's okay.
// TODO: convert reduces to more readable code throughout
response.reduce(function (acc, curr) {
let license;
let licenses = curr.license || curr.licenses;
if (Array.isArray(licenses)) {
license = licenses.reduce(function (accumulator, current) {
accumulator.push((current || {}).type || current);
return accumulator;
}, []).join('/');
}
else {
license = (licenses || {}).type || licenses;
}
let depInfo = depTypes(curr.name, pkg);
let depType = depInfo.type || rootDepType;
let depFrom = depInfo.from;
let valid = false;
if (depFrom) {
valid = semver.satisfies(curr.version, depFrom);
}
let full = curr.name + '@' + (curr.version || '0.0.0');
acc[curr.name] = {};
applyExtraFields(curr, acc[curr.name], options.extraFields);
(0, lodash_4.assign)(acc[curr.name], {
name: curr.name,
version: curr.version || null,
full: full,
valid: valid,
depType: depType,
snyk: curr.snyk,
license: license || 'none',
dep: depFrom || null,
__from: pkg.__from.concat(full),
__devDependencies: curr.devDependencies,
__dependencies: curr.dependencies,
__optionalDependencies: curr.optionalDependencies,
__bundleDependencies: curr.bundleDependencies,
__filename: curr.__filename,
});
if (depInfo.bundled) {
acc[curr.name].bundled = acc[curr.name].__from.slice(0);
}
if (pkg.shrinkwrap) {
acc[curr.name].shrinkwrap = pkg.shrinkwrap;
}
return acc;
}, modules.dependencies);
return modules;
});
}).then(function (mods) {
let deps = Object.keys(mods.dependencies);
let promises = deps.map(function (dep) {
let depType = mods.dependencies[dep].depType;
let directory = path.dirname(mods.dependencies[dep].__filename);
return loadModulesInternal(directory, depType, pkg);
});
return Promise.all(promises).then(function (res) {
res.forEach(function (mod) {
mods.dependencies[mod.name].dependencies = mod.dependencies;
});
return mods;
});
}).catch(function (error) {
// TODO decide whether it's okay that we keep throwing errors
// will this process get faster without it? (possibly...)
/* istanbul ignore else */
if (error.code === 'ENOENT') {
// there's no node_modules directory, that's fine, there's no deps
modules.dependencies = {};
return modules;
}
/* istanbul ignore next */
throw error;
});
});
return promise;
}
module.exports = loadModules;
//# sourceMappingURL=deps.js.map
;