diff --git a/help/help.txt b/help/help.txt index c2f473649ae..4a6a4917ab1 100644 --- a/help/help.txt +++ b/help/help.txt @@ -45,6 +45,7 @@ Options: --dry-run .......... Don't apply updates or patches during protect. --severity-threshold= Only report vulnerabilities of provided level or higher. + --print-deps ....... (experimental) Print the dependency tree. -q, --quiet ........ Silence all output. -h, --help ......... This help information. -v, --version ...... The CLI version. diff --git a/src/cli/commands/monitor.ts b/src/cli/commands/monitor.ts index 31835232d37..c6be5952e10 100644 --- a/src/cli/commands/monitor.ts +++ b/src/cli/commands/monitor.ts @@ -14,22 +14,12 @@ import * as spinner from '../../lib/spinner'; import * as detect from '../../lib/detect'; import * as plugins from '../../lib/plugins'; import {ModuleInfo} from '../../lib/module-info'; // TODO(kyegupov): fix import -import {SingleDepRootResult, MultiDepRootsResult, isMultiResult, MonitorError } from '../../lib/types'; +import { SingleDepRootResult, MultiDepRootsResult, isMultiResult, MonitorError, MonitorOptions } from '../../lib/types'; import { MethodArgs, ArgsOptions } from '../args'; +import { maybePrintDeps } from '../../lib/print-deps'; const SEPARATOR = '\n-------------------------------------------------------\n'; -// TODO(kyegupov): catch accessing ['undefined-properties'] via noImplicitAny -interface MonitorOptions { - id?: string; - docker?: boolean; - file?: string; - policy?: string; - json?: boolean; - 'all-sub-projects'?: boolean; // Corresponds to multiDepRoot in plugins - 'project-name'?: string; -} - interface GoodResult { ok: true; data: string; @@ -153,6 +143,8 @@ async function monitor(...args0: MethodArgs): Promise { // Post the project dependencies to the Registry for (const depRootDeps of perDepRootResults) { + maybePrintDeps(options, depRootDeps.package); + const res = await promiseOrCleanup( snykMonitor(path, meta, depRootDeps, targetFile), spinner.clear(postingMonitorSpinnerLabel)); diff --git a/src/lib/plugins/nodejs-plugin/npm-modules-parser.ts b/src/lib/plugins/nodejs-plugin/npm-modules-parser.ts index dca90f9904c..dbd25633b13 100644 --- a/src/lib/plugins/nodejs-plugin/npm-modules-parser.ts +++ b/src/lib/plugins/nodejs-plugin/npm-modules-parser.ts @@ -38,6 +38,6 @@ export async function parse(root: string, targetFile: string, options: Options): return snyk.modules( root, Object.assign({}, options, {noFromArrays: true})); } finally { - spinner.clear(resolveModuleSpinnerLabel)(); + await spinner.clear(resolveModuleSpinnerLabel)(); } } diff --git a/src/lib/print-deps.ts b/src/lib/print-deps.ts new file mode 100644 index 00000000000..106209d9285 --- /dev/null +++ b/src/lib/print-deps.ts @@ -0,0 +1,32 @@ +import { DepDict, TestOptions, MonitorOptions, DepTree } from './types'; + +// This option is still experimental and might be deprecated. +// It might be a better idea to convert it to a command (i.e. do not perform test/monitor). +export function maybePrintDeps(options: TestOptions | MonitorOptions, rootPackage: DepTree) { + if (options['print-deps']) { + if (options.json) { + // Will produce 2 JSON outputs, one for the deps, one for the vuln scan. + console.log(JSON.stringify(rootPackage, null, 2)); + } else { + printDeps({[rootPackage.name!]: rootPackage}); + } + } +} + +function printDeps(depDict: DepDict, prefix: string = '') { + let counter = 0; + const keys = Object.keys(depDict); + for (const name of keys) { + const dep = depDict[name]; + let branch = '├─ '; + const last = (counter === keys.length - 1); + if (last) { + branch = '└─ '; + } + console.log(prefix + (prefix ? branch : '') + dep.name + ' @ ' + dep.version); + if (dep.dependencies) { + printDeps(dep.dependencies, prefix + (last ? ' ' : '│ ')); + } + counter++; + } +} diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index c389081266c..1f8d29d383d 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -19,6 +19,7 @@ import gemfileLockToDependencies = require('../../lib/plugins/rubygems/gemfile-l import {convertTestDepGraphResultToLegacy, AnnotatedIssue, LegacyVulnApiResult, TestDepGraphResponse} from './legacy'; import {SingleDepRootResult, MultiDepRootsResult, isMultiResult, TestOptions} from '../types'; import { NoSupportedManifestsFoundError } from '../errors'; +import { maybePrintDeps } from '../print-deps'; // tslint:disable-next-line:no-var-requires const debug = require('debug')('snyk'); @@ -255,6 +256,10 @@ async function assembleLocalPayloads(root, options): Promise { for (const depRoot of deps.depRoots) { const pkg = depRoot.depTree; + if (options['print-deps']) { + await spinner.clear(spinnerLbl)(); + maybePrintDeps(options, pkg); + } if (deps.plugin && deps.plugin.packageManager) { options.packageManager = deps.plugin.packageManager; } @@ -349,7 +354,7 @@ async function assembleLocalPayloads(root, options): Promise { } return payloads; } finally { - spinner.clear(spinnerLbl)(); + await spinner.clear(spinnerLbl)(); } } diff --git a/src/lib/types.ts b/src/lib/types.ts index 0f97059543e..10475ebf473 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -71,4 +71,17 @@ export interface TestOptions { advertiseSubprojectsCount?: number; subProjectNames?: string[]; severityThreshold?: string; + 'print-deps'?: boolean; +} + +// TODO(kyegupov): catch accessing ['undefined-properties'] via noImplicitAny +export interface MonitorOptions { + id?: string; + docker?: boolean; + file?: string; + policy?: string; + json?: boolean; + 'all-sub-projects'?: boolean; // Corresponds to multiDepRoot in plugins + 'project-name'?: string; + 'print-deps'?: boolean; }