Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: yarn lock v2 support plus improvements #4222

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
JamesPatrickGill marked this conversation as resolved.
Show resolved Hide resolved
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(
JamesPatrickGill marked this conversation as resolved.
Show resolved Hide resolved
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',
);
},
},
};