diff --git a/.changeset/eighty-spoons-tickle.md b/.changeset/eighty-spoons-tickle.md new file mode 100644 index 0000000000..e5dc0aba1e --- /dev/null +++ b/.changeset/eighty-spoons-tickle.md @@ -0,0 +1,7 @@ +--- +'create-amplify': patch +'@aws-amplify/cli-core': patch +'@aws-amplify/backend-cli': patch +--- + +Handle uncaught exception in create-amplify and refactor the error handler to cli-core diff --git a/packages/cli-core/API.md b/packages/cli-core/API.md index 975ae9cdee..9fa030c14f 100644 --- a/packages/cli-core/API.md +++ b/packages/cli-core/API.md @@ -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'; @@ -33,12 +35,18 @@ export class AmplifyPrompter { }) => Promise; } +// @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); @@ -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); + // @public (undocumented) export enum LogLevel { // (undocumented) @@ -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; }, { @@ -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; }>; @@ -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; }, { @@ -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">; @@ -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; }[]; @@ -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; }[]; diff --git a/packages/cli-core/package.json b/packages/cli-core/package.json index cef19cdff5..d77e0d9f0a 100644 --- a/packages/cli-core/package.json +++ b/packages/cli-core/package.json @@ -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" } } diff --git a/packages/cli/src/error_handler.test.ts b/packages/cli-core/src/error_handler.test.ts similarity index 98% rename from packages/cli/src/error_handler.test.ts rename to packages/cli-core/src/error_handler.test.ts index 1e1dfeb585..66d1ddf152 100644 --- a/packages/cli/src/error_handler.test.ts +++ b/packages/cli-core/src/error_handler.test.ts @@ -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'; diff --git a/packages/cli/src/error_handler.ts b/packages/cli-core/src/error_handler.ts similarity index 85% rename from packages/cli/src/error_handler.ts rename to packages/cli-core/src/error_handler.ts index 655762e85b..2350fc8a09 100644 --- a/packages/cli/src/error_handler.ts +++ b/packages/cli-core/src/error_handler.ts @@ -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'; @@ -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; @@ -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) => { /** @@ -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; }; diff --git a/packages/cli/src/extract_sub_commands.ts b/packages/cli-core/src/extract_sub_commands.ts similarity index 100% rename from packages/cli/src/extract_sub_commands.ts rename to packages/cli-core/src/extract_sub_commands.ts diff --git a/packages/cli-core/src/index.ts b/packages/cli-core/src/index.ts index 366f908e1f..d9652c7178 100644 --- a/packages/cli-core/src/index.ts +++ b/packages/cli-core/src/index.ts @@ -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'; diff --git a/packages/cli/src/ampx.ts b/packages/cli/src/ampx.ts index 441f12025c..37542220bc 100755 --- a/packages/cli/src/ampx.ts +++ b/packages/cli/src/ampx.ts @@ -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, @@ -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'; diff --git a/packages/cli/src/test-utils/command_runner.ts b/packages/cli/src/test-utils/command_runner.ts index c65bae12c6..2f3e6a442b 100644 --- a/packages/cli/src/test-utils/command_runner.ts +++ b/packages/cli/src/test-utils/command_runner.ts @@ -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 = ''; diff --git a/packages/create-amplify/src/create_amplify.ts b/packages/create-amplify/src/create_amplify.ts index aecd7f2f0c..816c4f460e 100644 --- a/packages/create-amplify/src/create_amplify.ts +++ b/packages/create-amplify/src/create_amplify.ts @@ -8,10 +8,10 @@ */ 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'; @@ -19,26 +19,30 @@ 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); + } }