diff --git a/src/lib/plugins/nodejs-plugin/index.ts b/src/lib/plugins/nodejs-plugin/index.ts index a713269c6f9..1d500d8280f 100644 --- a/src/lib/plugins/nodejs-plugin/index.ts +++ b/src/lib/plugins/nodejs-plugin/index.ts @@ -4,6 +4,8 @@ import * as types from '../types'; import * as analytics from '../../analytics'; import { MissingTargetFileError } from '../../errors/missing-targetfile-error'; import { MultiProjectResult } from '@snyk/cli-interface/legacy/plugin'; +import { DepGraph } from '@snyk/dep-graph'; +import { PkgTree } from 'snyk-nodejs-lockfile-parser'; export async function inspect( root: string, @@ -18,12 +20,29 @@ export async function inspect( targetFile.endsWith('yarn.lock'); const getLockFileDeps = isLockFileBased && !options.traverseNodeModules; - const depTree: any = getLockFileDeps + const depRes: PkgTree | DepGraph = getLockFileDeps ? await lockParser.parse(root, targetFile, options) : await modulesParser.parse(root, targetFile, options); - if (depTree?.meta?.lockfileVersion) { - analytics.add('lockfileVersion', depTree.meta.lockfileVersion); + const isDepGraph = isResDepGraph(depRes); + + let scannedProjects: any[] = []; + if (isDepGraph) { + if ((depRes as DepGraph)?.pkgManager.version) { + analytics.add( + 'lockfileVersion', + (depRes as DepGraph)?.pkgManager.version, + ); + } + scannedProjects = [{ depGraph: depRes }]; + } else { + if ((depRes as PkgTree)?.meta?.lockfileVersion) { + analytics.add( + 'lockfileVersion', + (depRes as PkgTree)?.meta?.lockfileVersion, + ); + } + scannedProjects = [{ depTree: depRes }]; } return { @@ -31,6 +50,10 @@ export async function inspect( name: 'snyk-nodejs-lockfile-parser', runtime: process.version, }, - scannedProjects: [{ depTree }], + scannedProjects, }; } + +function isResDepGraph(depRes: PkgTree | DepGraph): boolean { + return 'rootPkg' in depRes; +} diff --git a/src/lib/plugins/nodejs-plugin/npm-lock-parser.ts b/src/lib/plugins/nodejs-plugin/npm-lock-parser.ts index b4cf8713321..e70cb53b612 100644 --- a/src/lib/plugins/nodejs-plugin/npm-lock-parser.ts +++ b/src/lib/plugins/nodejs-plugin/npm-lock-parser.ts @@ -5,14 +5,17 @@ import { spinner } from '../../spinner'; import * as analytics from '../../analytics'; import * as fs from 'fs'; import * as lockFileParser from 'snyk-nodejs-lockfile-parser'; -import { PkgTree } from 'snyk-nodejs-lockfile-parser'; +import { PkgTree, InvalidUserInputError } from 'snyk-nodejs-lockfile-parser'; import { Options } from '../types'; +import { DepGraph } from '@snyk/dep-graph'; +import { NodeLockfileVersion } from 'snyk-nodejs-lockfile-parser/dist/utils'; +import { ProjectParseOptions } from 'snyk-nodejs-lockfile-parser/dist/dep-graph-builders/types'; export async function parse( root: string, targetFile: string, options: Options, -): Promise { +): Promise { const lockFileFullPath = path.resolve(root, targetFile); if (!fs.existsSync(lockFileFullPath)) { throw new Error( @@ -47,9 +50,29 @@ export async function parse( }); const resolveModuleSpinnerLabel = `Analyzing npm dependencies for ${lockFileFullPath}`; debug(resolveModuleSpinnerLabel); + + const strictOutOfSync = options.strictOutOfSync !== false; + const lockfileVersion = lockFileParser.getLockfileVersion(targetFile); + if ( + lockfileVersion === NodeLockfileVersion.YarnLockV1 || + lockfileVersion === NodeLockfileVersion.YarnLockV2 + ) { + return buildDepGraph( + root, + manifestFileFullPath, + lockFileFullPath, + lockfileVersion, + { + includeDevDeps: options.dev || false, + includeOptionalDeps: true, + strictOutOfSync, + pruneCycles: true, + }, + ); + } + try { await spinner(resolveModuleSpinnerLabel); - const strictOutOfSync = options.strictOutOfSync !== false; return lockFileParser.buildDepTreeFromFiles( root, manifestFileFullPath, @@ -61,3 +84,45 @@ export async function parse( await spinner.clear(resolveModuleSpinnerLabel)(); } } + +async function buildDepGraph( + root: string, + manifestFilePath: string, + lockfilePath: string, + lockfileVersion: NodeLockfileVersion, + options: ProjectParseOptions, +): Promise { + const manifestFileFullPath = path.resolve(root, manifestFilePath); + const lockFileFullPath = path.resolve(root, lockfilePath); + + if (!fs.existsSync(manifestFileFullPath)) { + throw new InvalidUserInputError( + 'Target file package.json not found at ' + + `location: ${manifestFileFullPath}`, + ); + } + if (!fs.existsSync(lockFileFullPath)) { + throw new InvalidUserInputError( + 'Lockfile not found at location: ' + lockFileFullPath, + ); + } + + const manifestFileContents = fs.readFileSync(manifestFileFullPath, 'utf-8'); + const lockFileContents = fs.readFileSync(lockFileFullPath, 'utf-8'); + + switch (lockfileVersion) { + case NodeLockfileVersion.YarnLockV1: + return await lockFileParser.parseYarnLockV1Project( + manifestFileContents, + lockFileContents, + options, + ); + case NodeLockfileVersion.YarnLockV2: + return lockFileParser.parseYarnLockV2Project( + manifestFileContents, + lockFileContents, + options, + ); + } + throw new Error('Failed to build dep graph from current project'); +}