Skip to content

Commit

Permalink
Merge pull request snyk#955 from snyk/feat/exclude-auto-detect
Browse files Browse the repository at this point in the history
feat: add exclude arg and use to ignore
  • Loading branch information
lili2311 committed Jan 20, 2020
2 parents 4cea366 + 9ba768c commit 9e4c929
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 6 deletions.
5 changes: 5 additions & 0 deletions help/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ Experimental options:
(test & monitor commands only)
Use with --all-projects to indicate how many sub-directories to search.
Defaults to 1 (the current working directory).
--exclude=<comma seperated list of directory names>
(test & monitor commands only)
Can only be used with --all-projects to indicate sub-directories to exclude.
Directories must be comma seperated.
If using with --detection-depth exclude ignores directories at any level deep.

Examples:

Expand Down
2 changes: 1 addition & 1 deletion src/cli/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ const program = {
win32: 'clip',
}[process.platform];

export function copy(str) {
export function copy(str: string) {
return execSync(program, { input: str });
}
23 changes: 21 additions & 2 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as Debug from 'debug';
import * as pathLib from 'path';

// assert supported node runtime version
import * as runtime from './runtime';
Expand All @@ -15,9 +16,15 @@ import errors = require('../lib/errors/legacy-errors');
import ansiEscapes = require('ansi-escapes');
import { isPathToPackageFile } from '../lib/detect';
import { updateCheck } from '../lib/updater';
import { MissingTargetFileError, FileFlagBadInputError } from '../lib/errors';
import { UnsupportedOptionCombinationError } from '../lib/errors/unsupported-option-combination-error';
import {
MissingTargetFileError,
FileFlagBadInputError,
OptionMissingErrorError,
UnsupportedOptionCombinationError,
ExcludeFlagBadInputError,
} from '../lib/errors';
import stripAnsi from 'strip-ansi';
import { ExcludeFlagInvalidInputError } from '../lib/errors/exclude-flag-invalid-input';

const debug = Debug('snyk');
const EXIT_CODES = {
Expand Down Expand Up @@ -183,6 +190,18 @@ async function main() {
]);
}

if (args.options.exclude) {
if (typeof args.options.exclude !== 'string') {
throw new ExcludeFlagBadInputError();
}
if (!args.options.allProjects) {
throw new OptionMissingErrorError('--exclude', '--all-projects');
}
if (args.options.exclude.indexOf(pathLib.sep) > -1) {
throw new ExcludeFlagInvalidInputError();
}
}

if (
args.options.file &&
typeof args.options.file === 'string' &&
Expand Down
13 changes: 13 additions & 0 deletions src/lib/errors/exclude-flag-bad-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CustomError } from './custom-error';

export class ExcludeFlagBadInputError extends CustomError {
private static ERROR_CODE = 422;
private static ERROR_MESSAGE =
'Empty --exclude argument. Did you mean --exclude=subdirectory ?';

constructor() {
super(ExcludeFlagBadInputError.ERROR_MESSAGE);
this.code = ExcludeFlagBadInputError.ERROR_CODE;
this.userMessage = ExcludeFlagBadInputError.ERROR_MESSAGE;
}
}
13 changes: 13 additions & 0 deletions src/lib/errors/exclude-flag-invalid-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CustomError } from './custom-error';

export class ExcludeFlagInvalidInputError extends CustomError {
private static ERROR_CODE = 422;
private static ERROR_MESSAGE =
'The --exclude argument must be a comma seperated list of directory names and cannot contain a path.';

constructor() {
super(ExcludeFlagInvalidInputError.ERROR_MESSAGE);
this.code = ExcludeFlagInvalidInputError.ERROR_CODE;
this.userMessage = ExcludeFlagInvalidInputError.ERROR_MESSAGE;
}
}
3 changes: 3 additions & 0 deletions src/lib/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ export { UnsupportedPackageManagerError } from './unsupported-package-manager-er
export { FailedToRunTestError } from './failed-to-run-test-error';
export { TooManyVulnPaths } from './too-many-vuln-paths';
export { AuthFailedError } from './authentication-failed-error';
export { OptionMissingErrorError } from './option-missing-error';
export { ExcludeFlagBadInputError } from './exclude-flag-bad-input';
export { UnsupportedOptionCombinationError } from './unsupported-option-combination-error';
10 changes: 10 additions & 0 deletions src/lib/errors/option-missing-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CustomError } from './custom-error';

