diff --git a/src/cli/commands/monitor/formatters/format-monitor-response.ts b/src/cli/commands/monitor/formatters/format-monitor-response.ts index aa700598a39..0fb49ef0e58 100644 --- a/src/cli/commands/monitor/formatters/format-monitor-response.ts +++ b/src/cli/commands/monitor/formatters/format-monitor-response.ts @@ -15,7 +15,8 @@ export function formatMonitorOutput( const manageUrl = buildManageUrl(res.id, res.org); const advertiseGradleSubProjectsCount = packageManager === 'gradle' && !options['gradle-sub-project']; - + const advertiseAllProjectsCount = + packageManager !== 'gradle' && !options.allProjects && foundProjectCount; const issues = res.licensesPolicy ? 'issues' : 'vulnerabilities'; const humanReadableName = projectName ? `${res.path} (${projectName})` @@ -31,6 +32,12 @@ export function formatMonitorOutput( 'use --all-sub-projects flag to scan all sub-projects.\n\n', ) : '') + + (advertiseAllProjectsCount && foundProjectCount + ? chalk.bold.white( + `Tip: Detected multiple supported manifests (${foundProjectCount}), ` + + 'use --all-projects to scan all of them at once.\n\n', + ) + : '') + (res.isMonitored ? 'Notifications about newly disclosed ' + issues + diff --git a/src/cli/commands/monitor/index.ts b/src/cli/commands/monitor/index.ts index e6795a7b095..283fe871bfe 100644 --- a/src/cli/commands/monitor/index.ts +++ b/src/cli/commands/monitor/index.ts @@ -28,7 +28,7 @@ import { processJsonMonitorResponse } from './process-json-monitor'; import snyk = require('../../../lib'); // TODO(kyegupov): fix import import { formatMonitorOutput } from './formatters/format-monitor-response'; import { getDepsFromPlugin } from '../../../lib/plugins/get-deps-from-plugin'; -import { getSubProjectCount } from '../../../lib/plugins/get-sub-project-count'; +import { getExtraProjectCount } from '../../../lib/plugins/get-extra-project-count'; import { extractPackageManager } from '../../../lib/plugins/extract-package-manager'; import { MultiProjectResultCustom } from '../../../lib/plugins/get-multi-plugin-result'; import { convertMultiResultToMultiCustom } from '../../../lib/plugins/convert-multi-plugin-res-to-multi-custom'; @@ -241,7 +241,7 @@ async function monitor(...args0: MethodArgs): Promise { res, options, projectName, - getSubProjectCount(inspectResult), + await getExtraProjectCount(path, options, inspectResult), ); // push a good result results.push({ ok: true, data: monOutput, path, projectName }); diff --git a/src/cli/commands/test/index.ts b/src/cli/commands/test/index.ts index 96963e12bd2..02971c1520c 100644 --- a/src/cli/commands/test/index.ts +++ b/src/cli/commands/test/index.ts @@ -215,7 +215,6 @@ async function test(...args: MethodArgs): Promise { let response = results .map((result, i) => { resultOptions[i].pinningSupported = pinningSupported; - return displayResult( results[i] as LegacyVulnApiResult, resultOptions[i], @@ -425,13 +424,23 @@ function displayResult( let multiProjAdvice = ''; const advertiseGradleSubProjectsCount = - projectType === 'gradle' && !options['gradle-sub-project']; - if (advertiseGradleSubProjectsCount && foundProjectCount) { + projectType === 'gradle' && + !options['gradle-sub-project'] && + foundProjectCount; + if (advertiseGradleSubProjectsCount) { multiProjAdvice = chalk.bold.white( `\n\nTip: This project has multiple sub-projects (${foundProjectCount}), ` + 'use --all-sub-projects flag to scan all sub-projects.', ); } + const advertiseAllProjectsCount = + projectType !== 'gradle' && !options.allProjects && foundProjectCount; + if (advertiseAllProjectsCount) { + multiProjAdvice = chalk.bold.white( + `\n\nTip: Detected multiple supported manifests (${foundProjectCount}), ` + + 'use --all-projects to scan all of them at once.', + ); + } // OK => no vulns found, return if (res.ok && res.vulnerabilities.length === 0) { diff --git a/src/lib/plugins/get-extra-project-count.ts b/src/lib/plugins/get-extra-project-count.ts new file mode 100644 index 00000000000..8c40a71d7d0 --- /dev/null +++ b/src/lib/plugins/get-extra-project-count.ts @@ -0,0 +1,29 @@ +import { legacyPlugin as pluginApi } from '@snyk/cli-interface'; +import { find } from '../find-files'; +import { AUTO_DETECTABLE_FILES } from '../detect'; +import { Options } from '../types'; + +export async function getExtraProjectCount( + root: string, + options: Options, + inspectResult: pluginApi.InspectResult, +): Promise { + if (options.docker) { + return undefined; + } + if ( + inspectResult.plugin.meta && + inspectResult.plugin.meta.allSubProjectNames && + inspectResult.plugin.meta.allSubProjectNames.length > 1 + ) { + return inspectResult.plugin.meta.allSubProjectNames.length; + } + try { + const extraTargetFiles = await find(root, [], AUTO_DETECTABLE_FILES); + const foundProjectsCount = + extraTargetFiles.length > 1 ? extraTargetFiles.length - 1 : undefined; + return foundProjectsCount; + } catch (e) { + return undefined; + } +} diff --git a/src/lib/plugins/get-sub-project-count.ts b/src/lib/plugins/get-sub-project-count.ts deleted file mode 100644 index 5b11ae61675..00000000000 --- a/src/lib/plugins/get-sub-project-count.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { legacyPlugin as pluginApi } from '@snyk/cli-interface'; - -export function getSubProjectCount( - inspectResult: pluginApi.InspectResult, -): number | undefined { - if ( - inspectResult.plugin.meta && - inspectResult.plugin.meta.allSubProjectNames && - inspectResult.plugin.meta.allSubProjectNames.length > 1 - ) { - return inspectResult.plugin.meta.allSubProjectNames.length; - } - - return undefined; -} diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 9ebaf3a960f..ca6b6141259 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -47,7 +47,7 @@ import { ScannedProjectCustom } from '../plugins/get-multi-plugin-result'; import request = require('../request'); import spinner = require('../spinner'); import { extractPackageManager } from '../plugins/extract-package-manager'; -import { getSubProjectCount } from '../plugins/get-sub-project-count'; +import { getExtraProjectCount } from '../plugins/get-extra-project-count'; import { serializeCallGraphWithMetrics } from '../reachable-vulns'; import { validateOptions } from '../options-validator'; import { findAndLoadPolicy } from '../policy'; @@ -471,7 +471,7 @@ async function assembleLocalPayloads( projectNameOverride: options.projectName, originalProjectName, policy: policy ? policy.toString() : undefined, - foundProjectCount: getSubProjectCount(deps), + foundProjectCount: await getExtraProjectCount(root, options, deps), displayTargetFile: targetFile, docker: (pkg as DepTree).docker, hasDevDependencies: (pkg as any).hasDevDependencies, diff --git a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts index a2a2449d93d..f31388744ea 100644 --- a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts +++ b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts @@ -1370,6 +1370,12 @@ test('`monitor cocoapods-app --file=Podfile`', async (t) => { ); }); +test('`monitor large-mono-repo --file=bundler-app/Gemfile` suggest to use --all-projects', async (t) => { + chdirWorkspaces('large-mono-repo'); + const res = await cli.monitor({ file: 'bundler-app/Gemfile' }); + t.match(res, '--all-projects', 'Suggest using --all-projects'); +}); + test('`monitor cocoapods-app --file=Podfile.lock`', async (t) => { chdirWorkspaces('cocoapods-app'); const plugin = { diff --git a/test/acceptance/cli-test/cli-test.ruby.spec.ts b/test/acceptance/cli-test/cli-test.ruby.spec.ts index e765337cedc..5c86cbb794d 100644 --- a/test/acceptance/cli-test/cli-test.ruby.spec.ts +++ b/test/acceptance/cli-test/cli-test.ruby.spec.ts @@ -568,5 +568,19 @@ export const RubyTests: AcceptanceTests = { ); } }, + '`test large-mono-repo --file=bundler-app/Gemfile`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const res = await params.cli.test('large-mono-repo', { + file: 'bundler-app/Gemfile', + }); + t.match( + res.getDisplayResults(), + '--all-projects', + 'Suggest using --all-projects', + ); + }, }, };