mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-05 02:47:21 +00:00
Moved the build script to System.CommandLine (#1159)
This commit is contained in:
1 parent
f0fa1411c8
commit
54c3f0b3e0
12 files changed
+232
-70
No files matched your search
@@ -11,10 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cocona" Version="2.2.0" />
|
||||
|
||||
<!-- Pins Cocona's transitive Microsoft.Extensions.Hosting 6.0.0, which pulled in the vulnerable System.Text.Json 6.0.0 (GHSA-8g4q-xg66-9fp4) -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.14" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,10 +4,19 @@ namespace Build.Commands;
|
||||
|
||||
public sealed class AssistantPluginHashCommand
|
||||
{
|
||||
[Command("assistant-plugin-hash", Description = "Compute the canonical assistant-plugin hash for a plugin directory")]
|
||||
public void ComputeAssistantPluginHash(
|
||||
[Argument(Description = "Path to the assistant plugin directory")] string pluginDir,
|
||||
[Option("lua-snippet", Description = "Also print a Lua snippet for CONFIG[\"SETTINGS\"]")] bool luaSnippet = false)
|
||||
/// <summary>
|
||||
/// Defines the command "assistant-plugin-hash".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var pluginDir = new Argument<string>("pluginDir") { Description = "Path to the assistant plugin directory" };
|
||||
var luaSnippet = new Option<bool>("--lua-snippet") { Description = "Also print a Lua snippet for CONFIG[\"SETTINGS\"]" };
|
||||
var command = new Command("assistant-plugin-hash", "Compute the canonical assistant-plugin hash for a plugin directory") { pluginDir, luaSnippet };
|
||||
command.SetAction(parseResult => new AssistantPluginHashCommand().ComputeAssistantPluginHash(parseResult.GetRequiredValue(pluginDir), parseResult.GetValue(luaSnippet)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public void ComputeAssistantPluginHash(string pluginDir, bool luaSnippet = false)
|
||||
{
|
||||
if (!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed class CheckRidsCommand
|
||||
{
|
||||
[Command("check-rids", Description = "Check the RIDs for the current OS")]
|
||||
/// <summary>
|
||||
/// Defines the command "check-rids".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var command = new Command("check-rids", "Check the RIDs for the current OS");
|
||||
command.SetAction(_ => new CheckRidsCommand().GetRids());
|
||||
return command;
|
||||
}
|
||||
|
||||
public void GetRids()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
|
||||
@@ -2,10 +2,6 @@ using System.Text.RegularExpressions;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed partial class CollectI18NKeysCommand
|
||||
@@ -43,7 +39,16 @@ public sealed partial class CollectI18NKeysCommand
|
||||
END_TAG2
|
||||
];
|
||||
|
||||
[Command("collect-i18n", Description = "Collect I18N keys")]
|
||||
/// <summary>
|
||||
/// Defines the command "collect-i18n".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var command = new Command("collect-i18n", "Collect I18N keys");
|
||||
command.SetAction((_, _) => new CollectI18NKeysCommand().CollectI18NKeys());
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task CollectI18NKeys()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
|
||||
@@ -9,17 +9,38 @@ using SharedTools;
|
||||
namespace Build.Commands;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
public sealed partial class UpdateMetadataCommands
|
||||
{
|
||||
[Command("release", Description = "Prepare & build the next release")]
|
||||
public async Task Release(
|
||||
[Option("action", ['a'], Description = "The release action: patch, minor, or major")] PrepareAction action = PrepareAction.NONE,
|
||||
[Option("version", ['v'], Description = "Set a specific version directly, e.g., 26.1.2")] string? version = null,
|
||||
[Option("offline", Description = "Skip downloads and use locally available build dependencies")] bool offline = false)
|
||||
/// <summary>
|
||||
/// Creates the option "--action" of the commands which prepare a release.
|
||||
/// </summary>
|
||||
private static Option<PrepareAction> CreateActionOption() => new("--action", "-a") { Description = "The release action: build, month, or year, which raise the release, the month, or the year of the version, e.g., 26.11.1 becomes 26.11.2, 26.12.1, or 27.1.1", DefaultValueFactory = _ => PrepareAction.NONE };
|
||||
|
||||
/// <summary>
|
||||
/// Creates the option "--version" of the commands which prepare a release.
|
||||
/// </summary>
|
||||
private static Option<string> CreateVersionOption() => new("--version", "-v") { Description = "Set a specific version directly, e.g., 26.1.2" };
|
||||
|
||||
/// <summary>
|
||||
/// Creates the option "--offline" of the commands which build the app.
|
||||
/// </summary>
|
||||
private static Option<bool> CreateOfflineOption() => new("--offline") { Description = "Skip downloads and use locally available build dependencies" };
|
||||
|
||||
/// <summary>
|
||||
/// Defines the command "release".
|
||||
/// </summary>
|
||||
public static Command CreateReleaseCommand()
|
||||
{
|
||||
var action = CreateActionOption();
|
||||
var version = CreateVersionOption();
|
||||
var offline = CreateOfflineOption();
|
||||
var command = new Command("release", "Prepare & build the next release") { action, version, offline };
|
||||
command.SetAction((parseResult, _) => new UpdateMetadataCommands().Release(parseResult.GetValue(action), parseResult.GetValue(version), parseResult.GetValue(offline)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task Release(PrepareAction action = PrepareAction.NONE, string? version = null, bool offline = false)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
throw new CommandExitedException(1);
|
||||
@@ -57,9 +78,18 @@ public sealed partial class UpdateMetadataCommands
|
||||
Console.WriteLine(" \"Release Process\" in app/Build/AGENTS.md.");
|
||||
}
|
||||
|
||||
[Command("rebuild-release", Description = "Prepare & build a new build of the current release")]
|
||||
public async Task RebuildRelease(
|
||||
[Option("offline", Description = "Skip downloads and use locally available build dependencies")] bool offline = false)
|
||||
/// <summary>
|
||||
/// Defines the command "rebuild-release".
|
||||
/// </summary>
|
||||
public static Command CreateRebuildReleaseCommand()
|
||||
{
|
||||
var offline = CreateOfflineOption();
|
||||
var command = new Command("rebuild-release", "Prepare & build a new build of the current release") { offline };
|
||||
command.SetAction((parseResult, _) => new UpdateMetadataCommands().RebuildRelease(parseResult.GetValue(offline)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task RebuildRelease(bool offline = false)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
throw new CommandExitedException(1);
|
||||
@@ -106,10 +136,19 @@ public sealed partial class UpdateMetadataCommands
|
||||
await this.Build(offline, skipVerify: true);
|
||||
}
|
||||
|
||||
[Command("update-metainfo", Description = "Update the AppStream metainfo entry of one release from its changelog")]
|
||||
public async Task UpdateMetainfo(
|
||||
[Option("version", ['v'], Description = "The release version, e.g., 26.1.2. Defaults to the version from the metadata")] string? version = null,
|
||||
[Option("date", ['d'], Description = "The release date as yyyy-MM-dd. Defaults to the build time from the metadata")] string? date = null)
|
||||
/// <summary>
|
||||
/// Defines the command "update-metainfo".
|
||||
/// </summary>
|
||||
public static Command CreateUpdateMetainfoCommand()
|
||||
{
|
||||
var version = new Option<string>("--version", "-v") { Description = "The release version, e.g., 26.1.2. Defaults to the version from the metadata" };
|
||||
var date = new Option<string>("--date", "-d") { Description = "The release date as yyyy-MM-dd. Defaults to the build time from the metadata" };
|
||||
var command = new Command("update-metainfo", "Update the AppStream metainfo entry of one release from its changelog") { version, date };
|
||||
command.SetAction((parseResult, _) => new UpdateMetadataCommands().UpdateMetainfo(parseResult.GetValue(version), parseResult.GetValue(date)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task UpdateMetainfo(string? version = null, string? date = null)
|
||||
{
|
||||
const int APP_VERSION_INDEX = 0;
|
||||
const int BUILD_TIME_INDEX = 1;
|
||||
@@ -140,7 +179,16 @@ public sealed partial class UpdateMetadataCommands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("update-versions", Description = "The command will update the package versions in the metadata file")]
|
||||
/// <summary>
|
||||
/// Defines the command "update-versions".
|
||||
/// </summary>
|
||||
public static Command CreateUpdateVersionsCommand()
|
||||
{
|
||||
var command = new Command("update-versions", "The command will update the package versions in the metadata file");
|
||||
command.SetAction((_, _) => new UpdateMetadataCommands().UpdateVersions());
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task UpdateVersions()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
@@ -156,10 +204,19 @@ public sealed partial class UpdateMetadataCommands
|
||||
await this.UpdateVectorStoreVersion();
|
||||
}
|
||||
|
||||
[Command("prepare", Description = "Prepare the metadata for the next release")]
|
||||
public async Task Prepare(
|
||||
[Option("action", ['a'], Description = "The release action: patch, minor, or major")] PrepareAction action = PrepareAction.NONE,
|
||||
[Option("version", ['v'], Description = "Set a specific version directly, e.g., 26.1.2")] string? version = null)
|
||||
/// <summary>
|
||||
/// Defines the command "prepare".
|
||||
/// </summary>
|
||||
public static Command CreatePrepareCommand()
|
||||
{
|
||||
var action = CreateActionOption();
|
||||
var version = CreateVersionOption();
|
||||
var command = new Command("prepare", "Prepare the metadata for the next release") { action, version };
|
||||
command.SetAction((parseResult, _) => new UpdateMetadataCommands().Prepare(parseResult.GetValue(action), parseResult.GetValue(version)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task Prepare(PrepareAction action = PrepareAction.NONE, string? version = null)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
@@ -236,10 +293,19 @@ public sealed partial class UpdateMetadataCommands
|
||||
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "Pages", "Information.razor.cs")));
|
||||
}
|
||||
|
||||
[Command("build", Description = "Build MindWork AI Studio")]
|
||||
public async Task Build(
|
||||
[Option("offline", Description = "Skip downloads and use locally available build dependencies")] bool offline = false,
|
||||
[Option("skip-verify", Description = "Skip the quality gate which otherwise runs before anything is built")] bool skipVerify = false)
|
||||
/// <summary>
|
||||
/// Defines the command "build".
|
||||
/// </summary>
|
||||
public static Command CreateBuildCommand()
|
||||
{
|
||||
var offline = CreateOfflineOption();
|
||||
var skipVerify = new Option<bool>("--skip-verify") { Description = "Skip the quality gate which otherwise runs before anything is built" };
|
||||
var command = new Command("build", "Build MindWork AI Studio") { offline, skipVerify };
|
||||
command.SetAction((parseResult, _) => new UpdateMetadataCommands().Build(parseResult.GetValue(offline), parseResult.GetValue(skipVerify)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task Build(bool offline = false, bool skipVerify = false)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
throw new CommandExitedException(1);
|
||||
@@ -863,7 +929,16 @@ public sealed partial class UpdateMetadataCommands
|
||||
return shortVersion;
|
||||
}
|
||||
|
||||
[Command("update-project-hash", Description = "Update the project commit hash")]
|
||||
/// <summary>
|
||||
/// Defines the command "update-project-hash".
|
||||
/// </summary>
|
||||
public static Command CreateUpdateProjectHashCommand()
|
||||
{
|
||||
var command = new Command("update-project-hash", "Update the project commit hash");
|
||||
command.SetAction((_, _) => new UpdateMetadataCommands().UpdateProjectCommitHash());
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task UpdateProjectCommitHash()
|
||||
{
|
||||
const int COMMIT_HASH_INDEX = 8;
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
using SharedTools;
|
||||
|
||||
namespace Build.Commands;
|
||||
@@ -14,7 +10,16 @@ public sealed class UpdateWebAssetsCommand
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> SKIPPED_EXTENSIONS = new(StringComparer.OrdinalIgnoreCase) { ".br", ".gz", ".map" };
|
||||
|
||||
[Command("update-web", Description = "Update web assets")]
|
||||
/// <summary>
|
||||
/// Defines the command "update-web".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var command = new Command("update-web", "Update web assets");
|
||||
command.SetAction(_ => new UpdateWebAssetsCommand().UpdateWebAssets());
|
||||
return command;
|
||||
}
|
||||
|
||||
public void UpdateWebAssets()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
|
||||
@@ -2,9 +2,6 @@ using Build.Tools;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace Build.Commands;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +19,16 @@ public sealed class VerifyCommand
|
||||
/// </summary>
|
||||
private const string SIDECAR_PREFIX = "mindworkAIStudioServer-";
|
||||
|
||||
[Command("verify", Description = "Run the quality gate: .NET tests, the trim smoke test, Rust tests, Clippy, and the model sources")]
|
||||
/// <summary>
|
||||
/// Defines the command "verify".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var command = new Command("verify", "Run the quality gate: .NET tests, the trim smoke test, Rust tests, Clippy, and the model sources");
|
||||
command.SetAction((_, _) => new VerifyCommand().Verify());
|
||||
return command;
|
||||
}
|
||||
|
||||
public async Task<int> Verify()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace Build.Commands;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,9 +38,18 @@ public sealed partial class VerifyModelsCommand
|
||||
/// </remarks>
|
||||
private const string DAY_MARKER = "new DateOnly(";
|
||||
|
||||
[Command("verify-models", Description = "Report how long ago the pages behind the model rules were read")]
|
||||
public int VerifyModels(
|
||||
[Option("months", Description = "How long a page may go unread before it is reported")] int months = DEFAULT_MONTHS)
|
||||
/// <summary>
|
||||
/// Defines the command "verify-models".
|
||||
/// </summary>
|
||||
public static Command CreateCommand()
|
||||
{
|
||||
var months = new Option<int>("--months") { Description = "How long a page may go unread before it is reported", DefaultValueFactory = _ => DEFAULT_MONTHS };
|
||||
var command = new Command("verify-models", "Report how long ago the pages behind the model rules were read") { months };
|
||||
command.SetAction(parseResult => new VerifyModelsCommand().VerifyModels(parseResult.GetValue(months)));
|
||||
return command;
|
||||
}
|
||||
|
||||
public int VerifyModels(int months = DEFAULT_MONTHS)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return 1;
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
global using System.Text;
|
||||
|
||||
global using Cocona;
|
||||
global using System.CommandLine;
|
||||
|
||||
global using Environment = Build.Tools.Environment;
|
||||
+45
-11
@@ -1,12 +1,46 @@
|
||||
using Build.Commands;
|
||||
using System.CommandLine.Help;
|
||||
|
||||
var builder = CoconaApp.CreateBuilder();
|
||||
var app = builder.Build();
|
||||
app.AddCommands<CheckRidsCommand>();
|
||||
app.AddCommands<UpdateMetadataCommands>();
|
||||
app.AddCommands<UpdateWebAssetsCommand>();
|
||||
app.AddCommands<CollectI18NKeysCommand>();
|
||||
app.AddCommands<AssistantPluginHashCommand>();
|
||||
app.AddCommands<VerifyModelsCommand>();
|
||||
app.AddCommands<VerifyCommand>();
|
||||
app.Run();
|
||||
using Build.Commands;
|
||||
using Build.Tools;
|
||||
|
||||
//
|
||||
// A RootCommand would take the name of the executable, which is "build", and System.CommandLine refuses
|
||||
// a root with the same name as one of its commands. Hence, the root is a command of its own, which
|
||||
// brings only the help option of a RootCommand along:
|
||||
//
|
||||
var rootCommand = new Command("build-script", "Build, check, and release MindWork AI Studio. Run the commands from app/Build with 'dotnet run <command>'.")
|
||||
{
|
||||
new HelpOption(),
|
||||
CheckRidsCommand.CreateCommand(),
|
||||
UpdateMetadataCommands.CreateReleaseCommand(),
|
||||
UpdateMetadataCommands.CreateRebuildReleaseCommand(),
|
||||
UpdateMetadataCommands.CreateUpdateMetainfoCommand(),
|
||||
UpdateMetadataCommands.CreateUpdateVersionsCommand(),
|
||||
UpdateMetadataCommands.CreatePrepareCommand(),
|
||||
UpdateMetadataCommands.CreateBuildCommand(),
|
||||
UpdateMetadataCommands.CreateUpdateProjectHashCommand(),
|
||||
UpdateWebAssetsCommand.CreateCommand(),
|
||||
CollectI18NKeysCommand.CreateCommand(),
|
||||
AssistantPluginHashCommand.CreateCommand(),
|
||||
VerifyModelsCommand.CreateCommand(),
|
||||
VerifyCommand.CreateCommand()
|
||||
};
|
||||
|
||||
//
|
||||
// A command which fails deep inside, e.g., the build within a release, ends the whole script through
|
||||
// a CommandExitedException, once it has said why. System.CommandLine would print that exception with
|
||||
// its stack trace, so we handle the exceptions ourselves and print only those nobody expected:
|
||||
//
|
||||
try
|
||||
{
|
||||
return await rootCommand.Parse(args).InvokeAsync(new InvocationConfiguration { EnableDefaultExceptionHandler = false });
|
||||
}
|
||||
catch (CommandExitedException exception)
|
||||
{
|
||||
return exception.ExitCode;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine($"Unhandled exception: {exception}");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Build.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Ends the build script with an exit code, after the command has said what went wrong.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A release runs several commands one after another, and a failure deep inside one of them has to end
|
||||
/// the whole release, so that nothing is built on top of half-prepared metadata or broken artifacts.
|
||||
/// Program.cs turns the exception into the exit code and prints nothing more: the command already
|
||||
/// explained the failure, and a stack trace would only bury that explanation.
|
||||
/// </remarks>
|
||||
/// <param name="exitCode">The exit code of the build script, where anything but zero is a failure.</param>
|
||||
public sealed class CommandExitedException(int exitCode) : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The exit code the build script ends with.
|
||||
/// </summary>
|
||||
public int ExitCode { get; } = exitCode;
|
||||
}
|
||||
Reference in new issue
Block a user