Skip to content

Commit 4384db4

Browse files
committed
Refactor GetRootCommand to collect and sort all options and commands
1 parent 0a6ec71 commit 4384db4

File tree

2 files changed

+143
-70
lines changed

2 files changed

+143
-70
lines changed

dev-proxy/Program.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@
4141
IProxyContext context = new ProxyContext(ProxyCommandHandler.Configuration, ProxyEngine.Certificate, lmClient);
4242
ProxyHost proxyHost = new();
4343

44-
// this is where the root command is created which contains all commands and subcommands
45-
RootCommand rootCommand = proxyHost.GetRootCommand(logger);
46-
4744
// store the global options that are created automatically for us
4845
// rootCommand doesn't return the global options, so we have to store them manually
4946
string[] globalOptions = ["--version"];
@@ -62,20 +59,22 @@
6259
// load plugins to get their options and commands
6360
var pluginLoader = new PluginLoader(isDiscover, logger, loggerFactory);
6461
PluginLoaderResult loaderResults = await pluginLoader.LoadPluginsAsync(pluginEvents, context);
65-
var options = loaderResults.ProxyPlugins
62+
63+
var pluginOptions = loaderResults.ProxyPlugins
6664
.SelectMany(p => p.GetOptions())
6765
// remove duplicates by comparing the option names
6866
.GroupBy(o => o.Name)
6967
.Select(g => g.First())
70-
.ToList();
71-
options.ForEach(rootCommand.AddOption);
72-
// register all plugin commands
73-
loaderResults.ProxyPlugins
68+
.ToArray();
69+
70+
var pluginCommands = loaderResults.ProxyPlugins
7471
.SelectMany(p => p.GetCommands())
75-
.ToList()
76-
.ForEach(rootCommand.AddCommand);
72+
.ToArray();
73+
74+
// this is where the root command is created which contains all commands and subcommands
75+
RootCommand rootCommand = proxyHost.GetRootCommand(logger, pluginOptions, pluginCommands);
7776

78-
// get the list of available subcommands
77+
// get the list of available subcommand's names
7978
var subCommands = rootCommand.Children.OfType<Command>().Select(c => c.Name).ToArray();
8079

8180
// check if any of the subcommands are present
@@ -132,7 +131,7 @@
132131
pluginEvents.RaiseInit(new InitArgs());
133132
}
134133

135-
rootCommand.Handler = proxyHost.GetCommandHandler(pluginEvents, [.. options], loaderResults.UrlsToWatch, logger);
134+
rootCommand.Handler = proxyHost.GetCommandHandler(pluginEvents, [.. pluginOptions], loaderResults.UrlsToWatch, logger);
136135
var exitCode = await rootCommand.InvokeAsync(args);
137136
loggerFactory.Dispose();
138137
Environment.Exit(exitCode);

dev-proxy/ProxyHost.cs

Lines changed: 132 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,10 @@ public ProxyHost()
333333
ProxyCommandHandler.Configuration.ConfigFile = ConfigFile;
334334
}
335335

336-
public RootCommand GetRootCommand(ILogger logger)
336+
public RootCommand GetRootCommand(ILogger logger, Option[] pluginOptions, Command[] pluginCommands)
337337
{
338-
var command = new RootCommand {
338+
var command = new RootCommand("Dev Proxy is a command line tool for testing Microsoft Graph, SharePoint Online and any other HTTP APIs.");
339+
var options = (Option[])[
339340
_portOption,
340341
_ipAddressOption,
341342
_recordOption,
@@ -352,86 +353,87 @@ public RootCommand GetRootCommand(ILogger logger)
352353
_urlsToWatchOption!,
353354
_timeoutOption,
354355
_discoverOption,
355-
_envOption
356-
};
357-
command.Description = "Dev Proxy is a command line tool for testing Microsoft Graph, SharePoint Online and any other HTTP APIs.";
356+
_envOption,
357+
..pluginOptions
358+
];
359+
360+
command.AddOptions(options.OrderByName());
361+
358362
// _logLevelOption is set while initializing the Program
359363
// As such, it's always set here
360364
command.AddGlobalOption(_logLevelOption!);
361365

362-
var msGraphDbCommand = new Command("msgraphdb", "Generate a local SQLite database with Microsoft Graph API metadata")
363-
{
364-
Handler = new MSGraphDbCommandHandler(logger)
365-
};
366-
command.Add(msGraphDbCommand);
366+
var commands = (Command[])[
367+
CreateMsGraphDbCommand(logger),
368+
CreateConfigCommand(logger),
369+
CreateOutdatedCommand(logger),
370+
CreateJwtCommand(),
371+
CreateCertCommand(logger),
372+
..pluginCommands
373+
];
367374

368-
var configCommand = new Command("config", "Manage Dev Proxy configs");
375+
command.AddCommands(commands.OrderByName());
376+
return command;
377+
}
369378

