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: [OSM-1122] use workspaces parser #16

Merged
merged 1 commit into from
Jul 25, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 22 additions & 130 deletions lib/workspaces/pnpm-workspaces-parser.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,12 @@
import * as debugModule from 'debug';
import * as pathUtil from 'path';
import * as fs from 'fs';

const debug = debugModule('snyk-pnpm-workspaces');
import * as lockFileParser from 'snyk-nodejs-lockfile-parser';
import { MultiProjectResultCustom, ScannedProjectCustom } from '../types';
import { getFileContents, isSubpath, normalizeFilePath } from '../utils';
import {
getWorkspacesMap,
packageJsonBelongsToWorkspace,
sortTargetFiles,
} from './workspace-utils';
import { DepGraph } from '@snyk/dep-graph';

const PNPM_ROOT_FILES = [
'pnpm-workspace.yaml',
'package.json',
'pnpm-lock.yaml',
];

// Compute project versions map
// This is needed because the lockfile doesn't present the version of
// a project that's part of a workspace, we need to retrieve it from
// its corresponding package.json
function computeProjectVersionMaps(root: string, targetFiles) {
const projectsVersionMap = {};
for (const directory of Object.keys(targetFiles)) {
if (!isSubpath(directory, root)) {
continue;
}
const packageJsonFileName = pathUtil.join(directory, 'package.json');
const packageJson = getFileContents(root, packageJsonFileName);

try {
const parsedPkgJson = lockFileParser.parsePkgJson(packageJson.content);
const projectVersion = parsedPkgJson.version;
projectsVersionMap[
normalizeFilePath(pathUtil.relative(root, directory))
] = projectVersion || 'undefined';
} catch (err: any) {
debug(
`Error getting version for project: ${packageJsonFileName}. ERROR: ${err}`,
);
continue;
}
}
return projectsVersionMap;
}
import { sortTargetFiles } from './workspace-utils';
import { ScannedNodeProject } from 'snyk-nodejs-lockfile-parser/dist/dep-graph-builders/types';

export async function processPnpmWorkspaces(
root: string,
Expand All @@ -56,7 +17,7 @@ export async function processPnpmWorkspaces(
},
targetFiles: string[],
): Promise<MultiProjectResultCustom> {
const pnpmTargetFiles = sortTargetFiles(targetFiles, PNPM_ROOT_FILES);
const pnpmWorkspaceDirs = sortTargetFiles(targetFiles, ['pnpm-lock.yaml']);

debug(`Processing potential Pnpm workspaces (${targetFiles.length})`);

Expand All @@ -68,101 +29,32 @@ export async function processPnpmWorkspaces(
scannedProjects: [],
};

let pnpmWorkspacesMap = {};
const pnpmWorkspacesFilesMap = {};

let rootWorkspaceManifestContent = {};
const projectsVersionMap = {};

// the folders must be ordered highest first
for (const directory of Object.keys(pnpmTargetFiles)) {
for (const directory of Object.keys(pnpmWorkspaceDirs)) {
debug(`Processing ${directory} as a potential Pnpm workspace`);
let isPnpmWorkspacePackage = false;
let isRootPackageJson = false;
const packageJsonFileName = pathUtil.join(directory, 'package.json');
const packageJson = getFileContents(root, packageJsonFileName);
pnpmWorkspacesMap = {
...pnpmWorkspacesMap,
...getWorkspacesMap(root, directory, packageJson),
};

for (const workspaceRoot of Object.keys(pnpmWorkspacesMap)) {
const match = packageJsonBelongsToWorkspace(
packageJsonFileName,
pnpmWorkspacesMap,
workspaceRoot,
);
if (match) {
debug(`${packageJsonFileName} matches an existing workspace pattern`);
pnpmWorkspacesFilesMap[packageJsonFileName] = {
root: workspaceRoot,
};
isPnpmWorkspacePackage = true;
}
if (packageJsonFileName === workspaceRoot) {
isRootPackageJson = true;
const workspaceRootDir = pathUtil.dirname(workspaceRoot);
projectsVersionMap[workspaceRootDir] = computeProjectVersionMaps(
workspaceRootDir,
pnpmTargetFiles,
);
rootWorkspaceManifestContent = JSON.parse(packageJson.content);
}
}

if (!(isPnpmWorkspacePackage || isRootPackageJson)) {
const pnpmWorkspacePath = pathUtil.join(directory, 'pnpm-workspace.yaml');
if (!fs.existsSync(pnpmWorkspacePath)) {
debug(
`${packageJsonFileName} is not part of any detected workspace, skipping`,
`Workspace file not found at ${directory}. Can't be a pnpm workspace root.`,
);
continue;
}

const rootDir = isPnpmWorkspacePackage
? pathUtil.dirname(pnpmWorkspacesFilesMap[packageJsonFileName].root)
: pathUtil.dirname(packageJsonFileName);

try {
const rootPnpmLockfileName = pathUtil.join(rootDir, 'pnpm-lock.yaml');
const pnpmLock = getFileContents(root, rootPnpmLockfileName);
const lockfileVersion = lockFileParser.getPnpmLockfileVersion(
pnpmLock.content,
);
const res: DepGraph = await lockFileParser.parsePnpmProject(
packageJson.content,
pnpmLock.content,
{
includeDevDeps: settings.dev || false,
includeOptionalDeps: settings.optional || true,
pruneWithinTopLevelDeps: true,
strictOutOfSync:
settings.strictOutOfSync === undefined
? true
: settings.strictOutOfSync,
},
lockfileVersion,
{
isWorkspacePkg: true,
workspacePath: normalizeFilePath(
pathUtil.relative(rootDir, directory),
),
isRoot: isRootPackageJson,
projectsVersionMap: projectsVersionMap[rootDir],
rootOverrides: rootWorkspaceManifestContent?.['pnpm.overrides'] || {},
},
);
const project: ScannedProjectCustom = {
packageManager: 'pnpm',
targetFile: pathUtil.relative(root, packageJson.fileName),
depGraph: res as any,
plugin: {
name: 'snyk-nodejs-lockfile-parser',
runtime: process.version,
},
};
result.scannedProjects.push(project);
} catch (e) {
debug(`Error process workspace: ${packageJsonFileName}. ERROR: ${e}`);
}
const scannedProjects: ScannedNodeProject[] =
await lockFileParser.parsePnpmWorkspace(root, directory, {
includeDevDeps: settings.dev || false,
includeOptionalDeps: settings.optional || true,
includePeerDeps: true,
pruneWithinTopLevelDeps: true,
strictOutOfSync:
settings.strictOutOfSync === undefined
? true
: settings.strictOutOfSync,
});
result.scannedProjects = result.scannedProjects.concat(
scannedProjects as ScannedProjectCustom[],
);
}

if (!result.scannedProjects.length) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"lodash.isempty": "^4.4.0",
"lodash.sortby": "^4.7.0",
"micromatch": "4.0.7",
"snyk-nodejs-lockfile-parser": "^1.56.1",
"snyk-nodejs-lockfile-parser": "1.58.5",
"snyk-resolve-deps": "4.8.0"
},
"devDependencies": {
Expand Down