Skip to content

Commit 9635ab1

Browse files
Document exit codes in CLI help output and return exit code 2 for input errors (#1546)
* Initial plan * Add exit code documentation to help output and ensure validation errors return non-zero exit codes Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> * Return exit code 2 for input validation and parse errors Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> Co-authored-by: Waldek Mastykarz <waldek@mastykarz.nl>
1 parent cabe52a commit 9635ab1

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

DevProxy/Commands/DevProxyCommand.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using DevProxy.Abstractions.Proxy;
33
using DevProxy.Abstractions.Utils;
44
using System.CommandLine;
5+
using System.CommandLine.Help;
56
using System.CommandLine.Parsing;
67
using System.Globalization;
78

@@ -217,7 +218,17 @@ public async Task<int> InvokeAsync(string[] args, WebApplication app)
217218
var parseResult = IsStdioCommand
218219
? StdioCommand.ParseStdioArgs(this, args)
219220
: Parse(args);
220-
return await parseResult.InvokeAsync(app.Lifetime.ApplicationStopping);
221+
var exitCode = await parseResult.InvokeAsync(app.Lifetime.ApplicationStopping);
222+
223+
// Return exit code 2 for input validation and parse errors to distinguish
224+
// them from runtime errors (exit code 1), following conventions from
225+
// curl, git, and others
226+
if (exitCode != 0 && parseResult.Errors.Count > 0)
227+
{
228+
return 2;
229+
}
230+
231+
return exitCode;
221232
}
222233

223234
private async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken)
@@ -521,6 +532,12 @@ private void ConfigureCommand()
521532
commands.AddRange(_plugins.SelectMany(p => p.GetCommands()));
522533
this.AddCommands(commands.OrderByName());
523534

535+
var helpOption = Options.OfType<HelpOption>().FirstOrDefault();
536+
if (helpOption?.Action is HelpAction helpAction)
537+
{
538+
helpOption.Action = new ExitCodeHelpAction(helpAction);
539+
}
540+
524541
SetAction(InvokeAsync);
525542
}
526543

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.CommandLine;
6+
using System.CommandLine.Help;
7+
using System.CommandLine.Invocation;
8+
9+
namespace DevProxy.Commands;
10+
11+
sealed class ExitCodeHelpAction : SynchronousCommandLineAction
12+
{
13+
private readonly HelpAction _originalAction;
14+
15+
public ExitCodeHelpAction(HelpAction originalAction)
16+
{
17+
_originalAction = originalAction;
18+
Terminating = true;
19+
}
20+
21+
public override int Invoke(ParseResult parseResult)
22+
{
23+
var result = _originalAction.Invoke(parseResult);
24+
25+
var output = parseResult.Configuration.Output;
26+
output.WriteLine();
27+
output.WriteLine("Exit codes:");
28+
output.WriteLine(" 0 Success");
29+
output.WriteLine(" 1 Runtime error");
30+
output.WriteLine(" 2 Invalid input or usage");
31+
32+
return result;
33+
}
34+
}

0 commit comments

Comments
 (0)