370-
var configGetCommand = new Command("get", "Download the specified config from the Sample Solution Gallery");
371-
var configIdArgument = new Argument<string>("config-id", "The ID of the config to download");
372-
configGetCommand.AddArgument(configIdArgument);
373-
configGetCommand.SetHandler(async configId => await ConfigGetCommandHandler.DownloadConfigAsync(configId, logger), configIdArgument);
374-
configCommand.Add(configGetCommand);
379+
private static Command CreateCertCommand(ILogger logger)
380+
{
381+
var certCommand = new Command("cert", "Manage the Dev Proxy certificate");
375382

376-
var configNewCommand = new Command("new", "Create new Dev Proxy configuration file");
377-
var nameArgument = new Argument<string>("name", "Name of the configuration file")
378-
{
379-
Arity = ArgumentArity.ZeroOrOne
380-
};
381-
nameArgument.SetDefaultValue("devproxyrc.json");
382-
configNewCommand.AddArgument(nameArgument);
383-
configNewCommand.SetHandler(async name => await ConfigNewCommandHandler.CreateConfigFileAsync(name, logger), nameArgument);
384-
configCommand.Add(configNewCommand);
383+
var sortedCommands = new[]{
384+
CreateCertEnsureCommand(logger)
385+
}.OrderByName();
385386

386-
var configOpenCommand = new Command("open", "Open devproxyrc.json");
387-
configOpenCommand.SetHandler(() =>
388-
{
389-
var cfgPsi = new ProcessStartInfo(ConfigFile)
390-
{
391-
UseShellExecute = true
392-
};
393-
Process.Start(cfgPsi);
394-
});
395-
configCommand.Add(configOpenCommand);
396-
397-
command.Add(configCommand);
387+
certCommand.AddCommands(sortedCommands);
388+
return certCommand;
389+
}
398390

399-
var outdatedCommand = new Command("outdated", "Check for new version");
400-
var outdatedShortOption = new Option<bool>("--short", "Return version only");
401-
outdatedCommand.AddOption(outdatedShortOption);
402-
outdatedCommand.SetHandler(async versionOnly => await OutdatedCommandHandler.CheckVersionAsync(versionOnly, logger), outdatedShortOption);
403-
command.Add(outdatedCommand);
391+
private static Command CreateCertEnsureCommand(ILogger logger)
392+
{
393+
var certEnsureCommand = new Command("ensure", "Ensure certificates are setup (creates root if required). Also makes root certificate trusted.");
394+
certEnsureCommand.SetHandler(async () => await CertEnsureCommandHandler.EnsureCertAsync(logger));
395+
return certEnsureCommand;
396+
}
404397

398+
private static Command CreateJwtCommand()
399+
{
405400
var jwtCommand = new Command("jwt", "Manage JSON Web Tokens");
401+
402+
var sortedCommands = new[]{
403+
CreateJwtCreateCommand()
404+
}.OrderByName();
405+
406+
jwtCommand.AddCommands(sortedCommands);
407+
return jwtCommand;
408+
}
409+
410+
private static Command CreateJwtCreateCommand()
411+
{
406412
var jwtCreateCommand = new Command("create", "Create a new JWT token");
413+
407414
var jwtNameOption = new Option<string>("--name", "The name of the user to create the token for.");
408415
jwtNameOption.AddAlias("-n");
409-
jwtCreateCommand.AddOption(jwtNameOption);
410416

411417
var jwtAudienceOption = new Option<IEnumerable<string>>("--audience", "The audiences to create the token for. Specify once for each audience")
412418
{
413419
AllowMultipleArgumentsPerToken = true
414420
};
415421
jwtAudienceOption.AddAlias("-a");
416-
jwtCreateCommand.AddOption(jwtAudienceOption);
417422

418423
var jwtIssuerOption = new Option<string>("--issuer", "The issuer of the token.");
419424
jwtIssuerOption.AddAlias("-i");
420-
jwtCreateCommand.AddOption(jwtIssuerOption);
421425

422426
var jwtRolesOption = new Option<IEnumerable<string>>("--roles", "A role claim to add to the token. Specify once for each role.")
423427
{
424428
AllowMultipleArgumentsPerToken = true
425429
};
426430
jwtRolesOption.AddAlias("-r");
427-
jwtCreateCommand.AddOption(jwtRolesOption);
428431

429432
var jwtScopesOption = new Option<IEnumerable<string>>("--scopes", "A scope claim to add to the token. Specify once for each scope.")
430433
{
431434
AllowMultipleArgumentsPerToken = true
432435
};
433436
jwtScopesOption.AddAlias("-s");
434-
jwtCreateCommand.AddOption(jwtScopesOption);
435437

436438
var jwtClaimsOption = new Option<Dictionary<string, string>>("--claims",
437439
description: "Claims to add to the token. Specify once for each claim in the format \"name:value\".",
@@ -464,11 +466,9 @@ public RootCommand GetRootCommand(ILogger logger)
464466
{
465467
AllowMultipleArgumentsPerToken = true,
466468
};
467-
jwtCreateCommand.AddOption(jwtClaimsOption);
468469

469470
var jwtValidForOption = new Option<double>("--valid-for", "The duration for which the token is valid. Duration is set in minutes.");
470471
jwtValidForOption.AddAlias("-v");
471-
jwtCreateCommand.AddOption(jwtValidForOption);
472472

473473
var jwtSigningKeyOption = new Option<string>("--signing-key", "The signing key to sign the token. Minimum length is 32 characters.");
474474
jwtSigningKeyOption.AddAlias("-k");
@@ -487,7 +487,6 @@ public RootCommand GetRootCommand(ILogger logger)
487487
input.ErrorMessage = ex.Message;
488488
}
489489
});
490-
jwtCreateCommand.AddOption(jwtSigningKeyOption);
491490

