balena-cli/lib/errors.ts
Paulo Castro d94a74dfee Fix 'os configure --config', and migrate it to oclif + TypeScript
Also add more non-interactive configuration options:
--config-network, --config-wifi-*, --config-app-update-poll-interval

Change-type: minor
Signed-off-by: Paulo Castro <paulo@balena.io>
2019-11-12 21:09:18 +00:00

138 lines
3.8 KiB
TypeScript

/*
Copyright 2016-2019 Balena
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.
*/
import * as Bluebird from 'bluebird';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import * as os from 'os';
import * as Raven from 'raven';
export class ExpectedError extends Error {}
export class NotLoggedInError extends ExpectedError {}
const captureException = Bluebird.promisify<string, Error>(
Raven.captureException,
{ context: Raven },
);
function hasCode(error: any): error is Error & { code: string } {
return error.code != null;
}
function treatFailedBindingAsMissingModule(error: any): void {
if (error.message.startsWith('Could not locate the bindings file.')) {
error.code = 'MODULE_NOT_FOUND';
}
}
function interpret(error: Error): string {
treatFailedBindingAsMissingModule(error);
if (hasCode(error)) {
const errorCodeHandler = messages[error.code];
const message = errorCodeHandler && errorCodeHandler(error);
if (message) {
return message;
}
if (!_.isEmpty(error.message)) {
return `${error.code}: ${error.message}`;
}
}
return error.message;
}
const messages: {
[key: string]: (error: Error & { path?: string }) => string;
} = {
EISDIR: error => `File is a directory: ${error.path}`,
ENOENT: error => `No such file or directory: ${error.path}`,
ENOGIT: () => stripIndent`
Git is not installed on this system.
Head over to http://git-scm.com to install it and run this command again.`,
EPERM: () => stripIndent`
You don't have sufficient privileges to run this operation.
${
os.platform() === 'win32'
? 'Run a new Command Prompt as administrator and try running this command again.'
: 'Try running this command again prefixing it with `sudo`.'
}
If this is not the case, and you're trying to burn an SDCard, check that the write lock is not set.`,
EACCES: e => messages.EPERM(e),
ETIMEDOUT: () =>
'Oops something went wrong, please check your connection and try again.',
MODULE_NOT_FOUND: () => stripIndent`
Part of the CLI could not be loaded. This typically means your CLI install is in a broken state.
${
os.arch() === 'x64'
? 'You can normally fix this by uninstalling and reinstalling the CLI.'
: stripIndent`
You're using an unsupported architecture (${os.arch()}), so this is typically caused by missing native modules.
Reinstalling may help, but pay attention to errors in native module build steps en route.
`
}
`,
BalenaExpiredToken: () => stripIndent`
Looks like your session token is expired.
Please try logging in again with:
$ balena login`,
};
export async function handleError(error: any) {
const { printErrorMessage } = await import('./utils/patterns');
process.exitCode =
error.exitCode === 0
? 0
: parseInt(error.exitCode, 10) || process.exitCode || 1;
if (!(error instanceof Error)) {
printErrorMessage(String(error));
return;
}
const message = [interpret(error)];
if (process.env.DEBUG && error.stack) {
message.push(error.stack);
}
printErrorMessage(message.join('\n'));
if (error instanceof ExpectedError) {
return;
}
// Report "unexpected" errors via Sentry.io
await captureException(error)
.timeout(1000)
.catch(function() {
// Ignore any errors (from error logging, or timeouts)
})
// exit with the process.exitCode set earlier
.finally(() => process.exit());
}