cli: internal error handling refactor
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Internal refactor of error handling
|
||||
@@ -14,15 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CustomErrorBase, isError, stringifyError } from '@backstage/errors';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export class CustomError extends Error {
|
||||
get name(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExitCodeError extends CustomError {
|
||||
export class ExitCodeError extends CustomErrorBase {
|
||||
readonly code: number;
|
||||
|
||||
constructor(code: number, command?: string) {
|
||||
@@ -35,14 +30,33 @@ export class ExitCodeError extends CustomError {
|
||||
}
|
||||
}
|
||||
|
||||
export function exitWithError(error: Error): never {
|
||||
if (error instanceof ExitCodeError) {
|
||||
process.stderr.write(`\n${chalk.red(error.message)}\n\n`);
|
||||
process.exit(error.code);
|
||||
} else {
|
||||
process.stderr.write(`\n${chalk.red(`${error.stack}`)}\n\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
function exit(message: string, code: number = 1): never {
|
||||
process.stderr.write(`\n${chalk.red(message)}\n\n`);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
export class NotFoundError extends CustomError {}
|
||||
export function exitWithError(error: unknown): never {
|
||||
if (!isError(error)) {
|
||||
process.stderr.write(`\n${chalk.red(stringifyError(error))}\n\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
switch (error.name) {
|
||||
case 'InputError':
|
||||
return exit(error.message, 74 /* input/output error */);
|
||||
case 'NotFoundError':
|
||||
return exit(error.message, 127 /* command not found */);
|
||||
case 'NotImplementedError':
|
||||
return exit(error.message, 64 /* command line usage error */);
|
||||
case 'AuthenticationError':
|
||||
case 'NotAllowedError':
|
||||
return exit(error.message, 77 /* permissino denied */);
|
||||
case 'ExitCodeError':
|
||||
return exit(
|
||||
error.message,
|
||||
'code' in error && typeof error.code === 'number' ? error.code : 1,
|
||||
);
|
||||
default:
|
||||
return exit(stringifyError(error), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export function removed(message?: string) {
|
||||
return () => {
|
||||
console.error(
|
||||
message
|
||||
? `This command has been removed, ${message}`
|
||||
: 'This command has been removed',
|
||||
);
|
||||
process.exit(1);
|
||||
};
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
import * as runObj from '../run';
|
||||
import * as yarn from './yarn';
|
||||
import { fetchPackageInfo, mapDependencies } from './packages';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
|
||||
jest.mock('../run', () => {
|
||||
return {
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import { minimatch } from 'minimatch';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { detectYarnVersion } from './yarn';
|
||||
import { execFile } from '../run';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { removed } from '../../lib/removed';
|
||||
import { NotImplementedError } from '@backstage/errors';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'new',
|
||||
@@ -71,7 +71,9 @@ export default createCliPlugin({
|
||||
description: 'Create a new Backstage app',
|
||||
deprecated: true,
|
||||
execute: async () => {
|
||||
removed("use 'backstage-cli new' instead")();
|
||||
throw new NotImplementedError(
|
||||
`This command has been removed, use 'backstage-cli new' instead`,
|
||||
);
|
||||
},
|
||||
});
|
||||
reg.addCommand({
|
||||
@@ -79,7 +81,9 @@ export default createCliPlugin({
|
||||
description: 'Create a new Backstage plugin',
|
||||
deprecated: true,
|
||||
execute: async () => {
|
||||
removed("use 'backstage-cli new' instead")();
|
||||
throw new NotImplementedError(
|
||||
`This command has been removed, use 'backstage-cli new' instead`,
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Command } from 'commander';
|
||||
import { version } from '../lib/version';
|
||||
import chalk from 'chalk';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { isPromise } from 'util/types';
|
||||
|
||||
type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>;
|
||||
@@ -126,8 +126,7 @@ export class CliInitializer {
|
||||
},
|
||||
});
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
} catch (error: unknown) {
|
||||
exitWithError(error);
|
||||
}
|
||||
});
|
||||
@@ -142,11 +141,7 @@ export class CliInitializer {
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', rejection => {
|
||||
if (rejection instanceof Error) {
|
||||
exitWithError(rejection);
|
||||
} else {
|
||||
exitWithError(new Error(`Unknown rejection: '${rejection}'`));
|
||||
}
|
||||
exitWithError(new ForwardedError('Unhandled rejection', rejection));
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
Reference in New Issue
Block a user