Skip to content

Commit dc1be93

Browse files
authored
feat: [OSM-1039] move nodejs plugin functionality (#3) (#2)
1 parent 7dc8c90 commit dc1be93

84 files changed

Lines changed: 2739 additions & 135 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.releaserc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"branches": [
3+
"main"
4+
]
5+
}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Snyk helps you find, fix and monitor for known vulnerabilities in your dependenc
1111

1212
## Snyk Node.js Plugin
1313

14-
This plugin provides dependency metadata for npm and Yarn projects and workspaces. It is an internal component intended for use by our [CLI tool](https://github.com/snyk/snyk).
14+
This plugin provides dependency metadata for npm, Yarn and pnpm projects and workspaces. It is an internal component intended for use by our [CLI tool](https://github.com/snyk/snyk).
1515

1616
# Support
1717

@@ -33,6 +33,7 @@ This plugin provides dependency metadata for npm and Yarn projects and workspace
3333
| ----------- | --------- |
3434
| npm ||
3535
| Yarn ||
36+
| Pnpm ||
3637

3738
## Supported Node versions
3839

lib/index.ts

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,12 @@
1-
import * as modulesParser from './npm-modules-parser';
2-
import * as lockParser from './npm-lock-parser';
3-
import * as types from './types';
4-
import { MissingTargetFileError } from './errors';
5-
import { MultiProjectResult } from '@snyk/cli-interface/legacy/plugin';
6-
import { DepGraph } from '@snyk/dep-graph';
7-
import { PkgTree } from 'snyk-nodejs-lockfile-parser';
8-
9-
function isResDepGraph(depRes: PkgTree | DepGraph): depRes is DepGraph {
10-
return 'rootPkg' in depRes;
11-
}
12-
13-
export async function inspect(
14-
root: string,
15-
targetFile: string,
16-
options: types.Options = {},
17-
): Promise<MultiProjectResult> {
18-
if (!targetFile) {
19-
throw MissingTargetFileError(root);
20-
}
21-
const isLockFileBased =
22-
targetFile.endsWith('package-lock.json') ||
23-
targetFile.endsWith('yarn.lock');
24-
25-
const getLockFileDeps = isLockFileBased && !options.traverseNodeModules;
26-
const depRes: PkgTree | DepGraph = getLockFileDeps
27-
? await lockParser.parse(root, targetFile, options)
28-
: await modulesParser.parse(root, targetFile, options);
29-
30-
let scannedProjects: any[] = [];
31-
if (isResDepGraph(depRes)) {
32-
scannedProjects = [{ depGraph: depRes }];
33-
} else {
34-
scannedProjects = [{ depTree: depRes }];
35-
}
36-
37-
return {
38-
plugin: {
39-
name: 'snyk-nodejs-lockfile-parser',
40-
runtime: process.version,
41-
},
42-
scannedProjects,
43-
};
44-
}
1+
import { inspect } from './inspect';
2+
import {
3+
processNpmWorkspaces,
4+
processPnpmWorkspaces,
5+
processYarnWorkspaces,
6+
} from './workspaces';
7+
export {
8+
inspect,
9+
processNpmWorkspaces,
10+
processPnpmWorkspaces,
11+
processYarnWorkspaces,
12+
};

lib/inspect.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as modulesParser from './npm-modules-parser';
2+
import * as lockParser from './lock-parser';
3+
import * as types from './types';
4+
import { MissingTargetFileError } from './errors';
5+
import { MultiProjectResult } from '@snyk/cli-interface/legacy/plugin';
6+
import { DepGraph } from '@snyk/dep-graph';
7+
import { PkgTree } from 'snyk-nodejs-lockfile-parser';
8+
import { isResDepGraph } from './utils';
9+
10+
export async function inspect(
11+
root: string,
12+
targetFile: string,
13+
options: types.Options = {},
14+
): Promise<MultiProjectResult> {
15+
if (!targetFile) {
16+
throw MissingTargetFileError(root);
17+
}
18+
const isLockFileBased =
19+
targetFile.endsWith('package-lock.json') ||
20+
targetFile.endsWith('yarn.lock') ||
21+
targetFile.endsWith('pnpm-lock.yaml');
22+
23+
const getLockFileDeps = isLockFileBased && !options.traverseNodeModules;
24+
const depRes: PkgTree | DepGraph = getLockFileDeps
25+
? await lockParser.parse(root, targetFile, options)
26+
: await modulesParser.parse(root, targetFile, options);
27+
28+
let scannedProjects: any[] = [];
29+
if (isResDepGraph(depRes)) {
30+
scannedProjects = [{ depGraph: depRes }];
31+
} else {
32+
scannedProjects = [{ depTree: depRes }];
33+
}
34+
35+
return {
36+
plugin: {
37+
name: 'snyk-nodejs-lockfile-parser',
38+
runtime: process.version,
39+
},
40+
scannedProjects,
41+
};
42+
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import * as baseDebug from 'debug';
2-
const debug = baseDebug('snyk-test');
31
import * as path from 'path';
42
import * as fs from 'fs';
53
import * as lockFileParser from 'snyk-nodejs-lockfile-parser';
64
import {
75
NodeLockfileVersion,
8-
PkgTree,
96
InvalidUserInputError,
107
ProjectParseOptions,
118
} from 'snyk-nodejs-lockfile-parser';
12-
import { Options } from './types';
139
import { DepGraph } from '@snyk/dep-graph';
1410

15-
async function buildDepGraph(
11+
export async function buildDepGraph(
1612
root: string,
1713
manifestFilePath: string,
1814
lockfilePath: string,
@@ -38,6 +34,19 @@ async function buildDepGraph(
3834
const lockFileContents = fs.readFileSync(lockFileFullPath, 'utf-8');
3935

4036
switch (lockfileVersion) {
37+
case NodeLockfileVersion.PnpmLockV5:
38+
case NodeLockfileVersion.PnpmLockV6:
39+
return await lockFileParser.parsePnpmProject(
40+
manifestFileContents,
41+
lockFileContents,
42+
{
43+
includeDevDeps: options.includeDevDeps,
44+
includeOptionalDeps: options.includeOptionalDeps,
45+
pruneWithinTopLevelDeps: true,
46+
strictOutOfSync: options.strictOutOfSync,
47+
},
48+
lockfileVersion,
49+
);
4150
case NodeLockfileVersion.YarnLockV1:
4251
return await lockFileParser.parseYarnLockV1Project(
4352
manifestFileContents,
@@ -72,70 +81,3 @@ async function buildDepGraph(
7281
throw new Error('Failed to build dep graph from current project');
7382
}
7483
}
75-
76-
export async function parse(
77-
root: string,
78-
targetFile: string,
79-
options: Options,
80-
): Promise<PkgTree | DepGraph> {
81-
const lockFileFullPath = path.resolve(root, targetFile);
82-
if (!fs.existsSync(lockFileFullPath)) {
83-
throw new Error(
84-
'Lockfile ' + targetFile + ' not found at location: ' + lockFileFullPath,
85-
);
86-
}
87-
88-
const fullPath = path.parse(lockFileFullPath);
89-
const manifestFileFullPath = path.resolve(fullPath.dir, 'package.json');
90-
const shrinkwrapFullPath = path.resolve(fullPath.dir, 'npm-shrinkwrap.json');
91-
92-
if (!fs.existsSync(manifestFileFullPath)) {
93-
throw new Error(
94-
`Could not find package.json at ${manifestFileFullPath} ` +
95-
`(lockfile found at ${targetFile})`,
96-
);
97-
}
98-
99-
if (fs.existsSync(shrinkwrapFullPath)) {
100-
throw new Error(
101-
'Both `npm-shrinkwrap.json` and `package-lock.json` were found in ' +
102-
fullPath.dir +
103-
'.\n' +
104-
'Please run your command again specifying `--file=package.json` flag.',
105-
);
106-
}
107-
108-
const resolveModuleSpinnerLabel = `Analyzing npm dependencies for ${lockFileFullPath}`;
109-
debug(resolveModuleSpinnerLabel);
110-
111-
const strictOutOfSync = options.strictOutOfSync !== false;
112-
const lockfileVersion =
113-
lockFileParser.getLockfileVersionFromFile(lockFileFullPath);
114-
if (
115-
lockfileVersion === NodeLockfileVersion.YarnLockV1 ||
116-
lockfileVersion === NodeLockfileVersion.YarnLockV2 ||
117-
lockfileVersion === NodeLockfileVersion.NpmLockV2 ||
118-
lockfileVersion === NodeLockfileVersion.NpmLockV3
119-
) {
120-
return await buildDepGraph(
121-
root,
122-
manifestFileFullPath,
123-
lockFileFullPath,
124-
lockfileVersion,
125-
{
126-
includeDevDeps: options.dev || false,
127-
includeOptionalDeps: true,
128-
strictOutOfSync,
129-
pruneCycles: true,
130-
},
131-
);
132-
}
133-
134-
return lockFileParser.buildDepTreeFromFiles(
135-
root,
136-
manifestFileFullPath,
137-
lockFileFullPath,
138-
options.dev,
139-
strictOutOfSync,
140-
);
141-
}

lib/lock-parser/index.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs';
3+
import { buildDepGraph } from './build-dep-graph';
4+
import * as lockFileParser from 'snyk-nodejs-lockfile-parser';
5+
import { NodeLockfileVersion, PkgTree } from 'snyk-nodejs-lockfile-parser';
6+
import { Options } from '../types';
7+
import { DepGraph } from '@snyk/dep-graph';
8+
9+
export async function parse(
10+
root: string,
11+
targetFile: string,
12+
options: Options,
13+
): Promise<PkgTree | DepGraph> {
14+
const lockFileFullPath = path.resolve(root, targetFile);
15+
if (!fs.existsSync(lockFileFullPath)) {
16+
throw new Error(
17+
'Lockfile ' + targetFile + ' not found at location: ' + lockFileFullPath,
18+
);
19+
}
20+
21+
const fullPath = path.parse(lockFileFullPath);
22+
const manifestFileFullPath = path.resolve(fullPath.dir, 'package.json');
23+
const shrinkwrapFullPath = path.resolve(fullPath.dir, 'npm-shrinkwrap.json');
24+
25+
if (!fs.existsSync(manifestFileFullPath)) {
26+
throw new Error(
27+
`Could not find package.json at ${manifestFileFullPath} ` +
28+
`(lockfile found at ${targetFile})`,
29+
);
30+
}
31+
32+
if (fs.existsSync(shrinkwrapFullPath)) {
33+
throw new Error(
34+
'Both `npm-shrinkwrap.json` and `package-lock.json` were found in ' +
35+
fullPath.dir +
36+
'.\n' +
37+
'Please run your command again specifying `--file=package.json` flag.',
38+
);
39+
}
40+
41+
const strictOutOfSync = options.strictOutOfSync !== false;
42+
const lockfileVersion =
43+
lockFileParser.getLockfileVersionFromFile(lockFileFullPath);
44+
if (
45+
lockfileVersion === NodeLockfileVersion.YarnLockV1 ||
46+
lockfileVersion === NodeLockfileVersion.YarnLockV2 ||
47+
lockfileVersion === NodeLockfileVersion.NpmLockV2 ||
48+
lockfileVersion === NodeLockfileVersion.NpmLockV3 ||
49+
lockfileVersion === NodeLockfileVersion.PnpmLockV5 ||
50+
lockfileVersion === NodeLockfileVersion.PnpmLockV6
51+
) {
52+
return await buildDepGraph(
53+
root,
54+
manifestFileFullPath,
55+
lockFileFullPath,
56+
lockfileVersion,
57+
{
58+
includeDevDeps: options.dev || false,
59+
includeOptionalDeps: true,
60+
strictOutOfSync,
61+
pruneCycles: true,
62+
},
63+
);
64+
}
65+
66+
return lockFileParser.buildDepTreeFromFiles(
67+
root,
68+
manifestFileFullPath,
69+
lockFileFullPath,
70+
options.dev,
71+
strictOutOfSync,
72+
);
73+
}

lib/npm-modules-parser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export async function parse(
1919
options.file && options.file.replace('yarn.lock', 'package.json');
2020
}
2121

22+
if (targetFile.endsWith('pnpm-lock.yaml')) {
23+
options.file =
24+
options.file && options.file.replace('pnpm-lock.yaml', 'package.json');
25+
}
26+
2227
// package-lock.json falls back to package.json (used in wizard code)
2328
if (targetFile.endsWith('package-lock.json')) {
2429
options.file =
@@ -64,7 +69,6 @@ export async function parse(
6469
`without dependencies.\nPlease run '${packageManager} install' first.`,
6570
);
6671
}
67-
6872
return resolveNodeDeps(
6973
root,
7074
Object.assign({}, options, { noFromArrays: true }),

lib/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,9 @@ export interface MultiProjectResultCustom
3737
scannedProjects: ScannedProjectCustom[];
3838
failedResults?: FailedProjectScanError[];
3939
}
40+
41+
export interface PnpmWorkspacesMap {
42+
[packageJsonName: string]: {
43+
workspaces: string[];
44+
};
45+
}

lib/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3+
import { DepGraph } from '@snyk/dep-graph';
4+
import { PkgTree } from 'snyk-nodejs-lockfile-parser';
35

46
export function getFileContents(
57
root: string,
@@ -25,3 +27,23 @@ export function fileExists(root: string, fileName: string): boolean {
2527
const fullPath = path.resolve(root, fileName);
2628
return fs.existsSync(fullPath);
2729
}
30+
31+
export function isResDepGraph(depRes: PkgTree | DepGraph): depRes is DepGraph {
32+
return 'rootPkg' in depRes;
33+
}
34+
35+
export function normalizeFilePath(filePath: string): string {
36+
return path.normalize(filePath).replace(/\\/g, '/');
37+
}
38+
39+
export function isSubpath(subpath: string, parentPath: string): boolean {
40+
// Normalize both paths (ensure consistent separators)
41+
const normalizedSubpath = normalizeFilePath(subpath);
42+
const normalizedParentPath = normalizeFilePath(parentPath);
43+
44+
// Ensure subpath starts with parent path
45+
return (
46+
normalizedSubpath == normalizedParentPath ||
47+
normalizedSubpath.startsWith(`${normalizedParentPath}/`)
48+
);
49+
}

lib/workspaces/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { processNpmWorkspaces } from './npm-workspaces-parser';
2+
import { processPnpmWorkspaces } from './pnpm-workspaces-parser';
3+
import { processYarnWorkspaces } from './yarn-workspaces-parser';
4+
export { processNpmWorkspaces, processPnpmWorkspaces, processYarnWorkspaces };

0 commit comments

Comments
 (0)