Skip to content

Commit

Permalink
Merge pull request #4222 from snyk/feat/yarn-lock-2-support-and-depgr…
Browse files Browse the repository at this point in the history
…aph-migrations

feat: yarn lock v2 support plus improvements
  • Loading branch information
JamesPatrickGill committed Dec 20, 2022
2 parents 6846be1 + 798abf1 commit 7800441
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 19 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -118,7 +118,7 @@
"snyk-gradle-plugin": "3.24.6",
"snyk-module": "3.1.0",
"snyk-mvn-plugin": "2.32.0",
"snyk-nodejs-lockfile-parser": "1.44.0",
"snyk-nodejs-lockfile-parser": "1.45.1",
"snyk-nuget-plugin": "1.23.5",
"snyk-php-plugin": "1.9.2",
"snyk-policy": "^1.25.0",
Expand Down
23 changes: 19 additions & 4 deletions src/lib/plugins/nodejs-plugin/index.ts
Expand Up @@ -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,
Expand All @@ -18,19 +20,32 @@ 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);
let scannedProjects: any[] = [];
if (isResDepGraph(depRes)) {
if (depRes.pkgManager.version) {
analytics.add('lockfileVersion', depRes.pkgManager.version);
}
scannedProjects = [{ depGraph: depRes }];
} else {
if (depRes.meta?.lockfileVersion) {
analytics.add('lockfileVersion', depRes.meta.lockfileVersion);
}
scannedProjects = [{ depTree: depRes }];
}

return {
plugin: {
name: 'snyk-nodejs-lockfile-parser',
runtime: process.version,
},
scannedProjects: [{ depTree }],
scannedProjects,
};
}

function isResDepGraph(depRes: PkgTree | DepGraph): depRes is DepGraph {
return 'rootPkg' in depRes;
}
76 changes: 73 additions & 3 deletions src/lib/plugins/nodejs-plugin/npm-lock-parser.ts
Expand Up @@ -5,14 +5,20 @@ 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 {
NodeLockfileVersion,
PkgTree,
InvalidUserInputError,
ProjectParseOptions,
} from 'snyk-nodejs-lockfile-parser';
import { Options } from '../types';
import { DepGraph } from '@snyk/dep-graph';

export async function parse(
root: string,
targetFile: string,
options: Options,
): Promise<PkgTree> {
): Promise<PkgTree | DepGraph> {
const lockFileFullPath = path.resolve(root, targetFile);
if (!fs.existsSync(lockFileFullPath)) {
throw new Error(
Expand Down Expand Up @@ -47,9 +53,31 @@ export async function parse(
});
const resolveModuleSpinnerLabel = `Analyzing npm dependencies for ${lockFileFullPath}`;
debug(resolveModuleSpinnerLabel);

const strictOutOfSync = options.strictOutOfSync !== false;
const lockfileVersion = lockFileParser.getLockfileVersionFromFile(
lockFileFullPath,
);
if (
lockfileVersion === NodeLockfileVersion.YarnLockV1 ||
lockfileVersion === NodeLockfileVersion.YarnLockV2
) {
return await 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,
Expand All @@ -61,3 +89,45 @@ export async function parse(
await spinner.clear<void>(resolveModuleSpinnerLabel)();
}
}

async function buildDepGraph(
root: string,
manifestFilePath: string,
lockfilePath: string,
lockfileVersion: NodeLockfileVersion,
options: ProjectParseOptions,
): Promise<DepGraph> {
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');
}
8 changes: 8 additions & 0 deletions test/acceptance/workspaces/yarn-lock-v2-vuln/package.json
@@ -0,0 +1,8 @@
{
"name": "yarn-3-vuln",
"packageManager": "yarn@3.3.0",
"version": "1.0.0",
"dependencies": {
"lodash": "4.17.0"
}
}
21 changes: 21 additions & 0 deletions test/acceptance/workspaces/yarn-lock-v2-vuln/yarn.lock
@@ -0,0 +1,21 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6
cacheKey: 8

"lodash@npm:4.17.0":
version: 4.17.0
resolution: "lodash@npm:4.17.0"
checksum: 063b9c6f6057d616033b3d201a09982bb849d38decffc2067ce1121917761d910d376c85f12a1c7b5367a16f76fa0e9f3ea9b3edb3bc34d0ad5de1eec8fe25c4
languageName: node
linkType: hard

"yarn-3-vuln@workspace:.":
version: 0.0.0-use.local
resolution: "yarn-3-vuln@workspace:."
dependencies:
lodash: 4.17.0
languageName: unknown
linkType: soft
4 changes: 2 additions & 2 deletions test/jest/acceptance/snyk-test/all-projects.spec.ts
Expand Up @@ -56,7 +56,7 @@ describe('snyk test --all-projects (mocked server only)', () => {
'✗ 1/3 potential projects failed to get dependencies',
);
expect(stderr).toMatch(
`Dependency snyk was not found in yarn.lock. Your package.json and yarn.lock are probably out of sync. Please run "yarn install" and try again.`,
`Dependency snyk@1.320.0 was not found in yarn.lock. Your package.json and yarn.lock are probably out of sync. Please run "yarn install" and try again.`,
);
});

Expand All @@ -81,7 +81,7 @@ describe('snyk test --all-projects (mocked server only)', () => {
'✗ 1/3 potential projects failed to get dependencies',
);
expect(stderr).toMatch(
`Dependency snyk was not found in yarn.lock. Your package.json and yarn.lock are probably out of sync. Please run "yarn install" and try again.`,
`Dependency snyk@1.320.0 was not found in yarn.lock. Your package.json and yarn.lock are probably out of sync. Please run "yarn install" and try again.`,
);
});

Expand Down
2 changes: 1 addition & 1 deletion test/tap/cli-monitor/cli-monitor.all-projects.spec.ts
Expand Up @@ -190,7 +190,7 @@ export const AllProjectsTests: AcceptanceTests = {
);
t.match(
result,
'Dependency snyk was not found in yarn.lock',
'Dependency snyk@* was not found in yarn.lock',
'yarn project had an error and we displayed it',
);

Expand Down
25 changes: 24 additions & 1 deletion test/tap/cli-test/cli-test.yarn.spec.ts
Expand Up @@ -15,7 +15,7 @@ export const YarnTests: AcceptanceTests = {
t.equal(
e.message,
'\nTesting yarn-out-of-sync...\n\n' +
'Dependency snyk was not found in yarn.lock.' +
'Dependency snyk@* was not found in yarn.lock.' +
' Your package.json and yarn.lock are probably out of sync.' +
' Please run "yarn install" and try again.',
'Contains enough info about err',
Expand Down Expand Up @@ -335,5 +335,28 @@ export const YarnTests: AcceptanceTests = {
'depGraph looks fine',
);
},

'`test` on a yarn lock v2 package - uses yarn v3': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces('yarn-lock-v2-vuln');
await params.cli.test();
const req = params.server.popRequest();
t.equal(req.method, 'POST', 'makes POST request');
t.equal(
req.headers['x-snyk-cli-version'],
params.versionNumber,
'sends version number',
);
t.match(req.url, '/test-dep-graph', 'posts to correct url');
t.match(req.body.targetFile, undefined, 'target is undefined');
const depGraph = req.body.depGraph;
t.same(
depGraph.pkgs.map((p) => p.id).sort(),
['yarn-3-vuln@1.0.0', 'lodash@4.17.0'].sort(),
'depGraph looks fine',
);
},
},
};

0 comments on commit 7800441

Please sign in to comment.