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] improve workspaces parsing #234

Merged
merged 1 commit into from
Jul 18, 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
3 changes: 2 additions & 1 deletion lib/dep-graph-builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
extractPkgsFromYarnLockV2,
} from './yarn-lock-v2';
import { parseNpmLockV2Project } from './npm-lock-v2';
import { parsePnpmProject } from './pnpm';
import { parsePnpmProject, parsePnpmWorkspace } from './pnpm';
import { parsePkgJson } from './util';

export {
Expand All @@ -29,5 +29,6 @@ export {
parseYarnLockV2Project,
extractPkgsFromYarnLockV2,
parsePnpmProject,
parsePnpmWorkspace,
parsePkgJson,
};
15 changes: 4 additions & 11 deletions lib/dep-graph-builders/pnpm/build-dep-graph-pnpm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { DepGraphBuilder } from '@snyk/dep-graph';
import { getTopLevelDeps } from '../util';
import type {
Overrides,
PnpmProjectParseOptions,
PnpmWorkspaceArgs,
} from '../types';
import type { Overrides, PnpmProjectParseOptions } from '../types';
import type { PackageJsonBase } from '../types';
import { getPnpmChildNode } from './utils';
import { eventLoopSpinner } from 'event-loop-spinner';
Expand All @@ -15,7 +11,7 @@ export const buildDepGraphPnpm = async (
lockFileParser: PnpmLockfileParser,
pkgJson: PackageJsonBase,
options: PnpmProjectParseOptions,
workspaceArgs?: PnpmWorkspaceArgs,
importer?: string,
) => {
const {
strictOutOfSync,
Expand All @@ -37,7 +33,7 @@ export const buildDepGraphPnpm = async (
const topLevelDeps = getTopLevelDeps(pkgJson, options);

const extractedTopLevelDeps =
lockFileParser.extractTopLevelDependencies(options) || {};
lockFileParser.extractTopLevelDependencies(options, importer) || {};

for (const name of Object.keys(topLevelDeps)) {
topLevelDeps[name].version = extractedTopLevelDeps[name].version;
Expand All @@ -58,10 +54,7 @@ export const buildDepGraphPnpm = async (
strictOutOfSync,
includeOptionalDeps,
includeDevDeps,
// we have rootWorkspaceOverrides if this is workspace pkg with overrides
// at root - therefore it should take precedent
// TODO: inspect if this is needed at all, seems like pnpm resolves everything in lockfile
workspaceArgs?.rootOverrides || pkgJson.pnpm?.overrides || {},
pkgJson.pnpm?.overrides || {},
pruneWithinTopLevelDeps,
lockFileParser,
);
Expand Down
50 changes: 3 additions & 47 deletions lib/dep-graph-builders/pnpm/index.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,4 @@
import { parsePkgJson } from '../util';
import {
PackageJsonBase,
PnpmProjectParseOptions,
PnpmWorkspaceArgs,
} from '../types';
import { buildDepGraphPnpm } from './build-dep-graph-pnpm';
import { DepGraph } from '@snyk/dep-graph';
import { getPnpmLockfileParser } from './lockfile-parser/index';
import { PnpmLockfileParser } from './lockfile-parser/lockfile-parser';
import { NodeLockfileVersion } from '../../utils';
import { parsePnpmProject } from './parse-project';
import { parsePnpmWorkspace } from './parse-workspace';

export const parsePnpmProject = async (
pkgJsonContent: string,
pnpmLockContent: string,
options: PnpmProjectParseOptions,
lockfileVersion?: NodeLockfileVersion,
workspaceArgs?: PnpmWorkspaceArgs,
): Promise<DepGraph> => {
const {
includeDevDeps,
includeOptionalDeps,
strictOutOfSync,
pruneWithinTopLevelDeps,
} = options;

const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);

const lockFileParser: PnpmLockfileParser = getPnpmLockfileParser(
pnpmLockContent,
lockfileVersion,
workspaceArgs,
);

const depgraph = await buildDepGraphPnpm(
lockFileParser,
pkgJson,
{
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevelDeps,
},
workspaceArgs,
);

return depgraph;
};
export { parsePnpmProject, parsePnpmWorkspace };
71 changes: 24 additions & 47 deletions lib/dep-graph-builders/pnpm/lockfile-parser/lockfile-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,28 @@ export abstract class PnpmLockfileParser {
public lockFileVersion: string;
public rawPnpmLock: any;
public packages: Record<PnpmDepPath, PnpmLockPkg>;
public dependencies: Record<string, any>;
public devDependencies: Record<string, any>;
public optionalDependencies: Record<string, any>;
public peerDependencies: Record<string, any>;
public extractedPackages: NormalisedPnpmPkgs;
public importers: PnpmImporters;
public workspaceArgs?: PnpmWorkspaceArgs;

public constructor(rawPnpmLock: any, workspaceArgs?: PnpmWorkspaceArgs) {
this.rawPnpmLock = rawPnpmLock;
this.lockFileVersion = rawPnpmLock.lockFileVersion;
this.lockFileVersion = rawPnpmLock.lockfileVersion;
this.workspaceArgs = workspaceArgs;
const depsRoot = this.getRoot(rawPnpmLock);
this.packages = rawPnpmLock.packages || {};
this.dependencies = depsRoot.dependencies || {};
this.devDependencies = depsRoot.devDependencies || {};
this.optionalDependencies = depsRoot.optionalDependencies || {};
this.peerDependencies = depsRoot.peerDependencies || {};
this.extractedPackages = {};
this.importers = this.normaliseImporters(rawPnpmLock);
}

public isWorkspaceLockfile() {
return this.workspaceArgs?.isWorkspacePkg;
}

public getRoot(rawPnpmLock: any) {
let depsRoot = rawPnpmLock;
if (this.workspaceArgs?.isWorkspacePkg) {
depsRoot = rawPnpmLock.importers[this.workspaceArgs.workspacePath];
}
if (this.workspaceArgs?.isRoot) {
if (!this.workspaceArgs.workspacePath) {
this.workspaceArgs.workspacePath = '.';
}
depsRoot = rawPnpmLock.importers[this.workspaceArgs.workspacePath];
}
return depsRoot;
return this.workspaceArgs?.isWorkspace;
}

public extractPackages() {
// Packages should be parsed only one time for a parser
if (Object.keys(this.extractedPackages).length > 0) {
return this.extractedPackages;
}
const packages: NormalisedPnpmPkgs = {};
Object.entries(this.packages).forEach(
([depPath, versionData]: [string, any]) => {
Expand All @@ -81,42 +62,38 @@ export abstract class PnpmLockfileParser {
return packages;
}

public extractTopLevelDependencies(options: {
includeDevDeps: boolean;
includeOptionalDeps?: boolean;
includePeerDeps?: boolean;
}): PnpmDeps {
let importerName;
if (this.isWorkspaceLockfile()) {
importerName = this.workspaceArgs?.workspacePath;
public extractTopLevelDependencies(
options: {
includeDevDeps: boolean;
includeOptionalDeps?: boolean;
includePeerDeps?: boolean;
},
importer?: string,
): PnpmDeps {
let root = this.rawPnpmLock;
if (importer) {
root = this.rawPnpmLock.importers[importer];
}

const prodDeps = this.normalizeTopLevelDeps(
this.dependencies || {},
root.dependencies || {},
false,
importerName,
importer,
);
const devDeps = options.includeDevDeps
? this.normalizeTopLevelDeps(
this.devDependencies || {},
true,
importerName,
)
? this.normalizeTopLevelDeps(root.devDependencies || {}, true, importer)
: {};

const optionalDeps = options.includeOptionalDeps
? this.normalizeTopLevelDeps(
this.optionalDependencies || {},
root.optionalDependencies || {},
false,
importerName,
importer,
)
: {};

const peerDeps = options.includePeerDeps
? this.normalizeTopLevelDeps(
this.peerDependencies || {},
false,
importerName,
)
? this.normalizeTopLevelDeps(root.peerDependencies || {}, false, importer)
: {};

return { ...prodDeps, ...devDeps, ...optionalDeps, ...peerDeps };
Expand Down
5 changes: 0 additions & 5 deletions lib/dep-graph-builders/pnpm/lockfile-parser/lockfile-v5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ import { PnpmLockfileParser } from './lockfile-parser';
import { PnpmWorkspaceArgs } from '../../types';

export class LockfileV5Parser extends PnpmLockfileParser {
public specifiers: Record<string, string>;

public constructor(rawPnpmLock: any, workspaceArgs?: PnpmWorkspaceArgs) {
super(rawPnpmLock, workspaceArgs);
const depsRoot = this.getRoot(rawPnpmLock);
this.specifiers = depsRoot.specifiers;
}

public parseDepPath(depPath: string): ParsedDepPath {
Expand All @@ -36,7 +32,6 @@ export class LockfileV5Parser extends PnpmLockfileParser {
name,
version,
isDev,
specifier: this.specifiers[name],
};
return pnpmDeps;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class LockfileV6Parser extends PnpmLockfileParser {
pnpmDeps[name] = {
name,
version,
specifier: depInfo.specifier,
isDev,
};
return pnpmDeps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { PnpmWorkspaceArgs } from '../../types';
import { LockfileV6Parser } from './lockfile-v6';

const DEFAULT_WORKSPACE_ARGS: PnpmWorkspaceArgs = {
isWorkspacePkg: true,
isRoot: true,
workspacePath: '.',
isWorkspace: true,
projectsVersionMap: {},
rootOverrides: {},
};
export class LockfileV9Parser extends LockfileV6Parser {
public settings;
Expand Down
47 changes: 47 additions & 0 deletions lib/dep-graph-builders/pnpm/parse-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { parsePkgJson } from '../util';
import { PackageJsonBase, PnpmProjectParseOptions } from '../types';
import { buildDepGraphPnpm } from './build-dep-graph-pnpm';
import { DepGraph } from '@snyk/dep-graph';
import { getPnpmLockfileParser } from './lockfile-parser/index';
import { NodeLockfileVersion } from '../../utils';

export const parsePnpmProject = async (
pkgJsonContent: string,
pnpmLockContent: string,
options: PnpmProjectParseOptions,
lockfileVersion?: NodeLockfileVersion,
): Promise<DepGraph> => {
const {
includeDevDeps,
includeOptionalDeps,
strictOutOfSync,
pruneWithinTopLevelDeps,
} = options;
let importer = '';

const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);

const lockFileParser = getPnpmLockfileParser(
pnpmLockContent,
lockfileVersion,
);

// Lockfile V9 simple project has the root importer
if (lockFileParser.lockFileVersion.startsWith('9')) {
importer = '.';
}

const depgraph = await buildDepGraphPnpm(
lockFileParser,
pkgJson,
{
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevelDeps,
},
importer,
);

return depgraph;
};
Loading