Skip to content

Commit 38f7228

Browse files
refactor(tool): Update System.CommandLine
1 parent 52ada65 commit 38f7228

5 files changed

Lines changed: 168 additions & 114 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111

1212
- Move to xUnit v3 (#162)
13+
- Update System.CommandLine to latest release candidate (#163)
1314

1415
## [0.17.4.1] - 2025-09-19
1516

TypeContractor.Tool/Generator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public Generator(string assemblyPath,
5151
_casing = casing;
5252
}
5353

54-
public Task<int> Execute()
54+
public Task<int> Execute(CancellationToken cancellationToken)
5555
{
5656
var returnCode = 0;
5757

TypeContractor.Tool/Program.cs

Lines changed: 120 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,146 @@
11
using DotNetConfig;
22
using System.CommandLine;
3-
using System.CommandLine.Parsing;
43
using TypeContractor;
54
using TypeContractor.Logger;
65
using TypeContractor.Tool;
76

87
var config = Config.Build("typecontractor.config");
98

109
var rootCommand = new RootCommand("Tool for generating TypeScript definitions from C# code");
11-
var assemblyOption = new Option<string>("--assembly", "Path to the assembly to start with. Will be relative to the current directory");
12-
var outputOption = new Option<string>("--output", "Output path to write to. Will be relative to the current directory");
13-
var relativeRootOption = new Option<string>("--root", "Relative root for generating cleaner imports. For example '~/api'");
14-
var cleanOption = new Option<CleanMethod>("--clean", () => CleanMethod.Smart, "Choose how to clean up no longer relevant type files in output directory. Danger!");
15-
var replaceOptions = new Option<string[]>("--replace", "Provide one replacement in the form '<search>:<replace>'. Can be repeated");
16-
var stripOptions = new Option<string[]>("--strip", "Provide a prefix to strip out of types. Can be repeated");
17-
var mapOptions = new Option<string[]>("--custom-map", "Provide a custom type map in the form '<from>:<to>'. Can be repeated");
18-
var packsOptions = new Option<string>("--packs-path", () => @"C:\Program Files\dotnet\packs\", "Path where dotnet is installed and reference assemblies can be found.");
19-
var dotnetVersionOptions = new Option<int>("--dotnet-version", () => 8, "Major version of dotnet to look for");
20-
var logLevelOptions = new Option<LogLevel>("--log-level", () => LogLevel.Info);
21-
var buildZodSchemasOptions = new Option<bool>("--build-zod-schemas", () => false, "Enable experimental support for Zod schemas alongside generated types.");
22-
var generateApiClientsOptions = new Option<bool>("--generate-api-clients", () => false, "Enable experimental support for auto-generating API clients for each endpoint.");
23-
var apiClientsTemplateOptions = new Option<string>("--api-client-template", () => "aurelia", "Template to use for API clients. Either 'aurelia', 'react-axios' (built-in) or a path to a Handlebars file, including extension");
24-
var casingOptions = new Option<Casing>("--casing", () => Casing.Pascal, "Casing to use for generated file names");
25-
assemblyOption.IsRequired = true;
26-
outputOption.IsRequired = true;
27-
28-
rootCommand.AddOption(assemblyOption);
29-
rootCommand.AddOption(outputOption);
30-
rootCommand.AddOption(relativeRootOption);
31-
rootCommand.AddOption(cleanOption);
32-
rootCommand.AddOption(replaceOptions);
33-
rootCommand.AddOption(stripOptions);
34-
rootCommand.AddOption(mapOptions);
35-
rootCommand.AddOption(packsOptions);
36-
rootCommand.AddOption(dotnetVersionOptions);
37-
rootCommand.AddOption(logLevelOptions);
38-
rootCommand.AddOption(buildZodSchemasOptions);
39-
rootCommand.AddOption(generateApiClientsOptions);
40-
rootCommand.AddOption(apiClientsTemplateOptions);
41-
rootCommand.AddOption(casingOptions);
42-
43-
apiClientsTemplateOptions.AddValidator(result =>
44-
{
45-
var value = result.GetValueForOption(apiClientsTemplateOptions)!;
10+
11+
var assemblyOption = new Option<string>("--assembly")
12+
{
13+
Description = "Path to the assembly to start with. Will be relative to the current directory",
14+
Required = true,
15+
};
16+
17+
var outputOption = new Option<string>("--output")
18+
{
19+
Description = "Output path to write to. Will be relative to the current directory",
20+
Required = true,
21+
};
22+
23+
var relativeRootOption = new Option<string>("--root")
24+
{
25+
Description = "Relative root for generating cleaner imports. For example '~/api'",
26+
};
27+
28+
var cleanOption = new Option<CleanMethod>("--clean")
29+
{
30+
DefaultValueFactory = (arg) => CleanMethod.Smart,
31+
Description = "Choose how to clean up no longer relevant type files in output directory. Danger!",
32+
};
33+
34+
var replaceOptions = new Option<string[]>("--replace")
35+
{
36+
Description = "Provide one replacement in the form '<search>:<replace>'. Can be repeated",
37+
};
38+
39+
var stripOptions = new Option<string[]>("--strip")
40+
{
41+
Description = "Provide a prefix to strip out of types. Can be repeated",
42+
};
43+
44+
var mapOptions = new Option<string[]>("--custom-map")
45+
{
46+
Description = "Provide a custom type map in the form '<from>:<to>'. Can be repeated",
47+
};
48+
49+
var packsOptions = new Option<string>("--packs-path")
50+
{
51+
DefaultValueFactory = (arg) => @"C:\Program Files\dotnet\packs\",
52+
Description = "Path where dotnet is installed and reference assemblies can be found.",
53+
};
54+
55+
var dotnetVersionOptions = new Option<int>("--dotnet-version")
56+
{
57+
DefaultValueFactory = (arg) => 8,
58+
Description = "Major version of dotnet to look for",
59+
};
60+
61+
var logLevelOptions = new Option<LogLevel>("--log-level")
62+
{
63+
DefaultValueFactory = (arg) => LogLevel.Info,
64+
};
65+
66+
var buildZodSchemasOptions = new Option<bool>("--build-zod-schemas")
67+
{
68+
DefaultValueFactory = (arg) => false,
69+
Description = "Enable experimental support for Zod schemas alongside generated types.",
70+
};
71+
72+
var generateApiClientsOptions = new Option<bool>("--generate-api-clients")
73+
{
74+
DefaultValueFactory = (arg) => false,
75+
Description = "Enable experimental support for auto-generating API clients for each endpoint.",
76+
};
77+
78+
var apiClientsTemplateOptions = new Option<string>("--api-client-template")
79+
{
80+
DefaultValueFactory = (arg) => "aurelia",
81+
Description = "Template to use for API clients. Either 'aurelia', 'react-axios' (built-in) or a path to a Handlebars file, including extension",
82+
};
83+
84+
var casingOptions = new Option<Casing>("--casing")
85+
{
86+
DefaultValueFactory = (arg) => Casing.Pascal,
87+
Description = "Casing to use for generated file names",
88+
};
89+
90+
rootCommand.Options.Add(assemblyOption);
91+
rootCommand.Options.Add(outputOption);
92+
rootCommand.Options.Add(relativeRootOption);
93+
rootCommand.Options.Add(cleanOption);
94+
rootCommand.Options.Add(replaceOptions);
95+
rootCommand.Options.Add(stripOptions);
96+
rootCommand.Options.Add(mapOptions);
97+
rootCommand.Options.Add(packsOptions);
98+
rootCommand.Options.Add(dotnetVersionOptions);
99+
rootCommand.Options.Add(logLevelOptions);
100+
rootCommand.Options.Add(buildZodSchemasOptions);
101+
rootCommand.Options.Add(generateApiClientsOptions);
102+
rootCommand.Options.Add(apiClientsTemplateOptions);
103+
rootCommand.Options.Add(casingOptions);
104+
105+
apiClientsTemplateOptions.Validators.Add(result =>
106+
{
107+
var value = result.GetValue(apiClientsTemplateOptions)!;
46108
if (value.Equals("aurelia", StringComparison.CurrentCultureIgnoreCase) || value.Equals("react-axios", StringComparison.CurrentCultureIgnoreCase))
47109
return;
48110

49-
var generateClients = result.GetValueForOption(generateApiClientsOptions);
111+
var generateClients = result.GetValue(generateApiClientsOptions);
50112
if (!generateClients)
51113
{
52-
result.ErrorMessage = $"Must generate API clients for --{apiClientsTemplateOptions.Name} to have any effect.";
114+
result.AddError($"Must generate API clients for --{apiClientsTemplateOptions.Name} to have any effect.");
53115
return;
54116
}
55117

56118
if (!File.Exists(value))
57119
{
58-
result.ErrorMessage = $"The template specified does not exist or is not readable. Searched for {Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), value))}.";
120+
result.AddError($"The template specified does not exist or is not readable. Searched for {Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), value))}.");
59121
return;
60122
}
61123
});
62124

63125
// Apply configuration from file, if any
64126
rootCommand = rootCommand.WithConfigurableDefaults("typecontractor", config);
65127

66-
rootCommand.SetHandler(async (context) =>
67-
{
68-
var assemblyOptionValue = context.ParseResult.GetValueForOption(assemblyOption)!;
69-
var outputValue = context.ParseResult.GetValueForOption(outputOption)!;
70-
var relativeRootValue = context.ParseResult.GetValueForOption(relativeRootOption);
71-
var cleanValue = context.ParseResult.GetValueForOption(cleanOption);
72-
var replacementsValue = context.ParseResult.GetValueForOption(replaceOptions) ?? [];
73-
var stripValue = context.ParseResult.GetValueForOption(stripOptions) ?? [];
74-
var customMapsValue = context.ParseResult.GetValueForOption(mapOptions) ?? [];
75-
var packsPathValue = context.ParseResult.GetValueForOption(packsOptions)!;
76-
var dotnetVersionValue = context.ParseResult.GetValueForOption(dotnetVersionOptions);
77-
var logLevelValue = context.ParseResult.GetValueForOption(logLevelOptions);
78-
var buildZodSchemasValue = context.ParseResult.GetValueForOption(buildZodSchemasOptions);
79-
var generateApiClientsValue = context.ParseResult.GetValueForOption(generateApiClientsOptions);
80-
var apiClientsTemplateValue = context.ParseResult.GetValueForOption(apiClientsTemplateOptions)!;
81-
var casingValue = context.ParseResult.GetValueForOption(casingOptions);
128+
rootCommand.SetAction(async (parseResult, cancellationToken) =>
129+
{
130+
var assemblyOptionValue = parseResult.GetValue(assemblyOption)!;
131+
var outputValue = parseResult.GetValue(outputOption)!;
132+
var relativeRootValue = parseResult.GetValue(relativeRootOption);
133+
var cleanValue = parseResult.GetValue(cleanOption);
134+
var replacementsValue = parseResult.GetValue(replaceOptions) ?? [];
135+
var stripValue = parseResult.GetValue(stripOptions) ?? [];
136+
var customMapsValue = parseResult.GetValue(mapOptions) ?? [];
137+
var packsPathValue = parseResult.GetValue(packsOptions)!;
138+
var dotnetVersionValue = parseResult.GetValue(dotnetVersionOptions);
139+
var logLevelValue = parseResult.GetValue(logLevelOptions);
140+
var buildZodSchemasValue = parseResult.GetValue(buildZodSchemasOptions);
141+
var generateApiClientsValue = parseResult.GetValue(generateApiClientsOptions);
142+
var apiClientsTemplateValue = parseResult.GetValue(apiClientsTemplateOptions)!;
143+
var casingValue = parseResult.GetValue(casingOptions);
82144

83145
Log.Instance = new ConsoleLogger(logLevelValue);
84146
var generator = new Generator(assemblyOptionValue,
@@ -95,7 +157,8 @@
95157
apiClientsTemplateValue,
96158
casingValue);
97159

98-
context.ExitCode = await generator.Execute();
160+
return await generator.Execute(cancellationToken);
99161
});
100162

101-
return await rootCommand.InvokeAsync(args);
163+
var parsedResult = rootCommand.Parse(args);
164+
return await parsedResult.InvokeAsync();

TypeContractor.Tool/TypeContractor.Tool.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -26,7 +26,7 @@
2626

2727
<ItemGroup>
2828
<PackageReference Include="DotNetConfig" Version="1.2.0" />
29-
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
29+
<PackageReference Include="System.CommandLine" Version="2.0.0-rc.1.25451.107" />
3030
</ItemGroup>
3131

3232
<ItemGroup>

0 commit comments

Comments
 (0)