snyk-nodejs-lockfile-parser
Version:
Generate a dep tree given a lockfile
188 lines • 8.67 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PnpmLockfileParser = void 0;
const semver_1 = require("semver");
const pathUtil = require("path");
const lodash_1 = require("lodash");
const debugModule = require("debug");
const debug = debugModule('snyk-pnpm-workspaces');
class PnpmLockfileParser {
constructor(rawPnpmLock, workspaceArgs) {
this.rawPnpmLock = rawPnpmLock;
this.lockFileVersion = rawPnpmLock.lockfileVersion;
this.workspaceArgs = workspaceArgs;
this.packages = rawPnpmLock.packages || {};
this.extractedPackages = {};
this.resolvedPackages = {};
this.importers = this.normaliseImporters(rawPnpmLock);
}
isWorkspaceLockfile() {
var _a;
return (_a = this.workspaceArgs) === null || _a === void 0 ? void 0 : _a.isWorkspace;
}
extractPackages() {
// Packages should be parsed only one time for a parser
if (Object.keys(this.extractedPackages).length > 0) {
return this.extractedPackages;
}
const packages = {};
Object.entries(this.packages).forEach(([depPath, versionData]) => {
// name and version are optional in version data - if they don't show up in version data, they can be deducted from the dependency path
const { name, version } = versionData;
let parsedPath = {};
if (!(version && name)) {
parsedPath = this.parseDepPath(depPath);
}
const pkg = {
id: depPath,
name: name || parsedPath.name,
version: version || parsedPath.version || depPath,
isDev: versionData.dev == 'true',
dependencies: versionData.dependencies || {},
devDependencies: versionData.devDependencies || {},
optionalDependencies: versionData.optionalDependencies || {},
};
packages[`${pkg.name}@${pkg.version}`] = pkg;
this.resolvedPackages[depPath] = `${pkg.name}@${pkg.version}`;
});
return packages;
}
extractTopLevelDependencies(options, importer) {
var _a, _b;
let root = this.rawPnpmLock;
if (importer) {
const { name, version } = (_a = this.workspaceArgs) === null || _a === void 0 ? void 0 : _a.projectsVersionMap[importer];
if (
// Return early because dependencies were already normalized for this importer
// as part of another's importer dependency and stored in extractedPackages
this.extractedPackages[`${name}@${version}`] &&
!(0, lodash_1.isEmpty)(this.extractedPackages[`${name}@${version}`].dependencies)) {
return this.normalizedPkgToTopLevel(this.extractedPackages[`${name}@${version}`]);
}
root = this.rawPnpmLock.importers[importer];
}
const prodDeps = this.normalizeTopLevelDeps(root.dependencies || {}, false, importer);
const devDeps = options.includeDevDeps
? this.normalizeTopLevelDeps(root.devDependencies || {}, true, importer)
: {};
const optionalDeps = options.includeOptionalDeps
? this.normalizeTopLevelDeps(root.optionalDependencies || {}, false, importer)
: {};
const peerDeps = options.includePeerDeps
? this.normalizeTopLevelDeps(root.peerDependencies || {}, false, importer)
: {};
if (importer) {
const { name, version } = (_b = this.workspaceArgs) === null || _b === void 0 ? void 0 : _b.projectsVersionMap[importer];
this.extractedPackages[`${name}@${version}`] = {
id: `${name}@${version}`,
name: version,
version: version,
dependencies: this.topLevelDepsToNormalizedPkgs(prodDeps),
devDependencies: this.topLevelDepsToNormalizedPkgs(devDeps),
optionalDependencies: this.topLevelDepsToNormalizedPkgs(optionalDeps),
isDev: false,
};
}
return Object.assign(Object.assign(Object.assign(Object.assign({}, prodDeps), devDeps), optionalDeps), peerDeps);
}
normalizedPkgToTopLevel(pkg) {
const topLevel = {};
Object.keys(pkg.dependencies).forEach((depName) => (topLevel[depName] = {
name: depName,
version: pkg.dependencies[depName],
isDev: false,
}));
Object.keys(pkg.devDependencies).forEach((depName) => (topLevel[depName] = {
name: depName,
version: pkg.devDependencies[depName],
isDev: true,
}));
return topLevel;
}
topLevelDepsToNormalizedPkgs(deps) {
const normalizedPkgs = {};
Object.values(deps).forEach((dep) => (normalizedPkgs[dep.name] = dep.version));
return normalizedPkgs;
}
normalizeVersion(name, version, isDev, importerName) {
if (this.isWorkspaceLockfile()) {
version = this.resolveWorkspacesCrossReference(name, version, isDev, importerName);
}
if (!(0, semver_1.valid)(version)) {
version = this.excludeTransPeerDepsVersions(version);
if (!(0, semver_1.valid)(version)) {
// for npm and git ref packages
// they show up in packages with keys equal to the version in top level deps
// e.g. body-parser with version github.com/expressjs/body-parser/263f602e6ae34add6332c1eb4caa808893b0b711
if (this.packages[version]) {
return this.packages[version].version || version;
}
if (this.packages[`${name}@${version}`]) {
return this.packages[`${name}@${version}`].version || version;
}
}
}
return version;
}
resolveWorkspacesCrossReference(name, version, isDev, importerName) {
if (!this.workspaceArgs) {
return version;
}
if (version.startsWith('link:')) {
// In workspaces example:
// package-b:
// specifier: 1.0.0
// version: link:../pkg-b
const depPath = version.split('link:')[1];
const resolvedPathDep = pathUtil
.join(importerName || '.', depPath)
.replace(/\\/g, '/');
// cross referenced package, we add it to the extracted packages
const mappedProjInfo = this.workspaceArgs.projectsVersionMap[resolvedPathDep];
if (!mappedProjInfo) {
debug(`Importer ${resolvedPathDep} discovered as a dependency of ${importerName} was not found in the lockfile`);
version = 'undefined';
}
else {
version = mappedProjInfo.version;
}
if (!version) {
version = 'undefined';
}
// Stop recursion here if this package was already normalized and stored in extractedPackages
if (this.extractedPackages[`${name}@${version}`]) {
return version;
}
// Initialize this package before recursive calls to avoid inifinte recursion in cycles
// We can avoid keeping a visited arrat this way
this.extractedPackages[`${name}@${version}`] = {
name,
version,
id: `${name}@${version}`,
isDev,
dependencies: {},
devDependencies: {},
};
const subDeps = this.rawPnpmLock.importers[resolvedPathDep] || {
dependencies: {},
devDependencies: {},
optionalDependencies: {},
};
const resolvedDeps = this.normalizePackagesDeps(subDeps.dependencies || {}, isDev, resolvedPathDep);
const resolvedDevDeps = this.normalizePackagesDeps(subDeps.devDependencies || {}, true, resolvedPathDep);
const resolvedOptionalDeps = this.normalizePackagesDeps(subDeps.optionalDependencies || {}, true, resolvedPathDep);
this.extractedPackages[`${name}@${version}`] = {
name,
version,
id: `${name}@${version}`,
isDev,
dependencies: resolvedDeps,
devDependencies: resolvedDevDeps,
optionalDependencies: resolvedOptionalDeps,
};
}
return version;
}
}
exports.PnpmLockfileParser = PnpmLockfileParser;
//# sourceMappingURL=lockfile-parser.js.map