diff --git a/CHANGELOG.md b/CHANGELOG.md index 481f00a0a..f4f3ac8b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,9 @@ A comprehensive modernization of all Angular templates to align with Angular v21 * **ci:** mark `Util.sanitizeShellArg(x)` as command injection sanitizer for CodeQL ([#1524](https://github.com/IgniteUI/igniteui-cli/pull/1524)) * **deps:** bump minimatch, ajv, immutable, and lodash ([#1549](https://github.com/IgniteUI/igniteui-cli/pull/1549)) * **deps:** bump flatted ([#1559](https://github.com/IgniteUI/igniteui-cli/pull/1559)) +* **CLI error handling:** added `.fail()` handler to yargs to gracefully handle command validation errors (e.g., missing required subcommands) instead of showing raw stack traces ([#1614](https://github.com/IgniteUI/igniteui-cli/pull/1614)) +* **Unknown command detection:** running `ig ` now prints an error message and available commands instead of silently falling through to the step-by-step interactive mode ([#1614](https://github.com/IgniteUI/igniteui-cli/pull/1614)) +* **Unhandled promise rejection:** added `.catch()` in the CLI entry point to catch and display unexpected errors cleanly ([#1614](https://github.com/IgniteUI/igniteui-cli/pull/1614)) --- diff --git a/packages/cli/bin/execute.js b/packages/cli/bin/execute.js index 16f0b3f84..43c51591e 100755 --- a/packages/cli/bin/execute.js +++ b/packages/cli/bin/execute.js @@ -19,5 +19,8 @@ resolve("igniteui-cli", { basedir: process.cwd() }, function (err, res) { } else { cli = require("../lib/cli"); } - cli.run(args); + cli.run(args).catch(function (err) { + console.error("Error: " + (err.message || err)); + process.exit(1); + }); }); diff --git a/packages/cli/lib/cli.ts b/packages/cli/lib/cli.ts index 9ab0b7661..d4aa9cd53 100644 --- a/packages/cli/lib/cli.ts +++ b/packages/cli/lib/cli.ts @@ -83,6 +83,14 @@ export async function run(args = null) { false // setting this to `true` is supposed to exec the middleware after parsing and before arg validation // but in reality it also does not trigger the command's handler (╯°□°)╯︵ ┻━┻ ) + .fail((msg, err, yargs) => { + const message = err?.message ?? msg; + if (message) { + Util.error(message, "red"); + yargs.showHelp(); + process.exitCode = 1; + } + }) .help().alias("help", "h") .parseAsync( args, // the args to parse to argv @@ -121,10 +129,16 @@ export async function run(args = null) { App.testMode = !!argv.testMode; if (!helpRequest && !ALL_COMMANDS.has(command?.toString())) { - Util.log("Starting Step by step mode.", "green"); - Util.log("For available commands, stop this execution and use --help.", "green"); - const prompts = new PromptSession(templateManager); - prompts.start(); + if (command) { + process.exitCode = 1; + Util.error(`Unknown command: "${command}"`, "red"); + yargsModule.showHelp(); + } else { + Util.log("Starting Step by step mode.", "green"); + Util.log("For available commands, stop this execution and use --help.", "green"); + const prompts = new PromptSession(templateManager); + prompts.start(); + } } } ); diff --git a/spec/unit/cli-spec.ts b/spec/unit/cli-spec.ts index 8a936f123..225d26c32 100644 --- a/spec/unit/cli-spec.ts +++ b/spec/unit/cli-spec.ts @@ -54,15 +54,33 @@ describe("Unit - Cli.ts", () => { await run.run("list"); expect(list.handler).toHaveBeenCalled(); }); - it("Should fire properly - fallback to default", async () => { + it("Should fire properly - fallback to default with no args", async () => { spyOn(Util , "log"); spyOn(GoogleAnalytics, "post"); spyOn(PromptSession.prototype, "start"); - await run.run("Nonexistent command"); + await run.run([]); // expected console output: expect(Util.log).toHaveBeenCalledWith("Starting Step by step mode.", "green"); expect(Util.log).toHaveBeenCalledWith("For available commands, stop this execution and use --help.", "green"); expect(PromptSession.prototype.start).toHaveBeenCalled(); }); + it("Should show error for unknown command", async () => { + spyOn(Util , "log"); + spyOn(Util , "error"); + spyOn(GoogleAnalytics, "post"); + spyOn(PromptSession.prototype, "start"); + await run.run("nonexistent"); + + expect(Util.error).toHaveBeenCalledWith(`Unknown command: "nonexistent"`, "red"); + expect(PromptSession.prototype.start).not.toHaveBeenCalled(); + }); + it("Should gracefully handle subcommand validation errors", async () => { + spyOn(Util , "log"); + spyOn(Util , "error"); + spyOn(GoogleAnalytics, "post"); + await run.run("generate"); + + expect(Util.error).toHaveBeenCalledWith("Please select command", "red"); + }); });