Skip to content

Commit bb556b2

Browse files
committed
chore(cli): don't double up on errors
1 parent a0de04e commit bb556b2

File tree

5 files changed

+72
-35
lines changed

5 files changed

+72
-35
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/toolkit-error.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ export class ToolkitError extends Error {
3737
return this.isToolkitError(x) && CONTEXT_PROVIDER_ERROR_SYMBOL in x;
3838
}
3939

40+
/**
41+
* An AssemblyError with an original error as cause
42+
*/
43+
public static withCause(message: string, error: unknown): ToolkitError {
44+
return new ToolkitError(message, 'toolkit', error);
45+
}
46+
4047
/**
4148
* The type of the error, defaults to "toolkit".
4249
*/
@@ -47,13 +54,19 @@ export class ToolkitError extends Error {
4754
*/
4855
public readonly source: 'toolkit' | 'user';
4956

50-
constructor(message: string, type: string = 'toolkit') {
57+
/**
58+
* The specific original cause of the error, if available
59+
*/
60+
public readonly cause?: unknown;
61+
62+
constructor(message: string, type: string = 'toolkit', cause?: unknown) {
5163
super(message);
5264
Object.setPrototypeOf(this, ToolkitError.prototype);
5365
Object.defineProperty(this, TOOLKIT_ERROR_SYMBOL, { value: true });
5466
this.name = new.target.name;
5567
this.type = type;
5668
this.source = 'toolkit';
69+
this.cause = cause;
5770
}
5871
}
5972

@@ -106,17 +119,11 @@ export class AssemblyError extends ToolkitError {
106119
*/
107120
public readonly stacks?: cxapi.CloudFormationStackArtifact[];
108121

109-
/**
110-
* The specific original cause of the error, if available
111-
*/
112-
public readonly cause?: unknown;
113-
114122
private constructor(message: string, stacks?: cxapi.CloudFormationStackArtifact[], cause?: unknown) {
115-
super(message, 'assembly');
123+
super(message, 'assembly', cause);
116124
Object.setPrototypeOf(this, AssemblyError.prototype);
117125
Object.defineProperty(this, ASSEMBLY_ERROR_SYMBOL, { value: true });
118126
this.stacks = stacks;
119-
this.cause = cause;
120127
}
121128
}
122129

packages/aws-cdk/lib/api/plugin/plugin.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { inspect } from 'util';
22
import type { CredentialProviderSource, IPluginHost, Plugin } from '@aws-cdk/cli-plugin-contract';
3-
4-
import * as chalk from 'chalk';
53
import { type ContextProviderPlugin, isContextProviderPlugin } from './context-provider-plugin';
6-
import { error } from '../../logging';
74
import { ToolkitError } from '../../toolkit/error';
85

96
export let TESTING = false;
@@ -44,15 +41,13 @@ export class PluginHost implements IPluginHost {
4441
const plugin = require(moduleSpec);
4542
/* eslint-enable */
4643
if (!isPlugin(plugin)) {
47-
error(`Module ${chalk.green(moduleSpec)} is not a valid plug-in, or has an unsupported version.`);
48-
throw new ToolkitError(`Module ${moduleSpec} does not define a valid plug-in.`);
44+
throw new ToolkitError(`Module ${moduleSpec} is not a valid plug-in, or has an unsupported version.`);
4945
}
5046
if (plugin.init) {
5147
plugin.init(this);
5248
}
5349
} catch (e: any) {
54-
error(`Unable to load ${chalk.green(moduleSpec)}: ${e.stack}`);
55-
throw new ToolkitError(`Unable to load plug-in: ${moduleSpec}: ${e}`);
50+
throw ToolkitError.withCause(`Unable to load plug-in '${moduleSpec}'`, e);
5651
}
5752

5853
function isPlugin(x: any): x is Plugin {

packages/aws-cdk/lib/cli/cli.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CdkToolkit, AssetBuildTime } from './cdk-toolkit';
44
import { ciSystemIsStdErrSafe } from './ci-systems';
55
import { parseCommandLineArguments } from './parse-command-line-arguments';
66
import { checkForPlatformWarnings } from './platform-warnings';
7-
7+
import { prettyPrintError } from './pretty-print-error';
88
import * as version from './version';
99
import { asIoHelper } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
1010
import { SdkProvider } from '../api/aws-auth';
@@ -28,7 +28,6 @@ import { docs } from '../commands/docs';
2828
import { doctor } from '../commands/doctor';
2929
import { getMigrateScanType } from '../commands/migrate';
3030
import { cliInit, printAvailableTemplates } from '../init';
31-
import { result, debug, error, info } from '../logging';
3231
import { Notices } from '../notices';
3332
import type { Command } from './user-configuration';
3433
import { Configuration } from './user-configuration';
@@ -82,11 +81,11 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
8281
try {
8382
await checkForPlatformWarnings();
8483
} catch (e) {
85-
debug(`Error while checking for platform warnings: ${e}`);
84+
ioHost.defaults.debug(`Error while checking for platform warnings: ${e}`);
8685
}
8786

88-
debug('CDK Toolkit CLI version:', version.displayVersion());
89-
debug('Command line arguments:', argv);
87+
ioHost.defaults.debug('CDK Toolkit CLI version:', version.displayVersion());
88+
ioHost.defaults.debug('Command line arguments:', argv);
9089

9190
const configuration = new Configuration({
9291
commandLineArguments: {
@@ -158,7 +157,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
158157
if (loaded.has(resolved)) {
159158
continue;
160159
}
161-
debug(`Loading plug-in: ${chalk.green(plugin)} from ${chalk.blue(resolved)}`);
160+
ioHost.defaults.debug(`Loading plug-in: ${chalk.green(plugin)} from ${chalk.blue(resolved)}`);
162161
PluginHost.instance.load(plugin);
163162
loaded.add(resolved);
164163
}
@@ -168,8 +167,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
168167
try {
169168
return require.resolve(plugin);
170169
} catch (e: any) {
171-
error(`Unable to resolve plugin ${chalk.green(plugin)}: ${e.stack}`);
172-
throw new ToolkitError(`Unable to resolve plug-in: ${plugin}`);
170+
throw new ToolkitError(`Unable to resolve plug-in: Cannot find module '${plugin}'`);
173171
}
174172
}
175173
}
@@ -201,7 +199,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
201199
async function main(command: string, args: any): Promise<number | void> {
202200
ioHost.currentAction = command as any;
203201
const toolkitStackName: string = ToolkitInfo.determineName(configuration.settings.get(['toolkitStackName']));
204-
debug(`Toolkit stack: ${chalk.bold(toolkitStackName)}`);
202+
ioHost.defaults.debug(`Toolkit stack: ${chalk.bold(toolkitStackName)}`);
205203

206204
const cloudFormation = new Deployments({
207205
sdkProvider,
@@ -282,7 +280,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
282280

283281
case 'bootstrap':
284282
ioHost.currentAction = 'bootstrap';
285-
const source: BootstrapSource = determineBootstrapVersion(args);
283+
const source: BootstrapSource = determineBootstrapVersion(ioHost, args);
286284

287285
if (args.showTemplate) {
288286
const bootstrapper = new Bootstrapper(source, asIoHelper(ioHost, ioHost.currentAction));
@@ -523,7 +521,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
523521
});
524522
case 'version':
525523
ioHost.currentAction = 'version';
526-
return result(version.displayVersion());
524+
return ioHost.defaults.result(version.displayVersion());
527525

528526
default:
529527
throw new ToolkitError('Unknown command: ' + command);
@@ -534,13 +532,13 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
534532
/**
535533
* Determine which version of bootstrapping
536534
*/
537-
function determineBootstrapVersion(args: { template?: string }): BootstrapSource {
535+
function determineBootstrapVersion(ioHost: CliIoHost, args: { template?: string }): BootstrapSource {
538536
let source: BootstrapSource;
539537
if (args.template) {
540-
info(`Using bootstrapping template from ${args.template}`);
538+
ioHost.defaults.info(`Using bootstrapping template from ${args.template}`);
541539
source = { source: 'custom', templateFile: args.template };
542540
} else if (process.env.CDK_LEGACY_BOOTSTRAP) {
543-
info('CDK_LEGACY_BOOTSTRAP set, using legacy-style bootstrapping');
541+
ioHost.defaults.info('CDK_LEGACY_BOOTSTRAP set, using legacy-style bootstrapping');
544542
source = { source: 'legacy' };
545543
} else {
546544
// in V2, the "new" bootstrapping is the default
@@ -600,13 +598,9 @@ export function cli(args: string[] = process.argv.slice(2)) {
600598
}
601599
})
602600
.catch((err) => {
603-
error(err.message);
604-
605601
// Log the stack trace if we're on a developer workstation. Otherwise this will be into a minified
606602
// file and the printed code line and stack trace are huge and useless.
607-
if (err.stack && version.isDeveloperBuild()) {
608-
debug(err.stack);
609-
}
603+
prettyPrintError(err, version.isDeveloperBuild());
610604
process.exitCode = 1;
611605
});
612606
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable no-console */
2+
import * as chalk from 'chalk';
3+
4+
export function prettyPrintError(error: unknown, debug = false) {
5+
const err = ensureError(error);
6+
console.error(chalk.red(err.message));
7+
8+
if (err.cause) {
9+
const cause = ensureError(err.cause);
10+
console.error(chalk.yellow(cause.message));
11+
printTrace(err, debug);
12+
}
13+
14+
printTrace(err, debug);
15+
}
16+
17+
function printTrace(err: Error, debug = false) {
18+
// Log the stack trace if we're on a developer workstation. Otherwise this will be into a minified
19+
// file and the printed code line and stack trace are huge and useless.
20+
if (err.stack && debug) {
21+
console.debug(chalk.gray(err.stack));
22+
}
23+
}
24+
25+
function ensureError(value: unknown): Error & { cause?: unknown } {
26+
if (value instanceof Error) return value;
27+
28+
let stringified = '[Unable to stringify the thrown value]';
29+
try {
30+
stringified = JSON.stringify(value);
31+
} catch {
32+
}
33+
34+
const error = new Error(`An unexpected error was thrown: ${stringified}`);
35+
return error;
36+
}

packages/aws-cdk/lib/toolkit/cli-io-host.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as chalk from 'chalk';
44
import * as promptly from 'promptly';
55
import { ToolkitError } from './error';
66
import { type IIoHost, type IoMessage, type IoMessageCode, type IoMessageLevel, type IoRequest, type ToolkitAction } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io';
7-
import { IO, isMessageRelevantForLevel } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
7+
import { asIoHelper, IO, IoDefaultMessages, isMessageRelevantForLevel } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
88
import type { ActivityPrinterProps, IActivityPrinter } from '../cli/activity-printer';
99
import { CurrentActivityPrinter, HistoryActivityPrinter } from '../cli/activity-printer';
1010
import { StackActivityProgress } from '../commands/deploy';
@@ -204,6 +204,11 @@ export class CliIoHost implements IIoHost {
204204
return this._progress;
205205
}
206206

207+
public get defaults() {
208+
const helper = asIoHelper(this, this.currentAction as any);
209+
return new IoDefaultMessages(helper);
210+
}
211+
207212
/**
208213
* Executes a block of code with corked logging. All log messages during execution
209214
* are buffered and only written when all nested cork blocks complete (when CORK_COUNTER reaches 0).

0 commit comments

Comments
 (0)