Skip to content

Commit

Permalink
feat: improve error handling when no files detected
Browse files Browse the repository at this point in the history
  • Loading branch information
lili2311 committed May 14, 2019
1 parent a1c3cc6 commit 49c7fb9
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function args(processargv) {

if (!method) {
// if we failed to find a command, then default to an error
method = require('../lib/error');
method = require('../lib/errors/legacy-errors');
argv._.push(command);
}

Expand Down
10 changes: 4 additions & 6 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as sln from '../lib/sln';
import {args as argsLib} from './args';
import {copy} from './copy';
import spinner = require('../lib/spinner');
import errors = require('../lib/error');
import errors = require('../lib/errors/legacy-errors');
import ansiEscapes = require('ansi-escapes');
import {isPathToPackageFile} from '../lib/detect';
import {updateCheck} from '../lib/updater';
Expand Down Expand Up @@ -113,15 +113,13 @@ async function main() {
checkRuntime();

const args = argsLib(process.argv);

if (args.options.file && args.options.file.match(/\.sln$/)) {
sln.updateArgs(args);
}

let res = null;
let failed = false;

try {
if (args.options.file && args.options.file.match(/\.sln$/)) {
sln.updateArgs(args);
}
checkPaths(args);
res = await runCommand(args);
} catch (error) {
Expand Down
10 changes: 2 additions & 8 deletions src/lib/detect.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as fs from 'then-fs';
import * as pathLib from 'path';
import * as debugLib from 'debug';
import chalk from 'chalk';
import * as _ from 'lodash';
import { NoSupportedManifestsFoundError } from './errors';

const debug = debugLib('snyk');

Expand Down Expand Up @@ -93,13 +93,7 @@ export function detectPackageManager(root, options) {
packageManager = detectPackageManagerFromRegistry(registry);
}
if (!packageManager) {
throw new Error('Could not detect supported target files in ' + root +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
) +
' and make sure you are in the right directory.');
throw NoSupportedManifestsFoundError([root]);
}
return packageManager;
}
Expand Down
15 changes: 15 additions & 0 deletions src/lib/errors/custom-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class CustomError extends Error {

public innerError;
public code;
public userMessage;

constructor(message: string) {
super(message);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.code = undefined;
this.innerError = undefined;
this.userMessage = undefined;
}
}
1 change: 1 addition & 0 deletions src/lib/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {NoSupportedManifestsFoundError} from './no-supported-manifests-found';
19 changes: 10 additions & 9 deletions src/lib/error.js → src/lib/errors/legacy-errors.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
var config = require('../lib/config');
var chalk = require('chalk');
var SEVERITIES = require('./snyk-test/common').SEVERITIES;

var errors = {
const config = require('../config');
const chalk = require('chalk');
const {SEVERITIES} = require('../snyk-test/common');

const errors = {
connect: 'Check your network connection, failed to connect to Snyk API',
endpoint: 'The Snyk API is not available on ' + config.API,
auth: 'Unauthorized: please ensure you are logged in using `snyk auth`',
Expand Down Expand Up @@ -37,12 +38,12 @@ var errors = {
idRequired: 'id is a required field for `snyk ignore`',
unknownCommand: '%s\n\nRun `snyk --help` for a list of available commands.',
invalidSeverityThreshold: 'Invalid severity threshold, please use one of ' +
SEVERITIES.map(s => s.verboseName).join(' | '),
SEVERITIES.map((s) => s.verboseName).join(' | '),
};

// a key/value pair of error.code (or error.message) as the key, and our nice
// strings as the value.
var codes = {
const codes = {
ECONNREFUSED: errors.connect,
ENOTFOUND: errors.connect,
NOT_FOUND: errors.notfound,
Expand All @@ -66,13 +67,13 @@ var codes = {
};

module.exports = function error(command) {
var e = new Error('Unknown command "' + command + '"');
const e = new Error('Unknown command "' + command + '"');
e.code = 'UNKNOWN_COMMAND';
return Promise.reject(e);
};

module.exports.message = function (error) {
var message = error; // defaults to a string (which is super unlikely)
module.exports.message = function(error) {
let message = error; // defaults to a string (which is super unlikely)
if (error instanceof Error) {
if (error.code === 'VULNS') {
return error.message;
Expand Down
18 changes: 18 additions & 0 deletions src/lib/errors/no-supported-manifests-found.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import chalk from 'chalk';
import {CustomError} from './custom-error';

export function NoSupportedManifestsFoundError(atLocations: string[]) {
const locationsStr = atLocations.join(', ');
const errorMsg = 'Could not detect supported target files in ' + locationsStr +
'.\nPlease see our documentation for supported languages and ' +
'target files: ' +
chalk.underline(
'https://support.snyk.io/hc/en-us/articles/360000911957-Language-support',
) +
' and make sure you are in the right directory.';

const error = new CustomError(errorMsg);
error.code = 422;
error.userMessage = errorMsg;
return error;
}
2 changes: 1 addition & 1 deletion src/lib/protect/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var getVulnSource = require('./get-vuln-source');
var dedupe = require('./dedupe-patches');
var writePatchFlag = require('./write-patch-flag');
var spinner = require('../spinner');
var errors = require('../error');
var errors = require('../errors/legacy-errors');
var analytics = require('../analytics');
var getPatchFile = require('./fetch-patch');

Expand Down
2 changes: 1 addition & 1 deletion src/lib/protect/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var chalk = require('chalk');
var _ = require('lodash');
var moduleToObject = require('snyk-module');
var semver = require('semver');
var errors = require('../error');
var errors = require('../errors/legacy-errors');
var npm = require('../npm');
var yarn = require('../yarn');
var spinner = require('../spinner');
Expand Down
82 changes: 41 additions & 41 deletions src/lib/sln/index.js → src/lib/sln/index.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,68 @@
var fs = require('fs');
var path = require('path');
var debug = require('debug')('snyk');
var detect = require('../detect');
import * as fs from 'fs';
import * as path from 'path';
import * as detect from '../detect';
import {NoSupportedManifestsFoundError} from '../errors/no-supported-manifests-found';
import * as Debug from 'debug';

var sln = {};

sln.updateArgs = function (args) {
// save the path if --file=path/file.sln
var slnFilePath = path.dirname(args.options.file);

// extract all referenced projects from solution
// keep only those that contain relevant manifest files
var projectFolders = sln.parsePathsFromSln(args.options.file)
.map(function (projectPath) {
var projectFolder =
path.resolve(slnFilePath, path.dirname(projectPath));
var manifestFile = detect.detectPackageFile(projectFolder);
return manifestFile ? projectFolder : undefined;
})
.filter(Boolean);

debug('valid project folders in solution: ', projectFolders);

if (projectFolders.length === 0) {
throw new Error('No relevant projects found in Solution');
}

// delete the file option as the solution has now been parsed
delete (args.options.file);

// mutates args!
addProjectFoldersToArgs(args, projectFolders);
};
const debug = Debug('snyk');

// slnFile should exist.
// returns array of project paths (path/to/manifest.file)
sln.parsePathsFromSln = function (slnFile) {
export const parsePathsFromSln = (slnFile) => {
// read project scopes from solution file
// [\s\S] is like ., but with newlines!
// *? means grab the shortest match
var projectScopes =
const projectScopes =
loadFile(path.resolve(slnFile)).match(/Project[\s\S]*?EndProject/g) || [];

var paths = projectScopes.map(function (projectScope) {
var secondArg = projectScope.split(',')[1];
const paths = projectScopes.map((projectScope) => {
const secondArg = projectScope.split(',')[1];
// expected ` "path/to/manifest.file"`, clean it up
return secondArg && secondArg.trim().replace(/\"/g, '');
})
// drop falsey values
.filter(Boolean)
// convert path separators
.map(function (projectPath) {
.map((projectPath) => {
return projectPath.replace(/\\/g, path.sep);
});

debug('extracted paths from solution file: ', paths);
return paths;
};

export const updateArgs = (args) => {
// save the path if --file=path/file.sln
const slnFilePath = path.dirname(args.options.file);

// extract all referenced projects from solution
// keep only those that contain relevant manifest files
const projectFolders = parsePathsFromSln(args.options.file);

const foldersWithSupportedProjects = projectFolders
.map((projectPath) => {
const projectFolder =
path.resolve(slnFilePath, path.dirname(projectPath));
const manifestFile = detect.detectPackageFile(projectFolder);
return manifestFile ? projectFolder : undefined; })
.filter(Boolean);

debug('valid project folders in solution: ', projectFolders);

if (foldersWithSupportedProjects.length === 0) {
throw NoSupportedManifestsFoundError([...projectFolders]);
}

// delete the file option as the solution has now been parsed
delete (args.options.file);

// mutates args!
addProjectFoldersToArgs(args, foldersWithSupportedProjects);
};

function addProjectFoldersToArgs(args, projectFolders) {
// keep the last arg (options) aside for later use
var lastArg = args.options._.pop();
const lastArg = args.options._.pop();
// add relevant project paths as if they were given as a runtime path args
args.options._ = args.options._.concat(projectFolders);
// bring back the last (options) arg
Expand All @@ -74,5 +76,3 @@ function loadFile(filePath) {
}
return fs.readFileSync(filePath, 'utf8');
}

module.exports = sln;
7 changes: 6 additions & 1 deletion src/lib/snyk-test/run-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {DepTree} from '../types';
import gemfileLockToDependencies = require('../../lib/plugins/rubygems/gemfile-lock-to-dependencies');
import {convertTestDepGraphResultToLegacy, AnnotatedIssue, LegacyVulnApiResult, TestDepGraphResponse} from './legacy';
import {SingleDepRootResult, MultiDepRootsResult, isMultiResult, TestOptions} from '../types';
import { NoSupportedManifestsFoundError } from '../errors';

// tslint:disable-next-line:no-var-requires
const debug = require('debug')('snyk');
Expand Down Expand Up @@ -191,6 +192,9 @@ function assemblePayloads(root: string, options): Promise<Payload[]> {
// Force getDepsFromPlugin to return depRoots for processing in assembleLocalPayload
async function getDepsFromPlugin(root, options: TestOptions): Promise<MultiDepRootsResult> {
options.file = options.file || detect.detectPackageFile(root);
if (!options.docker && !(options.file || options.packageManager)) {
throw NoSupportedManifestsFoundError([...root]);
}
const plugin = plugins.loadPlugin(options.packageManager, options);
const moduleInfo = ModuleInfo(plugin, options.policy);
const pluginOptions = plugins.getPluginOptions(options.packageManager, options);
Expand Down Expand Up @@ -324,7 +328,8 @@ async function assembleLocalPayloads(root, options): Promise<Payload[]> {
};

if (['yarn', 'npm'].indexOf(options.packageManager) !== -1) {
const isLockFileBased = options.file.endsWith('package-lock.json') || options.file.endsWith('yarn.lock');
const isLockFileBased = options.file
&& options.file.endsWith('package-lock.json') || options.file.endsWith('yarn.lock');
if (!isLockFileBased || options.traverseNodeModules) {
payload.modules = pkg as DepTreeFromResolveDeps; // See the output of resolve-deps
}
Expand Down
17 changes: 15 additions & 2 deletions test/acceptance/sln-app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var path = require('path');
var sln = require('../../src/lib/sln');

test('parseFoldersFromSln when passed an existant filename', function (t) {
var slnFile = 'test/acceptance/workspaces/sln-example-app/mySolution.sln';
var slnFile = 'test/acceptance/workspaces/sln-example-app/mySolution.sln';
var expected = JSON.stringify([
"dotnet2_new_mvc_project/new_mvc_project.csproj",
"WebApplication2/WebApplication2.csproj"
Expand All @@ -15,7 +15,7 @@ test('parseFoldersFromSln when passed an existant filename', function (t) {

test('parseFoldersFromSln when non existant filename', function (t) {
var response;
var slnFile = 'test/acceptance/workspaces/sln-example-app/mySolution1.sln';
var slnFile = 'test/acceptance/workspaces/sln-example-app/mySolution1.sln';
try {
response = sln.parsePathsFromSln(slnFile);
t.fail('an exception should be thrown');
Expand All @@ -27,6 +27,19 @@ test('parseFoldersFromSln when non existant filename', function (t) {
t.end();
});

test('parseFoldersFromSln when no supported files found', function (t) {
let response;
const slnFile = 'test/acceptance/workspaces/sln-no-supported-files/mySolution1.sln';
try {
response = sln.parsePathsFromSln(slnFile);
t.fail('an exception should be thrown');
} catch (e) {
t.match(e.message, 'File not found: ', 'should throw exception');
t.equal(response, undefined, 'shouldnt return');
}
t.end();
});

test('sln.updateArgs for existing sln with regular paths', function (t) {
var args = {options: {
file: 'test/acceptance/workspaces/sln-example-app/mySolution.sln', _: []}};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "new_mvc_project", "dotnet2_new_mvc_project\new_mvc_project.csproj", "{26B9B59B-C5AC-49CE-9BD6-4C72940DAC89}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication2", "WebApplication2\WebApplication2.csproj", "{9A79CF26-C7E8-47A3-B718-C5E654AA2701}"
EndProject
Global
EndGlobal
4 changes: 2 additions & 2 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ test('snyk ignore - no ID', function (t) {
}).then(function (res) {
t.fail('should not succeed with missing ID');
}).catch(function (e) {
var errors = require('../src/lib/error');
var errors = require('../src/lib/errors/legacy-errors');
var message = stripAnsi(errors.message(e));
t.equal(message.toLowerCase().indexOf('id is a required field'), 0,
'captured failed ignore (no --id given)');
Expand Down Expand Up @@ -274,7 +274,7 @@ test('auth via key', function (t) {
test('auth via invalid key', function (t) {
t.plan(1);

var errors = require('../src/lib/error');
var errors = require('../src/lib/errors/legacy-errors');

cli.auth('_____________').then(function (res) {
t.fail('auth should not succeed: ' + res);
Expand Down

0 comments on commit 49c7fb9

Please sign in to comment.