mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 15:27:17 +00:00
feat(CLI): improve error messages (#1015)
- Inline dictionary of common error codes and friendly descriptions We used to rely on `resin-cli-errors` for this, however the friendly descriptions set there can be usually very Resin CLI specific, and thus confusing to Etcher users. The dictionary, along with other error related utilities live in `lib/cli/errors.js`. - Print error descriptions and codes if they are found - Move `utils.printError()` to `errors.print()` Change-Type: minor Changelog-Entry: Improve Etcher CLI error messages. Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
parent
6c8ddcea07
commit
c707c76633
@ -19,7 +19,7 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const yargs = require('yargs');
|
||||
const utils = require('./utils');
|
||||
const errors = require('./errors');
|
||||
const robot = require('../shared/robot');
|
||||
const EXIT_CODES = require('../shared/exit-codes');
|
||||
const packageJSON = require('../../package.json');
|
||||
@ -69,7 +69,7 @@ module.exports = yargs
|
||||
robot.printError(error || message);
|
||||
} else {
|
||||
yargs.showHelp();
|
||||
utils.printError(error || message);
|
||||
errors.print(error || message);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
|
130
lib/cli/errors.js
Normal file
130
lib/cli/errors.js
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* @summary Human-friendly error messages
|
||||
* @namespace HUMAN_FRIENDLY
|
||||
* @public
|
||||
*/
|
||||
exports.HUMAN_FRIENDLY = {
|
||||
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
/**
|
||||
* @property {Function} ENOENT
|
||||
* @memberof HUMAN_FRIENDLY
|
||||
* @param {Error} error - error object
|
||||
* @returns {String} message
|
||||
*/
|
||||
ENOENT: (error) => {
|
||||
return `No such file or directory: ${error.path}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @property {Function} EPERM
|
||||
* @memberof HUMAN_FRIENDLY
|
||||
* @returns {String} message
|
||||
*/
|
||||
EPERM: () => {
|
||||
return 'You\'re not authorized to perform this operation';
|
||||
},
|
||||
|
||||
/**
|
||||
* @property {Function} EACCES
|
||||
* @memberof HUMAN_FRIENDLY
|
||||
* @returns {String} message
|
||||
*/
|
||||
EACCES: () => {
|
||||
return 'You\'re don\'t have access to this resource';
|
||||
}
|
||||
|
||||
/* eslint-enable new-cap */
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get default error message
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {Error} error - error
|
||||
* @returns {String} error message
|
||||
*
|
||||
* @example
|
||||
* const message = defaultMessageGetter(new Error('foo bar'));
|
||||
* console.log(message);
|
||||
* > 'foo bar'
|
||||
*
|
||||
* @example
|
||||
* const message = defaultMessageGetter(new Error());
|
||||
* console.log(message);
|
||||
* > 'Unknown error'
|
||||
*/
|
||||
const defaultMessageGetter = (error) => {
|
||||
return error.message || 'Unknown error';
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get error message
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {(String|Error)} error - error
|
||||
* @returns {String} error message
|
||||
*
|
||||
* @example
|
||||
* const error = new Error('Foo bar');
|
||||
* error.description = 'This is a fake error';
|
||||
*
|
||||
* console.log(errors.getErrorMessage(error));
|
||||
* > 'Foo bar\n\nThis is a fake error'
|
||||
*/
|
||||
exports.getErrorMessage = (error) => {
|
||||
if (_.isString(error)) {
|
||||
return exports.getErrorMessage(new Error(error));
|
||||
}
|
||||
|
||||
const message = _.attempt(() => {
|
||||
const title = _.get(exports.HUMAN_FRIENDLY, error.code, defaultMessageGetter)(error);
|
||||
return error.code ? `${error.code}: ${title}` : title;
|
||||
});
|
||||
|
||||
if (error.description) {
|
||||
return message + '\n\n' + error.description;
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Print an error to stderr
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {(Error|String)} error - error
|
||||
*
|
||||
* @example
|
||||
* errors.print(new Error('Oops!'));
|
||||
*/
|
||||
exports.print = (error) => {
|
||||
const message = exports.getErrorMessage(error);
|
||||
console.error(chalk.red(message));
|
||||
};
|
@ -23,7 +23,7 @@ const visuals = require('resin-cli-visuals');
|
||||
const form = require('resin-cli-form');
|
||||
const drivelist = Bluebird.promisifyAll(require('drivelist'));
|
||||
const writer = require('./writer');
|
||||
const utils = require('./utils');
|
||||
const errors = require('./errors');
|
||||
const options = require('./cli');
|
||||
const robot = require('../shared/robot');
|
||||
const EXIT_CODES = require('../shared/exit-codes');
|
||||
@ -119,7 +119,7 @@ isElevated().then((elevated) => {
|
||||
return robot.printError(error);
|
||||
}
|
||||
|
||||
utils.printError(error);
|
||||
errors.print(error);
|
||||
}).then(() => {
|
||||
if (error.code === 'EVALIDATION') {
|
||||
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const errors = require('resin-cli-errors');
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* @summary Print an error to stderr
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {(Error|String)} error - error
|
||||
*
|
||||
* @example
|
||||
* utils.printError(new Error('Oops!'));
|
||||
*/
|
||||
exports.printError = (error) => {
|
||||
if (_.isString(error)) {
|
||||
error = new Error(error);
|
||||
}
|
||||
|
||||
console.error(chalk.red(errors.interpret(error)));
|
||||
};
|
12
npm-shrinkwrap.json
generated
12
npm-shrinkwrap.json
generated
@ -1417,18 +1417,6 @@
|
||||
"from": "require-uncached@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.2.tgz"
|
||||
},
|
||||
"resin-cli-errors": {
|
||||
"version": "1.2.0",
|
||||
"from": "resin-cli-errors@>=1.2.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resin-cli-errors/-/resin-cli-errors-1.2.0.tgz",
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"from": "lodash@>=3.10.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"resin-cli-form": {
|
||||
"version": "1.4.1",
|
||||
"from": "resin-cli-form@>=1.4.1 <2.0.0",
|
||||
|
@ -83,7 +83,6 @@
|
||||
"node-ipc": "^8.9.2",
|
||||
"redux": "^3.5.2",
|
||||
"redux-localstorage": "^0.4.1",
|
||||
"resin-cli-errors": "^1.2.0",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.2.8",
|
||||
"rx": "^4.1.0",
|
||||
|
132
tests/cli/errors.spec.js
Normal file
132
tests/cli/errors.spec.js
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const m = require('mochainon');
|
||||
const _ = require('lodash');
|
||||
const errors = require('../../lib/cli/errors');
|
||||
|
||||
describe('CLI: Errors', function() {
|
||||
|
||||
describe('.HUMAN_FRIENDLY', function() {
|
||||
|
||||
it('should be a plain object', function() {
|
||||
m.chai.expect(_.isPlainObject(errors.HUMAN_FRIENDLY)).to.be.true;
|
||||
});
|
||||
|
||||
it('should contain function properties', function() {
|
||||
m.chai.expect(_.every(_.map(errors.HUMAN_FRIENDLY, _.isFunction))).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.getErrorMessage()', function() {
|
||||
|
||||
describe('given errors without code properties', function() {
|
||||
|
||||
it('should understand a string error', function() {
|
||||
const error = 'foo bar';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal('foo bar');
|
||||
});
|
||||
|
||||
it('should return a generic error message if there is none', function() {
|
||||
const error = new Error();
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal('Unknown error');
|
||||
});
|
||||
|
||||
it('should return a generic error message if error is an empty string', function() {
|
||||
const error = '';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal('Unknown error');
|
||||
});
|
||||
|
||||
it('should return the error message', function() {
|
||||
const error = new Error('foo bar');
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal('foo bar');
|
||||
});
|
||||
|
||||
it('should make use of a description if there is one', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.description = 'This is a description';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(_.join([
|
||||
'foo bar',
|
||||
'',
|
||||
'This is a description'
|
||||
], '\n'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given errors with code properties', function() {
|
||||
|
||||
it('should provide a friendly message for ENOENT', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.code = 'ENOENT';
|
||||
error.path = 'foo.bar';
|
||||
const message = 'ENOENT: No such file or directory: foo.bar';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(message);
|
||||
});
|
||||
|
||||
it('should provide a friendly message for EPERM', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.code = 'EPERM';
|
||||
const message = 'EPERM: You\'re not authorized to perform this operation';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(message);
|
||||
});
|
||||
|
||||
it('should provide a friendly message for EACCES', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.code = 'EACCES';
|
||||
const message = 'EACCES: You\'re don\'t have access to this resource';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(message);
|
||||
});
|
||||
|
||||
it('should make use of a description if there is one', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.code = 'EPERM';
|
||||
error.description = 'This is the EPERM description';
|
||||
|
||||
const message = _.join([
|
||||
'EPERM: You\'re not authorized to perform this operation',
|
||||
'',
|
||||
'This is the EPERM description'
|
||||
], '\n');
|
||||
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(message);
|
||||
});
|
||||
|
||||
describe('given the code is not recognised', function() {
|
||||
|
||||
it('should make use of the error message', function() {
|
||||
const error = new Error('foo bar');
|
||||
error.code = 'EFOO';
|
||||
const message = 'EFOO: foo bar';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal(message);
|
||||
});
|
||||
|
||||
it('should return a generic error message if no there is no message', function() {
|
||||
const error = new Error();
|
||||
error.code = 'EFOO';
|
||||
m.chai.expect(errors.getErrorMessage(error)).to.equal('EFOO: Unknown error');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user