Merge pull request #1806 from balena-io/errors-improvements

Improve presentation of errors, help
This commit is contained in:
srlowe 2020-05-06 13:09:52 +02:00 committed by GitHub
commit c2561938c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 57 deletions

View File

@ -73,8 +73,8 @@ const manuallySortedPrimaryCommands = [
const general = function(_params, options, done) { const general = function(_params, options, done) {
console.log('Usage: balena [COMMAND] [OPTIONS]\n'); console.log('Usage: balena [COMMAND] [OPTIONS]\n');
console.log(messages.reachingOut);
console.log('\nPrimary commands:\n'); console.log('Primary commands:\n');
// We do not want the wildcard command // We do not want the wildcard command
// to be printed in the help screen. // to be printed in the help screen.
@ -123,6 +123,9 @@ const general = function(_params, options, done) {
console.log('\nGlobal Options:\n'); console.log('\nGlobal Options:\n');
print(parse(capitano.state.globalOptions).sort()); print(parse(capitano.state.globalOptions).sort());
} }
console.log(indent('--debug\n'));
console.log(messages.help);
return done(); return done();
}) })
@ -152,6 +155,8 @@ const commandHelp = (params, _options, done) =>
print(parse(command.options).sort()); print(parse(command.options).sort());
} }
console.log();
return done(); return done();
}); });

View File