492491
jwtCreateCommand.SetHandler(
493492
JwtCommandHandler.GetToken,
@@ -502,17 +501,92 @@ public RootCommand GetRootCommand(ILogger logger)
502501
jwtSigningKeyOption
503502
)
504503
);
505-
jwtCommand.Add(jwtCreateCommand);
506504

507-
command.Add(jwtCommand);
505+
var sortedOptions = new Option[]{
506+
jwtNameOption,
507+
jwtAudienceOption,
508+
jwtIssuerOption,
509+
jwtRolesOption,
510+
jwtScopesOption,
511+
jwtClaimsOption,
512+
jwtValidForOption,
513+
jwtSigningKeyOption
514+
}.OrderByName();
515+
516+
jwtCreateCommand.AddOptions(sortedOptions);
517+
return jwtCreateCommand;
518+
}
508519

509-
var certCommand = new Command("cert", "Manage the Dev Proxy certificate");
510-
var certEnsureCommand = new Command("ensure", "Ensure certificates are setup (creates root if required). Also makes root certificate trusted.");
511-
certEnsureCommand.SetHandler(async () => await CertEnsureCommandHandler.EnsureCertAsync(logger));
512-
certCommand.Add(certEnsureCommand);
513-
command.Add(certCommand);
520+
private static Command CreateOutdatedCommand(ILogger logger)
521+
{
522+
var outdatedCommand = new Command("outdated", "Check for new version");
523+
var outdatedShortOption = new Option<bool>("--short", "Return version only");
524+
outdatedCommand.SetHandler(async versionOnly => await OutdatedCommandHandler.CheckVersionAsync(versionOnly, logger), outdatedShortOption);
514525

515-
return command;
526+
var sortedOptions = new[]{
527+
outdatedShortOption
528+
}.OrderByName();
529+
530+
outdatedCommand.AddOptions(sortedOptions);
531+
return outdatedCommand;
532+
}
533+
534+
private static Command CreateConfigCommand(ILogger logger)
535+
{
536+
var configCommand = new Command("config", "Manage Dev Proxy configs");
537+
538+
var sortedCommands = new[] {
539+
CreateConfigGetCommand(logger),
540+
CreateConfigNewCommand(logger),
541+
CreateConfigOpenCommand()
542+
}.OrderByName();
543+
544+
configCommand.AddCommands(sortedCommands);
545+
return configCommand;
546+
}
547+
548+
private static Command CreateConfigGetCommand(ILogger logger)
549+
{
550+
var configGetCommand = new Command("get", "Download the specified config from the Sample Solution Gallery");
551+
var configIdArgument = new Argument<string>("config-id", "The ID of the config to download");
552+
configGetCommand.AddArgument(configIdArgument);
553+
configGetCommand.SetHandler(async configId => await ConfigGetCommandHandler.DownloadConfigAsync(configId, logger), configIdArgument);
554+
return configGetCommand;
555+
}
556+
557+
private static Command CreateConfigNewCommand(ILogger logger)
558+
{
559+
var configNewCommand = new Command("new", "Create new Dev Proxy configuration file");
560+
var nameArgument = new Argument<string>("name", "Name of the configuration file")
561+
{
562+
Arity = ArgumentArity.ZeroOrOne
563+
};
564+
nameArgument.SetDefaultValue("devproxyrc.json");
565+
configNewCommand.AddArgument(nameArgument);
566+
configNewCommand.SetHandler(async name => await ConfigNewCommandHandler.CreateConfigFileAsync(name, logger), nameArgument);
567+
return configNewCommand;
568+
}
569+
570+
private static Command CreateConfigOpenCommand()
571+
{
572+
var configOpenCommand = new Command("open", "Open devproxyrc.json");
573+
configOpenCommand.SetHandler(() =>
574+
{
575+
var cfgPsi = new ProcessStartInfo(ConfigFile)
576+
{
577+
UseShellExecute = true
578+
};
579+
Process.Start(cfgPsi);
580+
});
581+
return configOpenCommand;
582+
}
583+
584+
private static Command CreateMsGraphDbCommand(ILogger logger)
585+
{
586+
return new Command("msgraphdb", "Generate a local SQLite database with Microsoft Graph API metadata")
587+
{
588+
Handler = new MSGraphDbCommandHandler(logger)
589+
};
516590
}
517591

518592
public ProxyCommandHandler GetCommandHandler(PluginEvents pluginEvents, Option[] optionsFromPlugins, ISet<UrlToWatch> urlsToWatch, ILogger logger) => new(

0 commit comments

Comments
 (0)