Skip to content

Commit

Permalink
Merge pull request #5 from snyk/feat/parse-lockfile-recursively
Browse files Browse the repository at this point in the history
Parse lockfile recursively
  • Loading branch information
miiila committed Aug 9, 2018
2 parents cc90405 + c04aeee commit 5ae268f
Show file tree
Hide file tree
Showing 5 changed files with 5,012 additions and 66 deletions.
8 changes: 8 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env node
const inspect = require('../dist/index')

inspect.buildDepTreeFromFiles('./', 'package.json', 'package-lock.json')
.then((tree) => {
console.log(JSON.stringify(tree));
})
.catch(console.error);
129 changes: 70 additions & 59 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
import 'source-map-support/register';
import * as fs from 'fs';
import * as path from 'path';
import * as _ from 'lodash';

export default function parseLockFile(root, targetFilePath, lockFilePath, options) {
if (!root || !lockFilePath || !lockFilePath) {
throw new Error('Missing required parameters for parseLockFile()');
}
// TODO: validate only valid options were passed in

const targetFileFullPath = path.resolve(root, targetFilePath);
const lockFileFullPath = path.resolve(root, lockFilePath);

if (!fs.existsSync(targetFilePath)) {
throw new Error(`Target file package.json not found at location: ${targetFileFullPath}`);
}
if (!fs.existsSync(lockFilePath)) {
throw new Error(`LockFile package-lock.json not found at location: ${lockFileFullPath}`);
}

const targetFile = fs.readFileSync(targetFilePath);
const lockFile = fs.readFileSync(lockFilePath);

return buildDepTree(targetFile, lockFile, options);
}

function buildDepTree(targetFileRaw, lockFileRaw, options) {
export async function buildDepTree(targetFileRaw, lockFileRaw, options) {

const lockFile = JSON.parse(lockFileRaw);
const targetFile = JSON.parse(targetFileRaw);
Expand All @@ -40,46 +20,77 @@ function buildDepTree(targetFileRaw, lockFileRaw, options) {
name: targetFile.name || undefined,
version: targetFile.version || undefined,
};
const parentDepList = targetFile.dependencies;
const fullDepList = lockFile.dependencies;

const parentDepsMap = Object.keys(parentDepList).reduce((acc, depName) => {
const version = parentDepList[depName];
const name = `${depName}@${version}`;
acc[name] = {
name: depName,
version,
};
return acc;
}, {});

const depsMap = Object.keys(fullDepList).reduce((acc, dep) => {
const version = fullDepList[dep];
const name = `${dep}@${version}`;
acc[name] = dep;
return acc;
}, {});

for (const dep in depsMap) {
if (depsMap.hasOwnProperty(dep)) {
const subTree = buildSubTreeRecursive(dep, new Set(), depsMap);

if (subTree) {
depTree.dependencies[subTree.name] = subTree;
}
}
}

const topLevelDeps = Object.keys(targetFile.dependencies);

await Promise.all(topLevelDeps.map(async (dep) => {
depTree.dependencies[dep] = await buildSubTreeRecursive(dep, [], lockFile);
}));

return depTree;
}

function buildSubTreeRecursive(dep, ancestors, depsMap) {
const newAncestors = (new Set(ancestors)).add(dep);
// TODO
const tree = {
name: depsMap[dep].name,
version: depsMap[dep].version,
async function buildSubTreeRecursive(dep: string, depKeys: string[], lockFile: object) {

const depSubTree = {
dependencies: {},
name: dep,
version: undefined,
};

return tree;
// Get path to the nested dependencies from list ['package1', 'package2']
// to ['dependencies', 'package1', 'dependencies', 'package2', 'dependencies']
const depPath = getDepPath(depKeys);
// try to get list of deps on the path
const deps = _.get(lockFile, depPath);

// If exists and looked-up dep is there
if (deps && deps[dep]) {
// update the tree
depSubTree.version = deps[dep].version;
// repeat the process for dependencies of looked-up dep
const newDeps = deps[dep].requires ? Object.keys(deps[dep].requires) : [];
await Promise.all(newDeps.map(async (subDep) => {
depSubTree.dependencies[subDep] = await buildSubTreeRecursive(subDep, [...depKeys, dep], lockFile);
}));
return depSubTree;
} else {
// tree was walked to the root and dependency was not found
if (!depKeys.length) {
throw new Error(`Dependency ${dep} was not found in package-lock.json.
Your package.json and package-lock.json are probably out of sync.
Please run npm install and try to parse the log again.`);
}
// dependency was not found on a current path, remove last key (move closer to the root) and try again
return buildSubTreeRecursive(dep, depKeys.slice(0, -1), lockFile);
}
}

function getDepPath(depKeys: string[]) {
const depPath = depKeys.reduce((acc, key) => {
return acc.concat([key, 'dependencies']);
}, ['dependencies']);

return depPath;
}

export function buildDepTreeFromFiles(root, targetFilePath, lockFilePath, options) {
if (!root || !lockFilePath || !lockFilePath) {
throw new Error('Missing required parameters for parseLockFile()');
}

const targetFileFullPath = path.resolve(root, targetFilePath);
const lockFileFullPath = path.resolve(root, lockFilePath);

if (!fs.existsSync(targetFilePath)) {
throw new Error(`Target file package.json not found at location: ${targetFileFullPath}`);
}
if (!fs.existsSync(lockFilePath)) {
throw new Error(`LockFile package-lock.json not found at location: ${lockFileFullPath}`);
}

const targetFile = fs.readFileSync(targetFileFullPath);
const lockFile = fs.readFileSync(lockFileFullPath);

return buildDepTree(targetFile, lockFile, options);
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "snyk-nodejs-lockfile-parser",
"description": "Generate a dep tree given a lockfile",
"main": "dist/lib/index.js",
"bin": {
"parse-nodejs-lockfile": "./bin/index.js"
},
"scripts": {
"test": "npm run lint && npm run unit-test",
"unit-test": "tap test/lib -R=spec --timeout=300 --node-path ts-node --test-file-pattern '/\\.[tj]s$/'",
Expand All @@ -26,6 +29,7 @@
"@types/node": "10.5.5",
"@types/sinon": "5.0.1",
"sinon": "6.1.4",
"source-map-support": "^0.5.6",
"tap": "github:snyk/node-tap#alternative-runtimes",
"ts-node": "7.0.0",
"tslint": "5.11.0",
Expand Down
Loading

0 comments on commit 5ae268f

Please sign in to comment.