/// <summary> /// Runs the application with specified command line arguments and environment variables, and returns the exit code. /// </summary> /// <remarks> /// If a <see cref="CommandException"/> is thrown during command execution, it will be handled and routed to the console. /// Additionally, if the debugger is not attached (i.e. the app is running in production), all other exceptions thrown within /// this method will be handled and routed to the console as well. /// </remarks> public async ValueTask <int> RunAsync( IReadOnlyList <string> commandLineArguments, IReadOnlyDictionary <string, string> environmentVariables) { try { var root = RootSchema.Resolve(_configuration.CommandTypes); var input = CommandInput.Parse(commandLineArguments, root.GetCommandNames()); // Debug mode if (_configuration.IsDebugModeAllowed && input.IsDebugDirectiveSpecified) { await LaunchAndWaitForDebuggerAsync(); } // Preview mode if (_configuration.IsPreviewModeAllowed && input.IsPreviewDirectiveSpecified) { WriteCommandLineInput(input); return(ExitCode.Success); } // Try to get the command matching the input or fallback to default var command = root.TryFindCommand(input.CommandName) ?? root.TryFindDefaultCommand() ?? StubDefaultCommand.Schema; // Version option if (command.IsVersionOptionAvailable && input.IsVersionOptionSpecified) { _console.Output.WriteLine(_metadata.VersionText); return(ExitCode.Success); } // Get command instance (also used in help text) var instance = GetCommandInstance(command); // To avoid instantiating the command twice, we need to get default values // before the arguments are bound to the properties var defaultValues = command.GetArgumentValues(instance); // Help option if (command.IsHelpOptionAvailable && input.IsHelpOptionSpecified || command == StubDefaultCommand.Schema && !input.Parameters.Any() && !input.Options.Any()) { _helpTextWriter.Write(root, command, defaultValues); return(ExitCode.Success); } // Bind arguments try { command.Bind(instance, input, environmentVariables); } // This may throw exceptions which are useful only to the end-user catch (CliFxException ex) { WriteError(ex.ToString()); _helpTextWriter.Write(root, command, defaultValues); return(ExitCode.FromException(ex)); } // Execute the command try { await instance.ExecuteAsync(_console); return(ExitCode.Success); } // Swallow command exceptions and route them to the console catch (CommandException ex) { WriteError(ex.ToString()); if (ex.ShowHelp) { _helpTextWriter.Write(root, command, defaultValues); } return(ex.ExitCode); } } // To prevent the app from showing the annoying Windows troubleshooting dialog, // we handle all exceptions and route them to the console nicely. // However, we don't want to swallow unhandled exceptions when the debugger is attached, // because we still want the IDE to show them to the developer. catch (Exception ex) when(!Debugger.IsAttached) { WriteError(ex.ToString()); return(ExitCode.FromException(ex)); } }