@ -28,7 +28,7 @@ export class NotLoggedInError extends ExpectedError {}
export class InsufficientPrivilegesError extends ExpectedError {} export class InsufficientPrivilegesError extends ExpectedError {}
/** /**
* instanceOf is a more reliable implemention of the plain `instanceof` * instanceOf is a more reliable implementation of the plain `instanceof`
* typescript operator, for use with TypedError errors when the error * typescript operator, for use with TypedError errors when the error
* classes may be defined in external packages/dependencies. * classes may be defined in external packages/dependencies.
* Sample usage: * Sample usage:
@ -126,56 +126,81 @@ const messages: {
`, `,
BalenaExpiredToken: () => stripIndent` BalenaExpiredToken: () => stripIndent`
Looks like your session token is expired. Looks like your session token has expired.
Please try logging in again with: Please try logging in again with:
$ balena login`, $ balena login`,
}; };
const EXPECTED_ERROR_REGEXES = [
/^BalenaApplicationNotFound:/, // balena-sdk
/^BalenaDeviceNotFound:/, // balena-sdk
/^Missing \w+$/, // Capitano, oclif parser: RequiredArgsError, RequiredFlagError
/^Unexpected arguments?:/, // oclif parser: UnexpectedArgsError
/to be one of/, // oclif parser: FlagInvalidOptionError, ArgInvalidOptionError
];
export async function handleError(error: any) { export async function handleError(error: any) {
// Set appropriate exitCode
process.exitCode = process.exitCode =
error.exitCode === 0 error.exitCode === 0
? 0 ? 0
: parseInt(error.exitCode, 10) || process.exitCode || 1; : parseInt(error.exitCode, 10) || process.exitCode || 1;
// Handle non-Error objects (probably strings)
if (!(error instanceof Error)) { if (!(error instanceof Error)) {
printErrorMessage(String(error)); printErrorMessage(String(error));
return; return;
} }
// Prepare message
const message = [interpret(error)]; const message = [interpret(error)];
if (process.env.DEBUG && error.stack) { if (error.stack) {
message.push('\n' + error.stack); if (process.env.DEBUG) {
message.push('\n' + error.stack);
} else {
// Include first line of stacktrace
message.push('\n' + error.stack.split(`\n`)[0]);
}
} }
printErrorMessage(message.join('\n'));
const expectedErrorREs = [ // Expected?
/^BalenaApplicationNotFound:/, // balena-sdk const isExpectedError =
/^BalenaDeviceNotFound:/, // balena-sdk
/^Missing \w+$/, // Capitano's command line parsing error
/^Unexpected arguments?:/, // oclif's command line parsing error
];
if (
error instanceof ExpectedError || error instanceof ExpectedError ||
expectedErrorREs.some(re => re.test(message[0])) EXPECTED_ERROR_REGEXES.some(re => re.test(message[0]));
) {
return;
}
// Report "unexpected" errors via Sentry.io // Output/report error
const Sentry = await import('@sentry/node'); if (isExpectedError) {
Sentry.captureException(error); printExpectedErrorMessage(message.join('\n'));
await Sentry.close(1000); } else {
// Unhandled/unexpected error: ensure that the process terminates. printErrorMessage(message.join('\n'));
// The exit error code was set above through `process.exitCode`.
process.exit(); // Report "unexpected" errors via Sentry.io
const Sentry = await import('@sentry/node');
Sentry.captureException(error);
await Sentry.close(1000);
// Unhandled/unexpected error: ensure that the process terminates.
// The exit error code was set above through `process.exitCode`.
process.exit();
}
} }
export function printErrorMessage(message: string) { export function printErrorMessage(message: string) {
const chalk = getChalk(); const chalk = getChalk();
console.error(chalk.red(message));
console.error(chalk.red(`\n${getHelp}\n`)); // Only first line should be red
const messageLines = message.split('\n');
console.error(chalk.red(messageLines.shift()));
messageLines.forEach(line => {
console.error(line);
});
console.error(`\n${getHelp}\n`);
}
export function printExpectedErrorMessage(message: string) {
console.error(`${message}\n`);
} }
/** /**

View File

@ -9,19 +9,16 @@ create a new one at: https://github.com/balena-io/balena-cli/issues/\
`; `;
const debugHint = `\ const debugHint = `\
Additional information may be available by setting a DEBUG=1 environment Additional information may be available with the \`--debug\` flag.
variable: "set DEBUG=1" on a Windows command prompt, "$env:DEBUG = 1" on
powershell, or "export DEBUG=1" on Linux or macOS.\n
`; `;
export const getHelp = `${DEBUG_MODE ? '' : debugHint}\ export const help = `\
If you need help, don't hesitate in contacting our support forums at For help, visit our support forums: https://forums.balena.io
https://forums.balena.io For bug reports or feature requests, see: https://github.com/balena-io/balena-cli/issues/
For CLI bug reports or feature requests, have a look at the GitHub issues or
create a new one at: https://github.com/balena-io/balena-cli/issues/\
`; `;
export const getHelp = (DEBUG_MODE ? '' : debugHint) + help;
export const balenaAsciiArt = `\ export const balenaAsciiArt = `\
_ _ _ _
| |__ __ _ | | ____ _ __ __ _ | |__ __ _ | | ____ _ __ __ _

View File

@ -4,12 +4,6 @@ import { cleanOutput, runCommand } from '../helpers';
const SIMPLE_HELP = ` const SIMPLE_HELP = `
Usage: balena [COMMAND] [OPTIONS] Usage: balena [COMMAND] [OPTIONS]
If you need help, or just want to say hi, don't hesitate in reaching out
through our discussion and support forums at https://forums.balena.io
For bug reports or feature requests, have a look at the GitHub issues or
create a new one at: https://github.com/balena-io/balena-cli/issues/
Primary commands: Primary commands:
help [command...] show help help [command...] show help
@ -84,33 +78,33 @@ Additional commands:
`; `;
const LIST_ADDITIONAL = `
Run \`balena help --verbose\` to list additional commands
`;
const GLOBAL_OPTIONS = ` const GLOBAL_OPTIONS = `
Global Options: Global Options:
--help, -h --help, -h
--version, -v --version, -v
--debug
`;
const ONLINE_RESOURCES = `
For help, visit our support forums: https://forums.balena.io
For bug reports or feature requests, see: https://github.com/balena-io/balena-cli/issues/
`; `;
describe('balena help', function() { describe('balena help', function() {
it('should list primary command summaries', async () => { it('should list primary command summaries', async () => {
const { out, err } = await runCommand('help'); const { out, err } = await runCommand('help');
console.log('ONE');
console.log(cleanOutput(out));
console.log(
cleanOutput([
SIMPLE_HELP,
'Run `balena help --verbose` to list additional commands',
GLOBAL_OPTIONS,
]),
);
console.log();
expect(cleanOutput(out)).to.deep.equal( expect(cleanOutput(out)).to.deep.equal(
cleanOutput([ cleanOutput([
SIMPLE_HELP, SIMPLE_HELP,
'Run `balena help --verbose` to list additional commands', LIST_ADDITIONAL,
GLOBAL_OPTIONS, GLOBAL_OPTIONS,
ONLINE_RESOURCES,
]), ]),
); );
@ -121,7 +115,12 @@ describe('balena help', function() {
const { out, err } = await runCommand('help -v'); const { out, err } = await runCommand('help -v');
expect(cleanOutput(out)).to.deep.equal( expect(cleanOutput(out)).to.deep.equal(
cleanOutput([SIMPLE_HELP, ADDITIONAL_HELP, GLOBAL_OPTIONS]), cleanOutput([
SIMPLE_HELP,
ADDITIONAL_HELP,
GLOBAL_OPTIONS,
ONLINE_RESOURCES,
]),
); );
expect(err.join('')).to.equal(''); expect(err.join('')).to.equal('');
@ -135,8 +134,9 @@ describe('balena help', function() {
expect(cleanOutput(out)).to.deep.equal( expect(cleanOutput(out)).to.deep.equal(
cleanOutput([ cleanOutput([
SIMPLE_HELP, SIMPLE_HELP,
'Run `balena help --verbose` to list additional commands', LIST_ADDITIONAL,
GLOBAL_OPTIONS, GLOBAL_OPTIONS,
ONLINE_RESOURCES,
]), ]),
); );