Skip to content

fix: Handle uncaught exception in create-amplify #2828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/eighty-spoons-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'create-amplify': patch
'@aws-amplify/cli-core': minor
'@aws-amplify/backend-cli': patch
---

Handle uncaught exception in create-amplify and refactor the error handler to cli-core
23 changes: 17 additions & 6 deletions packages/cli-core/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
```ts

import { AmplifyIOHost } from '@aws-amplify/plugin-types';
import { Argv } from 'yargs';
import { PackageManagerController } from '@aws-amplify/plugin-types';
import { UsageDataEmitter } from '@aws-amplify/platform-core';
import { WriteStream } from 'node:tty';
import z from 'zod';

Expand Down Expand Up @@ -33,12 +35,18 @@ export class AmplifyPrompter {
}) => Promise<boolean>;
}

// @public
export const attachUnhandledExceptionListeners: (usageDataEmitter?: UsageDataEmitter) => void;

// @public (undocumented)
export type ColorName = (typeof colorNames)[number];

// @public (undocumented)
export const colorNames: readonly ["Green", "Yellow", "Blue", "Magenta", "Cyan", "Red"];

// @public
export const extractSubCommands: (yargs: Argv) => string | undefined;

// @public
export class Format {
constructor(packageManagerRunnerName?: string);
Expand Down Expand Up @@ -75,6 +83,9 @@ export class Format {
// @public (undocumented)
export const format: Format;

// @public
export const generateCommandFailureHandler: (parser?: Argv, usageDataEmitter?: UsageDataEmitter) => ((message: string, error: Error) => Promise<void>);

// @public (undocumented)
export enum LogLevel {
// (undocumented)
Expand Down Expand Up @@ -185,7 +196,7 @@ export const noticeSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}, {
Expand Down Expand Up @@ -213,7 +224,7 @@ export const noticeSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}>;
Expand Down Expand Up @@ -314,7 +325,7 @@ export const noticesManifestSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}, {
Expand Down Expand Up @@ -342,7 +353,7 @@ export const noticesManifestSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}>, "many">;
Expand Down Expand Up @@ -372,7 +383,7 @@ export const noticesManifestSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}[];
Expand Down Expand Up @@ -402,7 +413,7 @@ export const noticesManifestSchema: z.ZodObject<{
errorMessage: string;
})[];
link?: string | undefined;
frequency?: "once" | "command" | "deployment" | "daily" | undefined;
frequency?: "command" | "once" | "deployment" | "daily" | undefined;
validFrom?: number | undefined;
validTo?: number | undefined;
}[];
Expand Down
3 changes: 2 additions & 1 deletion packages/cli-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0",
"semver": "^7.6.3",
"zod": "3.25.17"
"zod": "3.25.17",
"yargs": "^17.7.2"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
generateCommandFailureHandler,
} from './error_handler.js';
import { Argv } from 'yargs';
import { LogLevel, format, printer } from '@aws-amplify/cli-core';
import { format } from './format/format.js';
import { LogLevel } from './printer/printer.js';
import { printer } from './printer.js';
import assert from 'node:assert';
import { AmplifyUserError, UsageDataEmitter } from '@aws-amplify/platform-core';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { LogLevel, format, printer } from '@aws-amplify/cli-core';
import { format } from './format/format.js';
import { LogLevel } from './printer/printer.js';
import { printer } from './printer.js';
import { Argv } from 'yargs';
import { AmplifyError, UsageDataEmitter } from '@aws-amplify/platform-core';
import { extractSubCommands } from './extract_sub_commands.js';
Expand All @@ -17,7 +19,7 @@ type HandleErrorProps = {
* Attaches process listeners to handle unhandled exceptions and rejections
*/
export const attachUnhandledExceptionListeners = (
usageDataEmitter: UsageDataEmitter,
usageDataEmitter?: UsageDataEmitter,
): void => {
if (hasAttachUnhandledExceptionListenersBeenCalled) {
return;
Expand Down Expand Up @@ -54,7 +56,7 @@ export const attachUnhandledExceptionListeners = (
* This prevents our top-level error handler from being invoked after the yargs error handler has already been invoked
*/
export const generateCommandFailureHandler = (
parser: Argv,
parser?: Argv,
usageDataEmitter?: UsageDataEmitter,
): ((message: string, error: Error) => Promise<void>) => {
/**
Expand All @@ -63,19 +65,29 @@ export const generateCommandFailureHandler = (
* @param error error thrown by yargs handler
*/
const handleCommandFailure = async (message: string, error?: Error) => {
const printHelp = () => {
printer.printNewLine();
parser.showHelp();
printer.printNewLine();
};
await handleErrorSafe({
command: extractSubCommands(parser),
printMessagePreamble: printHelp,
error,
message,
usageDataEmitter,
});
parser.exit(1, error || new Error(message));
if (!parser) {
await handleErrorSafe({
error,
message,
});
}

// for ampx commands
if (parser) {
const printHelp = () => {
printer.printNewLine();
parser.showHelp();
printer.printNewLine();
};
await handleErrorSafe({
command: extractSubCommands(parser),
printMessagePreamble: printHelp,
error,
message,
usageDataEmitter,
});
parser.exit(1, error || new Error(message));
}
};
return handleCommandFailure;
};
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from './package-manager-controller/package_manager_controller_factory.j
export * from './loggers/amplify_io_events_bridge_singleton_factory.js';
export * from './notices/notices.js';
export * from './notices/notices_manifest_validator.js';
export * from './error_handler.js';
export * from './extract_sub_commands.js';
13 changes: 7 additions & 6 deletions packages/cli/src/ampx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import {
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import { createMainParser } from './main_parser_factory.js';
import {
attachUnhandledExceptionListeners,
generateCommandFailureHandler,
} from './error_handler.js';
import { extractSubCommands } from './extract_sub_commands.js';
import {
AmplifyFault,
PackageJsonReader,
Expand All @@ -25,7 +20,13 @@ import {
import { fileURLToPath } from 'node:url';
import { verifyCommandName } from './verify_command_name.js';
import { hideBin } from 'yargs/helpers';
import { PackageManagerControllerFactory, format } from '@aws-amplify/cli-core';
import {
PackageManagerControllerFactory,
attachUnhandledExceptionListeners,
extractSubCommands,
format,
generateCommandFailureHandler,
} from '@aws-amplify/cli-core';
import { NoticesRenderer } from './notices/notices_renderer.js';
import { extractCommandInfo } from './extract_command_info.js';
import { DeepPartial } from '@aws-amplify/plugin-types';
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/test-utils/command_runner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Argv } from 'yargs';
import { AsyncLocalStorage } from 'node:async_hooks';
import { UsageDataEmitter } from '@aws-amplify/platform-core';
import { generateCommandFailureHandler } from '../error_handler.js';
import { extractSubCommands } from '../extract_sub_commands.js';
import {
extractSubCommands,
generateCommandFailureHandler,
} from '@aws-amplify/cli-core';

class OutputInterceptor {
private output = '';
Expand Down
43 changes: 25 additions & 18 deletions packages/create-amplify/src/create_amplify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,44 @@
*/

import {
LogLevel,
PackageManagerControllerFactory,
attachUnhandledExceptionListeners,
format,
printer,
generateCommandFailureHandler,
} from '@aws-amplify/cli-core';
import { ProjectRootValidator } from './project_root_validator.js';
import { AmplifyProjectCreator } from './amplify_project_creator.js';
import { getProjectRoot } from './get_project_root.js';
import { GitIgnoreInitializer } from './gitignore_initializer.js';
import { InitialProjectFileGenerator } from './initial_project_file_generator.js';

const projectRoot = await getProjectRoot();
attachUnhandledExceptionListeners();
const errorHandler = generateCommandFailureHandler();

const packageManagerControllerFactory = new PackageManagerControllerFactory(
projectRoot,
);
try {
const projectRoot = await getProjectRoot();

const packageManagerController =
packageManagerControllerFactory.getPackageManagerController();
const packageManagerControllerFactory = new PackageManagerControllerFactory(
projectRoot,
);

const amplifyProjectCreator = new AmplifyProjectCreator(
projectRoot,
packageManagerController,
new ProjectRootValidator(projectRoot),
new GitIgnoreInitializer(projectRoot),
new InitialProjectFileGenerator(projectRoot, packageManagerController),
);
const packageManagerController =
packageManagerControllerFactory.getPackageManagerController();

const amplifyProjectCreator = new AmplifyProjectCreator(
projectRoot,
packageManagerController,
new ProjectRootValidator(projectRoot),
new GitIgnoreInitializer(projectRoot),
new InitialProjectFileGenerator(projectRoot, packageManagerController),
);

try {
await amplifyProjectCreator.create();
} catch (err) {
printer.log(format.error(err), LogLevel.ERROR);
process.exitCode = 1;
if (err instanceof Error) {
await errorHandler(format.error(err), err);
} else {
// eslint-disable-next-line @aws-amplify/amplify-backend-rules/prefer-amplify-errors
await errorHandler(format.error(err), new Error(JSON.stringify(err)));
}
}
Loading