Releases: dotmake-build/command-line
DotMake.CommandLine v2.6.0
-
Fixed: With v2.5.8, auto short form aliases were still being added even when they already existed, causing the unnecessary conflict error.
-
Fixed: When calling
Cli.Run<>
with sub-commands but not a root command, help output did not include our custom changes.
This happened because we were not adding our settings like HelpOption with CustomHelpAction, when the command was not a root command.
For example, help output showed (as System.CommandLine default output):--settingsfile (REQUIRED) Path to settings file
where it should show:
--settingsfile Path to settings file [required]
So now, even if it's a sub-command, its root command should be found and those settings should be added there.
Additionaly, the fix introduced first in v2.0.0
System.CommandLine.RootCommand returned by Cli.GetConfiguration should have correct parents.
turns out it disabled help output if you usedCli.Run<>
with level-2 sub-commands or above,
due to a subtle bug (root folder was not being populated so help option was lost).
DotMake.CommandLine v2.5.8
-
Improved: CliNamer to support a parent CliNamer. Unique names were only checked within the command builder
so the current command was not aware of names of sibling commands. This is now fixed.
And from now on, we will throw an explanatory exception when there is a name or alias conflict like this:System.Exception: CommandAlias 'c' for 'createschema' conflicts with parent CommandAlias for 'checkfornewtools'!
This is because System.CommandLine threw a vague exception when there was a conflict during parsing:
System.ArgumentException: An item with the same key has already been added. Key: c
Unique names are checked for DirectiveName (surrounded with []), CommandName, CommandAlias, OptionName, OptionAlias.
Auto short form aliases will be always derived from symbol name even when a specific name was specified,
because if user specifies a specific name with all lowercase likecheckfornewtools
, we cannot split word boundaries.
Help output will show short aliases first consistently especially for commands (order by prefix and then by length). -
Improved: Set the values for the parent command accessors before directive, option and argument properties so that
property get accessors can access them:public class DefaultSettingsCliCommand { [CliOption] public string SettingsFile { get => RootCommand.SettingsFile; //<-- Avoid NullReferenceException with `RootCommand` here set => RootCommand.SettingsFile = value; } public RootCliCommand RootCommand { get; set; } }
-
Fixed: Usage of
[CliArgument].AllowedValues
caused compiler error CS1061 becauseArgument<T>.AcceptOnlyFromAmong(...)
was moved to extension methodSystem.CommandLine.ArgumentValidation.AcceptOnlyFromAmong(...)
in latest System.CommandLine. -
Improved: Added empty
publish/
folder to the repository. Insrc/nuget.config
we add a custom package source so that we can consume our own built nuget package locally:<add key="DotMake Local" value="..\publish" />
So as the
publish/
folder was not commited to the repository, users could have a problem building the project due to NuGet Package Manager complaining about non-existingpublish/
folder.
DotMake.CommandLine v2.5.6
-
Improved: Switched back to using official NuGet feed from DotNet Daily Builds feed for System.CommandLine as now
2.0.0-beta5.25306.1
is released.
Now the DLL will no more be packed into our package and System.CommandLine will appear as NuGet dependency. -
Fixed: Error
DMCLI01: DotMake.CommandLine.SourceGeneration -> System.ArgumentException: Syntax node is not within syntax tree
when using partial class for a command which has a nested class for a subcommand.
Reason: SemanticModel from parent symbol can be different for a child symbol, especially for nested classes in a partial class.
Solution: Check if SyntaxTree is same for current SemanticModel and SyntaxNode, if not, get a new SemanticModel
viasemanticModel.Compilation.GetSemanticModel(syntaxNode.SyntaxTree)
. -
Fixed: The new enum
CliNameAutoGenerate
in v2.5.0 is a[Flags]
enum and it was not translated to C# code correctly
when multiple flags were combined:[CliCommand( ShortFormAutoGenerate = CliNameAutoGenerate.Options | CliNameAutoGenerate.Arguments )] public class RootHelpOnEmptyCliCommand
-
Improved: Added
.cmd
batch scripts to repository root for easier building:1. Build TestApp.cmd 2. Build Nuget Package.cmd 3.1. Build TestApp.Nuget.cmd 3.2. Build TestApp.NugetDI.cmd 3.3. Build TestApp.NugetAot.cmd 4. Build Api Docs WebSite.cmd
Output results can be found in
publish
folder.
DotMake.CommandLine v2.5.0
-
Added:
[CliDirective]
attribute for properties to define custom directives:[CliCommand(Description = "A root cli command with directives")] public class DirectiveCliCommand { [CliDirective] public bool Debug { get; set; } [CliDirective] public string Directive2 { get; set; } [CliDirective] public string[] Vars { get; set; } public void Run(CliContext context) { if (context.IsEmpty()) context.ShowHelp(); else { Console.WriteLine($"Directive '{nameof(Debug)}' = {StringExtensions.FormatValue(Debug)}"); Console.WriteLine($"Directive '{nameof(Directive2)}' = {StringExtensions.FormatValue(Directive2)}"); Console.WriteLine($"Directive '{nameof(Vars)}' = {StringExtensions.FormatValue(Vars)}"); } } }
Currently only
bool
,string
andstring[]
types are supported for[CliDirective]
properties.
Here is sample usage and output:src\TestApp\bin\Debug\net8.0>TestApp [debug] [directive-2:val1] [vars:val2] [vars:val3] Directive 'Debug' = true Directive 'Directive2' = "val1" Directive 'Vars' = ["val2", "val3"]
-
Improved:
CliNameCasingConvention
will now generate better CLI identifiers, i.e. it will treat unicode letter casing
and unicode numbers in class and property names correctly.StringExtensions.ToCase()
will handle strings that contain
unicode space and punctuation characters correctly. -
Improved: Smarter auto-generated short form aliases. In previous versions, only first letter was added as short form
if it wasn't already used. Now letters of every word in the option name will be used to reduce conflicts.
Also short form aliases will now also be generated for commands.public class ShortFormCliCommand { [CliOption(Alias = "o2")] public string Oauth2GrantType { get; set; } = ""; [CliOption] public string Oauth2TokenUrl { get; set; } = ""; [CliOption] public string Oauth2ClientId { get; set; } = ""; [CliOption] public string Oauth2ClientSecret { get; set; } = ""; [CliOption] public string Sha256 { get; set; } = ""; }
Options: -o2, --oauth-2-grant-type -o2tu, --oauth-2-token-url -o2ci, --oauth-2-client-id -o2cs, --oauth-2-client-secret -s256, --sha-256
-
Added:
Alias
property toCliCommand
andCliOption
attributes in addition to existingAliases
property.
Alias
property is more useful, as most of the time you will want to add a single alias which is usually a short form alias.
When this property is set, it will override the auto-generated short form alias so when you are not happy with a specific
auto-generated alias you can override it instead of disabling all viaShortFormAutoGenerate = false
.When manually setting this property, if you don't specify a prefix, it will be prefixed automatically according
toShortFormPrefixConvention
(e.g.-o
or--o
or/o
) unless it's set to newly addedCliNamePrefixConvention.None
.
This will now be also same for existingAliases
property but except, aliases without prefix will be prefixed automatically
according toNamePrefixConvention
instead. -
Added:
CliNameAutoGenerate
enum and changed type of[CliCommand].ShortFormAutoGenerate
frombool
to this enum.
Added new[CliCommand].NameAutoGenerate
property with same enum.
Auto-generated names can be disabled for all or specific CLI symbol types via[CliCommand].NameAutoGenerate
.
Auto-generated short form aliases can be disabled for all or specific CLI symbol types via[CliCommand].ShortFormAutoGenerate
.
DotMake.CommandLine v2.4.3
-
Fixed: In previous version, using
System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject
causedIL2072
trimming warnings.
We now changed handling of completions so that we never need an uninitialized instance of definition type, thus removed usage ofGetUninitializedObject
to fix the issue:ICliAddCompletions
interface is now renamed toICliGetCompletions
.void AddCompletions(string propertyName, List<Func<CompletionContext, IEnumerable<CompletionItem>>> completionSources)
interface method
is now changed toIEnumerable<CompletionItem> GetCompletions(string propertyName, CompletionContext completionContext)
.
So now, instead we will use
Bind
method to get cached definition instance and callGetCompletions
interface method.
The signature change also made implementing interface method more easy and straightforward.
DotMake.CommandLine v2.4.2
-
Updated to daily build 2.0.0-beta5.25302.104 of System.CommandLine (cannot update beyond this version for now, as Help related classes are made internal).
-
Improved: In previous versions, for setting DefaultValueFactory of options and arguments, a default instance of definition type was being created and its properties were being accessed.
However if you used dependency injection, this caused unnecessary instantiations because every definition type in the hierarchy had to be instantiated for building the command hierarchy.
From now on, CliCommandBuilder.Build method will not create a default instance of definition type to avoid IServiceProvider instantiations for other commands in the hierarchy.
Instead, we will read the property initializer's SyntaxNode, fully-qualify symbols and then use that SyntaxNode directly for setting DefaultValueFactory.
However, we still need an uninitialized instance for being able to call AddCompletions method that comes with ICliAddCompletions interface, for now.
The uninitialized instance can not be used for property access, as the constructor is skipped, properties will come as null but it can be used for calling method access.
Creating an uninitialized instance will not trigger dependency injection, so the problem will be avoided. -
Fixed: Property initializers were being checked for
SyntaxKind.NullKeyword
where they should be checked forSyntaxKind.NullLiteralExpression
instead
to auto-determineRequired = true
when it's not specified explicitly.
DotMake.CommandLine v2.4.0
-
Updated to latest daily build 2.0.0-beta5.25257.101 of System.CommandLine.
-
Improved: The order of subcommands in the array passed to
CliCommandAttribute.Children
property should be respected (should not be reversed).
So the help output andCliContext.ShowHierarchy
should display the correct order of subcommands. -
Improved: The order of inherited properties with
[CliOption]
or[CliArgument]
attribute, should be respected.
The definition order in the most derived class should be preserved. This is especially important for[CliArgument]
properties because if a property is overriden in class, it should imply that the order is the overriden definition order.
DotMake.CommandLine v2.3.0
-
Added: Custom completions for options and arguments support. Normally completions were automatically added for options or arguments with Enum types.
Now, you can add (or clear/modify) completions for any option or argument.
In your command class, inheritICliAddCompletions
and implementAddCompletions
method.
This method will be called for every option and argument in your class.
In the method, you should switch according to the property name
which corresponds to the option or argument whose completions will be modified.using System; using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Completions; using System.Linq; using DotMake.CommandLine; [CliCommand(Description = "A root cli command with completions for options and arguments")] public class AddCompletionsCliCommand : ICliAddCompletions { [CliOption(Description = "Description for DateOption")] public DateTime DateOption { get; set; } [CliArgument(Description = "Description for FruitArgument")] public string FruitArgument { get; set; } = "DefaultForFruitArgument"; public void Run(CliContext context) { if (context.IsEmptyCommand()) context.ShowHelp(); else context.ShowValues(); } public void AddCompletions(string propertyName, List<Func<CompletionContext, IEnumerable<CompletionItem>>> completionSources) { switch (propertyName) { case nameof(DateOption): completionSources.Add(completionContext => { var today = System.DateTime.Today; var dates = new List<CompletionItem>(); foreach (var i in Enumerable.Range(1, 7)) { var date = today.AddDays(i); dates.Add(new CompletionItem( label: date.ToShortDateString(), sortText: $"{i:2}")); } return dates; }); break; case nameof(FruitArgument): completionSources.Add("apple", "orange", "banana"); break; } } }
The dynamic tab completion list created by this code also appears in help output:
DotMake Command-Line TestApp v2.3.0 Copyright © 2023-2025 DotMake A root cli command with completions for options and arguments Usage: TestApp [<fruit>] [options] Arguments: <apple|banana|orange> Description for FruitArgument [default: DefaultForFruitArgument] Options: -d, --date Description for DateOption [default: 1.01.0001 00:00:00] <22.04.2025|23.04.2025|24.04.2025|25.04.2025|26.04.2025|27 .04.2025|28.04.2025> -?, -h, --help Show help and usage information -v, --version Show version information
-
Added: We provide new interfaces
ICliRun
,ICliRunWithReturn
,ICliRunWithContext
,ICliRunWithContextAndReturn
and async versionsICliRunAsync
,ICliRunAsyncWithReturn
,ICliRunAsyncWithContext
,ICliRunAsyncWithContextAndReturn
that you can inherit in your command class.
Normally you don't need an interface for a handler method as the source generator can detect it automatically,
but the interfaces can be used to prevent your IDE complain about unused method in class.[CliCommand] public class RunAsyncWithReturnCliCommand : ICliRunAsyncWithReturn { public async Task<int> RunAsync() { } }
-
Fixed: ServiceProvider.Dispose() should be called in
AppDomain.CurrentDomain.ProcessExit
event, not at the end of the command execution.
AsCli.Run
may be called multiple time, for example for testing purpose. -
Improved:
CliReferenceDependantInput
should be instantiated once per compilation change for performance optimization.
In older versions, it was re-instantiated inCliCommandInput
for non-nested commands.
DotMake.CommandLine v2.2.0
-
Updated to latest daily build 2.0.0-beta5.25218.1 of System.CommandLine.
-
Fixed: New CliCommandAttribute.Children property did not work across projects.
-
Improved: Calculating parent tree is hard (across projects) and expensive in generator so we removed
Collect and Re-generate logic in CliIncrementalGenerator that was added in v2.1.0.
We can already figure out whole command hierarchy at runtime.- for parent command accessors, we will only check if property type has CliCommand attribute.
- for circular dependency, we will do some simple checks for types in Parent and Children attributes and class type itself.
Full circular dependency can be detected at runtime.
-
Fixed: ServiceProvider.Dispose() should be called at the end of the command execution.
DotMake.CommandLine v2.1.0
-
Updated to latest daily build 2.0.0-beta5.25209.2 of System.CommandLine.
-
Improved: Redesigned the source generator. Separated code into Inputs and Outputs. This way collecting metadata (inputs)
and generating source code (outputs) can be maintained more easily. For example, generating outputs for a different CLI library
may be possible in the future. But the main reason for the redesign was to handle input cli command hierarchy in a better way. -
Added: CliCommandAttribute.Children property. It's set to an array of types.
This is similar to existing Parent property but in reverse. For example if you have a ConsoleAppParent and ConsoleAppChild
projects and you have the root command in ConsoleAppParent, the external child commands in ConsoleAppChild had to reference
ConsoleAppParent to that Parent property could refer to the root command. This compiled but did not work
because ConsoleAppChild references ConsoleAppParent where logically it should be the opposite, i.e. ConsoleAppParent
does not know about ConsoleAppChild so it can't find the external child commands inside it.
For this reason, we added Children property so that ConsoleAppParent can reference ConsoleAppChild./* Command hierarchy in below example is: TestApp └╴external-level-1 └╴external-level-2 */ [CliCommand( Description = "A root cli command with external children", Children = new [] { typeof(ExternalLevel1SubCliCommand) } )] public class RootWithExternalChildrenCliCommand { [CliOption(Description = "Description for Option1")] public string Option1 { get; set; } = "DefaultForOption1"; [CliArgument(Description = "Description for Argument1")] public string Argument1 { get; set; } = "DefaultForArgument1"; public void Run(CliContext context) { if (context.IsEmptyCommand()) context.ShowHierarchy(); else context.ShowValues(); } } [CliCommand( Description = "An external level 1 sub-command", Children = new[] { typeof(ExternalLevel2SubCliCommand) } )] public class ExternalLevel1SubCliCommand { [CliOption(Description = "Description for Option1")] public string Option1 { get; set; } = "DefaultForOption1"; [CliArgument(Description = "Description for Argument1")] public string Argument1 { get; set; } public void Run(CliContext context) { context.ShowValues(); } } [CliCommand(Description = "An external level 2 sub-command")] public class ExternalLevel2SubCliCommand { [CliOption(Description = "Description for Option1")] public string Option1 { get; set; } = "DefaultForOption1"; [CliArgument(Description = "Description for Argument1")] public string Argument1 { get; set; } public void Run(CliContext context) { context.ShowValues(); } }
-
Added: CliContext.ShowHierarchy method which shows hierarchy for all commands.
It will start from the root command and show a tree. Useful for testing a command.TestApp ├╴external-level-1-with-nested │ └╴level-2 └╴level_1 └╴external_level_2_with_nested └╴level_3