Skip to content
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
23 changes: 22 additions & 1 deletion src/lib/plugins/get-multi-plugin-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
SUPPORTED_MANIFEST_FILES,
SupportedPackageManagers,
} from '../package-managers';
const { SHOW_NPM_SCOPE } = require('../feature-flags');
import { SHOW_NPM_SCOPE } from '../feature-flags';
import { getSinglePluginResult } from './get-single-plugin-result';
import { convertSingleResultToMultiCustom } from './convert-single-splugin-res-to-multi-custom';
import { convertMultiResultToMultiCustom } from './convert-multi-plugin-res-to-multi-custom';
Expand Down Expand Up @@ -85,6 +85,13 @@ export async function getMultiPluginResult(
featureFlags,
);
unprocessedFilesfromWorkspaces = unprocessedFilesFromPnpm;
// Annotate each scanned project with the workspace plugin name for later identification
scannedPnpmResults.forEach((project) => {
if (!project.meta) {
project.meta = {};
}
project.meta.workspacePluginName = 'snyk-nodejs-pnpm-workspaces';
});
allResults.push(...scannedPnpmResults);

const {
Expand All @@ -97,6 +104,13 @@ export async function getMultiPluginResult(
'yarn',
featureFlags,
);
// Annotate each scanned project with the workspace plugin name for later identification
scannedYarnResults.forEach((project) => {
if (!project.meta) {
project.meta = {};
}
project.meta.workspacePluginName = 'snyk-nodejs-yarn-workspaces';
});
allResults.push(...scannedYarnResults);

const { scannedProjects: scannedNpmResults, unprocessedFiles } =
Expand All @@ -107,6 +121,13 @@ export async function getMultiPluginResult(
'npm',
featureFlags,
);
// Annotate each scanned project with the workspace plugin name for later identification
scannedNpmResults.forEach((project) => {
if (!project.meta) {
project.meta = {};
}
project.meta.workspacePluginName = 'snyk-nodejs-npm-workspaces';
});
allResults.push(...scannedNpmResults);

debug(`Not part of a workspace: ${unprocessedFiles.join(', ')}}`);
Expand Down
36 changes: 35 additions & 1 deletion src/lib/snyk-test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ import { CLI, ProblemError } from '@snyk/error-catalog-nodejs-public';
import { CustomError } from '../errors';
import { FailedProjectScanError } from '../plugins/get-multi-plugin-result';

/**
* Determines workspace information from the plugin name and scanned project metadata.
* Returns workspace metadata if the plugin is a workspace plugin, otherwise undefined.
*/
function getWorkspaceInfo(
pluginName: string | undefined,
workspacePluginName: string | undefined,
): { type: string } | undefined {
// Check workspace plugin name from scannedProject.meta (--all-projects) or parent plugin (--yarn-workspaces)
if (
workspacePluginName === 'snyk-nodejs-yarn-workspaces' ||
pluginName === 'snyk-nodejs-yarn-workspaces'
) {
return { type: 'yarn' };
}
if (
workspacePluginName === 'snyk-nodejs-npm-workspaces' ||
pluginName === 'snyk-nodejs-npm-workspaces'
) {
return { type: 'npm' };
}
if (
workspacePluginName === 'snyk-nodejs-pnpm-workspaces' ||
pluginName === 'snyk-nodejs-pnpm-workspaces'
) {
return { type: 'pnpm' };
}

return undefined;
}

export function assembleQueryString(options) {
const org = options.org || config.org || null;
const qs: {
Expand Down Expand Up @@ -116,15 +147,18 @@ export async function printEffectiveDepGraph(
targetFileFromPlugin: string | undefined,
target: GitTarget | ContainerTarget | null | undefined,
targetRuntime: string | undefined,
pluginName: string | undefined,
workspacePluginName: string | undefined,
destination: Writable,
): Promise<void> {
return new Promise((res, rej) => {
const effectiveGraphOutput = {
const effectiveGraphOutput: any = {
depGraph,
normalisedTargetFile,
targetFileFromPlugin,
target,
targetRuntime,
workspace: getWorkspaceInfo(pluginName, workspacePluginName),
};

new ConcatStream(
Expand Down
2 changes: 2 additions & 0 deletions src/lib/snyk-test/run-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,8 @@ async function assembleLocalPayloads(
project.plugin.targetFile,
target,
scannedProject.meta?.targetRuntime ?? project.plugin?.targetRuntime,
deps.plugin.name,
scannedProject.meta?.workspacePluginName,
process.stdout,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,82 @@ describe('`test` command with `--print-effective-graph-with-errors` option', ()
});
expect(outputs[0].depGraph).toBeDefined();
});

it('includes workspace type for yarn workspaces', async () => {
const project = await createProjectFromWorkspace('yarn-workspaces');
const { code, stdout } = await runSnykCLI(
'test --yarn-workspaces --print-effective-graph-with-errors',
{
cwd: project.path(),
env,
},
);

expect(code).toBe(0);

const outputs = parseJSONL(stdout) as any[];

// All workspace projects should have workspace field
expect(outputs.length).toBeGreaterThan(0);
for (const output of outputs) {
expect(output).toHaveProperty('workspace');
expect(output.workspace).toEqual({ type: 'yarn' });
expect(output).toHaveProperty('depGraph');
expect(output.depGraph.pkgManager.name).toBe('yarn');
}
});

it('includes workspace type for workspaces with --all-projects', async () => {
const project = await createProjectFromWorkspace('yarn-workspaces');

const { code, stdout } = await runSnykCLI(
'test --all-projects --print-effective-graph-with-errors',
{
cwd: project.path(),
env,
},
);

expect(code).toBe(0);

const outputs = parseJSONL(stdout) as any[];

// Should have outputs with workspace field
expect(outputs.length).toBeGreaterThan(0);

// All outputs should have workspace field when detected as workspace
const workspaceOutputs = outputs.filter((output) => output.workspace);
expect(workspaceOutputs.length).toBeGreaterThan(0);

// Verify workspace field structure
for (const output of workspaceOutputs) {
expect(output.workspace).toHaveProperty('type');
expect(['yarn', 'npm', 'pnpm']).toContain(output.workspace.type);
expect(output).toHaveProperty('depGraph');
}
});

it('does not include workspace field for non-workspace projects', async () => {
const project = await createProjectFromFixture(
'npm/with-vulnerable-lodash-dep',
);
server.setCustomResponse(
await project.readJSON('test-dep-graph-result.json'),
);
const { code, stdout } = await runSnykCLI(
'test --print-effective-graph-with-errors',
{
cwd: project.path(),
env,
},
);

expect(code).toEqual(0);

const jsonOutput = JSON.parse(stdout);

// Non-workspace project should NOT have workspace field
expect(jsonOutput).not.toHaveProperty('workspace');
expect(jsonOutput).toHaveProperty('depGraph');
});
});
Loading