From 9b1c3c665b90f2f67af6342d8f0502f92fd0485d Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Thu, 30 Apr 2020 10:45:16 +0200 Subject: [PATCH] Refactor: move error related functions into error module Change-type: patch Signed-off-by: Scott Lowe --- lib/actions/auth.ts | 5 +++-- lib/actions/config.js | 2 +- lib/actions/help.js | 2 +- lib/actions/local/common.js | 2 +- lib/actions/logs.ts | 5 ++--- lib/actions/preload.js | 6 +++--- lib/actions/tags.ts | 6 +++--- lib/app-capitano.js | 2 +- lib/errors.ts | 34 ++++++++++++++++++++++++++++++---- lib/preparser.ts | 2 +- lib/utils/device/deploy.ts | 6 +++--- lib/utils/docker-js.js | 2 +- lib/utils/patterns.ts | 31 ++----------------------------- lib/utils/promote.ts | 7 ++++--- lib/utils/remote-build.ts | 2 +- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/lib/actions/auth.ts b/lib/actions/auth.ts index 9f2de12e..24201f99 100644 --- a/lib/actions/auth.ts +++ b/lib/actions/auth.ts @@ -87,6 +87,7 @@ Examples: const balena = getBalenaSdk(); const patterns = await import('../utils/patterns'); const messages = await import('../utils/messages'); + const { exitWithExpectedError } = await import('../errors'); const doLogin = async (loginOptions: Options): Promise => { if (loginOptions.token != null) { @@ -103,7 +104,7 @@ Examples: } await balena.auth.loginWithToken(token); if (!(await balena.auth.whoami())) { - patterns.exitWithExpectedError('Token authentication failed'); + exitWithExpectedError('Token authentication failed'); } return; } else if (loginOptions.credentials) { @@ -120,7 +121,7 @@ Examples: const signupUrl = 'https://dashboard.balena-cloud.com/signup'; const open = await import('open'); open(signupUrl, { wait: false }); - return patterns.exitWithExpectedError(`Please sign up at ${signupUrl}`); + return exitWithExpectedError(`Please sign up at ${signupUrl}`); } loginOptions[loginType] = true; diff --git a/lib/actions/config.js b/lib/actions/config.js index fb7c1cd2..97faef9a 100644 --- a/lib/actions/config.js +++ b/lib/actions/config.js @@ -322,7 +322,7 @@ Examples: generateApplicationConfig, } = require('../utils/config'); const helpers = require('../utils/helpers'); - const { exitWithExpectedError } = require('../utils/patterns'); + const { exitWithExpectedError } = require('../errors'); if (options.device == null && options.application == null) { exitWithExpectedError(`\ diff --git a/lib/actions/help.js b/lib/actions/help.js index f9c69ef5..c8358bc7 100644 --- a/lib/actions/help.js +++ b/lib/actions/help.js @@ -20,7 +20,7 @@ import * as capitano from 'capitano'; import * as columnify from 'columnify'; import * as messages from '../utils/messages'; import { getManualSortCompareFunction } from '../utils/helpers'; -import { exitWithExpectedError } from '../utils/patterns'; +import { exitWithExpectedError } from '../errors'; import { getOclifHelpLinePairs } from './help_ts'; const parse = object => diff --git a/lib/actions/local/common.js b/lib/actions/local/common.js index b534cdd1..44476141 100644 --- a/lib/actions/local/common.js +++ b/lib/actions/local/common.js @@ -1,7 +1,7 @@ import * as Promise from 'bluebird'; import * as _ from 'lodash'; import * as dockerUtils from '../../utils/docker'; -import { exitWithExpectedError } from '../../utils/patterns'; +import { exitWithExpectedError } from '../../errors'; import { getChalk } from '../../utils/lazy'; export const dockerPort = 2375; diff --git a/lib/actions/logs.ts b/lib/actions/logs.ts index 52abc2d6..913a9d23 100644 --- a/lib/actions/logs.ts +++ b/lib/actions/logs.ts @@ -91,9 +91,8 @@ export const logs: CommandDefinition< '../utils/device/logs' ); const { validateIPAddress } = await import('../utils/validation'); - const { checkLoggedIn, exitWithExpectedError } = await import( - '../utils/patterns' - ); + const { checkLoggedIn } = await import('../utils/patterns'); + const { exitWithExpectedError } = await import('../errors'); const Logger = await import('../utils/logger'); const logger = Logger.getLogger(); diff --git a/lib/actions/preload.js b/lib/actions/preload.js index 544a74c6..a3993a1b 100644 --- a/lib/actions/preload.js +++ b/lib/actions/preload.js @@ -87,7 +87,7 @@ const getApplicationsWithSuccessfulBuilds = function(deviceType) { const selectApplication = function(deviceType) { const visuals = getVisuals(); const form = require('resin-cli-form'); - const { exitWithExpectedError } = require('../utils/patterns'); + const { exitWithExpectedError } = require('../errors'); const applicationInfoSpinner = new visuals.Spinner( 'Downloading list of applications and releases.', @@ -116,7 +116,7 @@ const selectApplication = function(deviceType) { const selectApplicationCommit = function(releases) { const form = require('resin-cli-form'); - const { exitWithExpectedError } = require('../utils/patterns'); + const { exitWithExpectedError } = require('../errors'); if (releases.length === 0) { exitWithExpectedError('This application has no successful releases.'); @@ -263,7 +263,7 @@ Examples: const balenaPreload = require('balena-preload'); const visuals = getVisuals(); const nodeCleanup = require('node-cleanup'); - const { exitWithExpectedError } = require('../utils/patterns'); + const { exitWithExpectedError } = require('../errors'); const progressBars = {}; diff --git a/lib/actions/tags.ts b/lib/actions/tags.ts index 700ef8e4..7d1756ee 100644 --- a/lib/actions/tags.ts +++ b/lib/actions/tags.ts @@ -60,7 +60,7 @@ export const list: CommandDefinition< const _ = await import('lodash'); const balena = getBalenaSdk(); - const { exitWithExpectedError } = await import('../utils/patterns'); + const { exitWithExpectedError } = await import('../errors'); return Bluebird.try( async () => { @@ -161,7 +161,7 @@ export const set: CommandDefinition< const _ = await import('lodash'); const balena = getBalenaSdk(); - const { exitWithExpectedError } = await import('../utils/patterns'); + const { exitWithExpectedError } = await import('../errors'); if (_.isEmpty(params.tagKey)) { return exitWithExpectedError('No tag key was provided'); @@ -250,7 +250,7 @@ export const remove: CommandDefinition< async action(params, options) { const _ = await import('lodash'); const balena = getBalenaSdk(); - const { exitWithExpectedError } = await import('../utils/patterns'); + const { exitWithExpectedError } = await import('../errors'); if (_.isEmpty(params.tagKey)) { return exitWithExpectedError('No tag key was provided'); diff --git a/lib/app-capitano.js b/lib/app-capitano.js index af66af52..a4c7f63a 100644 --- a/lib/app-capitano.js +++ b/lib/app-capitano.js @@ -22,7 +22,7 @@ import * as events from './events'; capitano.permission('user', done => require('./utils/patterns') - .exitIfNotLoggedIn() + .checkLoggedIn() .then(done, done), ); diff --git a/lib/errors.ts b/lib/errors.ts index ddd2f21b..c35dedff 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -1,5 +1,5 @@ /* -Copyright 2016-2020 Balena +Copyright 2016-2020 Balena Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import { stripIndent } from 'common-tags'; import * as _ from 'lodash'; import * as os from 'os'; import { TypedError } from 'typed-error'; +import { getChalk } from './utils/lazy'; +import { getHelp } from './utils/messages'; export class ExpectedError extends TypedError {} @@ -130,8 +132,6 @@ const messages: { }; export async function handleError(error: any) { - const { printErrorMessage } = await import('./utils/patterns'); - process.exitCode = error.exitCode === 0 ? 0 @@ -145,7 +145,7 @@ export async function handleError(error: any) { const message = [interpret(error)]; if (process.env.DEBUG && error.stack) { - message.push(error.stack); + message.push('\n' + error.stack); } printErrorMessage(message.join('\n')); @@ -171,3 +171,29 @@ export async function handleError(error: any) { // The exit error code was set above through `process.exitCode`. process.exit(); } + +export function printErrorMessage(message: string) { + const chalk = getChalk(); + console.error(chalk.red(message)); + console.error(chalk.red(`\n${getHelp}\n`)); +} + +/** + * Print a friendly error message and exit the CLI with an error code, BYPASSING + * error reporting through Sentry.io's platform (raven.Raven.captureException). + * Note that lib/errors.ts provides top-level error handling code to catch any + * otherwise uncaught errors, AND to report them through Sentry.io. But many + * "expected" errors (say, a JSON parsing error in a file provided by the user) + * don't warrant reporting through Sentry.io. For such mundane errors, catch + * them and call this function. + * + * DEPRECATED: Use `throw new ExpectedError()` instead. + */ +export function exitWithExpectedError(message: string | Error): never { + if (message instanceof Error) { + ({ message } = message); + } + + printErrorMessage(message); + process.exit(1); +} diff --git a/lib/preparser.ts b/lib/preparser.ts index 5c1497ee..8cc0bed7 100644 --- a/lib/preparser.ts +++ b/lib/preparser.ts @@ -16,7 +16,7 @@ */ import { stripIndent } from 'common-tags'; -import { exitWithExpectedError } from './utils/patterns'; +import { exitWithExpectedError } from './errors'; export interface AppOptions { // Prevent the default behavior of flushing stdout after running a command diff --git a/lib/utils/device/deploy.ts b/lib/utils/device/deploy.ts index 1d5e9a1a..6acfccc6 100644 --- a/lib/utils/device/deploy.ts +++ b/lib/utils/device/deploy.ts @@ -74,7 +74,7 @@ async function environmentFromInput( serviceNames: string[], logger: Logger, ): Promise { - const { exitWithExpectedError } = await import('../patterns'); + const { exitWithExpectedError } = await import('../../errors'); // A normal environment variable regex, with an added part // to find a colon followed servicename at the start const varRegex = /^(?:([^\s:]+):)?([^\s]+?)=(.*)$/; @@ -121,7 +121,7 @@ async function environmentFromInput( export async function deployToDevice(opts: DeviceDeployOptions): Promise { const { tarDirectory } = await import('../compose'); - const { exitWithExpectedError } = await import('../patterns'); + const { exitWithExpectedError } = await import('../../errors'); const { displayDeviceLogs } = await import('./logs'); const api = new DeviceAPI(globalLogger, opts.deviceHost); @@ -574,7 +574,7 @@ export function generateTargetState( } async function inspectBuildResults(images: LocalImage[]): Promise { - const { exitWithExpectedError } = await import('../patterns'); + const { exitWithExpectedError } = await import('../../errors'); const failures: LocalPushErrors.BuildFailure[] = []; diff --git a/lib/utils/docker-js.js b/lib/utils/docker-js.js index d8437ca2..22915d30 100644 --- a/lib/utils/docker-js.js +++ b/lib/utils/docker-js.js @@ -270,7 +270,7 @@ export const createClient = function(opts) { }; var ensureDockerSeemsAccessible = function(docker) { - const { exitWithExpectedError } = require('./patterns'); + const { exitWithExpectedError } = require('../errors'); return docker .ping() .catch(() => diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index 3896082f..26e385bb 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -20,9 +20,8 @@ import { stripIndent } from 'common-tags'; import _ = require('lodash'); import _form = require('resin-cli-form'); -import { instanceOf, NotLoggedInError } from '../errors'; -import { getBalenaSdk, getChalk, getVisuals } from './lazy'; -import messages = require('./messages'); +import { exitWithExpectedError, instanceOf, NotLoggedInError } from '../errors'; +import { getBalenaSdk, getVisuals } from './lazy'; import validation = require('./validation'); const getForm = _.once((): typeof _form => require('resin-cli-form')); @@ -419,29 +418,3 @@ export function selectFromList( })), }); } - -export function printErrorMessage(message: string) { - const chalk = getChalk(); - console.error(chalk.red(message)); - console.error(chalk.red(`\n${messages.getHelp}\n`)); -} - -/** - * Print a friendly error message and exit the CLI with an error code, BYPASSING - * error reporting through Sentry.io's platform (raven.Raven.captureException). - * Note that lib/errors.ts provides top-level error handling code to catch any - * otherwise uncaught errors, AND to report them through Sentry.io. But many - * "expected" errors (say, a JSON parsing error in a file provided by the user) - * don't warrant reporting through Sentry.io. For such mundane errors, catch - * them and call this function. - * - * DEPRECATED: Use `throw new ExpectedError()` instead. - */ -export function exitWithExpectedError(message: string | Error): never { - if (message instanceof Error) { - ({ message } = message); - } - - printErrorMessage(message); - process.exit(1); -} diff --git a/lib/utils/promote.ts b/lib/utils/promote.ts index e4a2f8b4..1dcc506b 100644 --- a/lib/utils/promote.ts +++ b/lib/utils/promote.ts @@ -17,7 +17,7 @@ import * as BalenaSdk from 'balena-sdk'; import { stripIndent } from 'common-tags'; -import { ExpectedError } from '../errors'; +import { ExpectedError, printErrorMessage } from '../errors'; import { getVisuals } from './lazy'; import Logger = require('./logger'); import { exec, execBuffered, getDeviceOsRelease } from './ssh'; @@ -325,7 +325,6 @@ async function createApplication( ): Promise { const form = await import('resin-cli-form'); const validation = await import('./validation'); - const patterns = await import('./patterns'); let username = await sdk.auth.whoami(); if (!username) { @@ -352,7 +351,9 @@ async function createApplication( ], }, }); - patterns.printErrorMessage( + // TODO: This is the only example in the codebase where `printErrorMessage()` + // is called directly. Consider refactoring. + printErrorMessage( 'You already have an application with that name; please choose another.', ); continue; diff --git a/lib/utils/remote-build.ts b/lib/utils/remote-build.ts index 527163fb..1b573ef1 100644 --- a/lib/utils/remote-build.ts +++ b/lib/utils/remote-build.ts @@ -24,7 +24,7 @@ import streamToPromise = require('stream-to-promise'); import { Pack } from 'tar-stream'; import { ExpectedError } from '../errors'; -import { exitWithExpectedError } from '../utils/patterns'; +import { exitWithExpectedError } from '../errors'; import { tarDirectory } from './compose'; import { getVisuals } from './lazy'; import Logger = require('./logger');