export class OptionMissingErrorError extends CustomError {
constructor(option: string, required: string) {
const msg = `The ${option} option can only be use in combination with ${required}.`;
super(msg);
this.code = 422;
this.userMessage = msg;
}
}
9 changes: 8 additions & 1 deletion src/lib/plugins/get-deps-from-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ export async function getDepsFromPlugin(

if (options.allProjects) {
const levelsDeep = options.detectionDepth || 1; // default to 1 level deep
const targetFiles = await find(root, [], AUTO_DETECTABLE_FILES, levelsDeep);
const ignore = options.exclude ? options.exclude.split(',') : [];
const targetFiles = await find(
root,
ignore,
AUTO_DETECTABLE_FILES,
levelsDeep,
);
debug(
`auto detect manifest files, found ${targetFiles.length}`,
targetFiles,
Expand All @@ -41,6 +47,7 @@ export async function getDepsFromPlugin(
detectPackageManagerFromFile(file),
),
levelsDeep,
ignore,
};
analytics.add('allProjects', analyticData);
return inspectRes;
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface Options {
scanAllUnmanaged?: boolean;
allProjects?: boolean;
detectionDepth?: number;
exclude?: string;
}

// TODO(kyegupov): catch accessing ['undefined-properties'] via noImplicitAny
Expand Down
45 changes: 44 additions & 1 deletion test/acceptance/cli-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const argsNotAllowedWithAllProjects = [
];

argsNotAllowedWithAllProjects.forEach((arg) => {
test(`using --${arg} and --all-projects throws exception`, (t) => {
test(`using --${arg} and --all-projects displays error message`, (t) => {
t.plan(2);
exec(`node ${main} test --${arg} --all-projects`, (err, stdout) => {
if (err) {
Expand All @@ -121,3 +121,46 @@ argsNotAllowedWithAllProjects.forEach((arg) => {
});
});
});

test('`test --exclude without --all-project displays error message`', (t) => {
t.plan(1);
exec(`node ${main} test --exclude=test`, (err, stdout) => {
if (err) {
throw err;
}
t.equals(
stdout.trim(),
'The --exclude option can only be use in combination with --all-projects.',
);
});
});

test('`test --exclude without any value displays error message`', (t) => {
t.plan(1);
exec(`node ${main} test --all-projects --exclude`, (err, stdout) => {
if (err) {
throw err;
}
t.equals(
stdout.trim(),
'Empty --exclude argument. Did you mean --exclude=subdirectory ?',
);
});
});

test('`test --exclude=path/to/dir displays error message`', (t) => {
t.plan(1);
const exclude = 'path/to/dir'.replace(/\//g, sep);
exec(
`node ${main} test --all-projects --exclude=${exclude}`,
(err, stdout) => {
if (err) {
throw err;
}
t.equals(
stdout.trim(),
'The --exclude argument must be a comma seperated list of directory names and cannot contain a path.',
);
},
);
});
35 changes: 34 additions & 1 deletion test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export const AllProjectsTests: AcceptanceTests = {
'calls maven plugin twice',
);
// maven
console.log(result);
t.match(result, 'maven/some/project-id', 'maven project was monitored ');

const requests = params.server.popRequests(2);
Expand Down Expand Up @@ -250,5 +249,39 @@ export const AllProjectsTests: AcceptanceTests = {
t.fail('should have passed', error);
}
},
'`monitor maven-multi-app --all-projects --detection-depth=2 --exclude=simple-child`': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces();
const spyPlugin = sinon.spy(params.plugins, 'loadPlugin');
t.teardown(spyPlugin.restore);
const result = await params.cli.monitor('maven-multi-app', {
allProjects: true,
detectionDepth: 2,
exclude: 'simple-child',
});
t.ok(
spyPlugin.withArgs('rubygems').notCalled,
'did not call rubygems plugin',
);
t.ok(spyPlugin.withArgs('npm').notCalled, 'did not call npm plugin');
t.equals(
spyPlugin.withArgs('maven').callCount,
1,
'calls maven plugin once, excluding simple-child',
);
t.match(result, 'maven/some/project-id', 'maven project was monitored ');
const request = params.server.popRequest();
t.ok(request, 'Monitor POST request');
t.match(request.url, '/monitor/', 'puts at correct url');
t.notOk(request.body.targetFile, "doesn't send the targetFile");
t.equal(request.method, 'PUT', 'makes PUT request');
t.equal(
request.headers['x-snyk-cli-version'],
params.versionNumber,
'sends version number',
);
},
},
};
29 changes: 29 additions & 0 deletions test/acceptance/cli-test/cli-test.all-projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,35 @@ export const AllProjectsTests: AcceptanceTests = {
}
},

'`test large-mono-repo with --all-projects, --detection-depth=7 and --exclude=bundler-app,maven-project-1`': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces();
const spyPlugin = sinon.spy(params.plugins, 'loadPlugin');
t.teardown(spyPlugin.restore);
await params.cli.test('large-mono-repo', {
allProjects: true,
detectionDepth: 7,
exclude: 'bundler-app,maven-project-1',
});
t.equals(
spyPlugin.withArgs('rubygems').callCount,
0,
'does not call rubygems',
);
t.equals(
spyPlugin.withArgs('npm').callCount,
19,
'calls npm plugin 19 times',
);
t.equals(
spyPlugin.withArgs('maven').callCount,
1,
'calls maven plugin once, excluding the rest',
);
},

'`test empty --all-projects`': (params, utils) => async (t) => {
utils.chdirWorkspaces();
try {
Expand Down

0 comments on commit 9e4c929

Please sign in to comment.