diff --git a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
index 375e67d8..dd8f0101 100644
--- a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
+++ b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
@@ -56,7 +56,8 @@
diff --git a/DiscordChatExporter.Gui/Publish-MacOSBundle.csx b/DiscordChatExporter.Gui/Publish-MacOSBundle.csx
index 2f339347..ab6fc635 100644
--- a/DiscordChatExporter.Gui/Publish-MacOSBundle.csx
+++ b/DiscordChatExporter.Gui/Publish-MacOSBundle.csx
@@ -1,103 +1,128 @@
#!/usr/bin/env -S dotnet run --
+#:package CliFx
-// Set up arguments
-string GetArg(string name)
+using CliFx;
+using CliFx.Attributes;
+using CliFx.Infrastructure;
+
+return await new CliApplicationBuilder()
+ .AddCommand()
+ .Build()
+ .RunAsync(args);
+
+[Command(Description = "Publishes the GUI app as a macOS .app bundle.")]
+public class PublishMacOSBundleCommand : ICommand
{
- var idx = Array.IndexOf(args, name);
- if (idx < 0 || idx + 1 >= args.Length)
- throw new InvalidOperationException($"Missing required option: {name}");
- return args[idx + 1];
-}
+ private const string BundleName = "DiscordChatExporter.app";
+ private const string AppName = "DiscordChatExporter";
+ private const string AppCopyright = "© Oleksii Holub";
+ private const string AppIdentifier = "me.Tyrrrz.DiscordChatExporter";
+ private const string AppSpokenName = "Discord Chat Exporter";
+ private const string AppIconName = "AppIcon";
-var publishDirPathArg = GetArg("--publish-dir");
-var iconsFilePathArg = GetArg("--icons-file");
-var fullVersionArg = GetArg("--full-version");
-var shortVersionArg = GetArg("--short-version");
+ [CommandOption("publish-dir", Description = "Path to the publish output directory.")]
+ public required string PublishDirPath { get; init; }
-const string BundleName = "DiscordChatExporter.app";
-const string AppName = "DiscordChatExporter";
-const string AppCopyright = "© Oleksii Holub";
-const string AppIdentifier = "me.Tyrrrz.DiscordChatExporter";
-const string AppSpokenName = "Discord Chat Exporter";
-const string AppIconName = "AppIcon";
+ [CommandOption("icons-file", Description = "Path to the .icns icons file.")]
+ public required string IconsFilePath { get; init; }
-// Set up paths
-var publishDirPath = Path.GetFullPath(publishDirPathArg);
-var tempDirPath = Path.GetFullPath(Path.Combine(publishDirPath, "../publish-macos-app-temp"));
+ [CommandOption("full-version", Description = "Full version string (e.g. '1.2.3.4').")]
+ public required string FullVersion { get; init; }
-// Ensure the temporary directory is clean before use in case a previous run crashed
-if (Directory.Exists(tempDirPath))
- Directory.Delete(tempDirPath, true);
+ [CommandOption("short-version", Description = "Short version string (e.g. '1.2.3').")]
+ public required string ShortVersion { get; init; }
-var bundleDirPath = Path.Combine(tempDirPath, BundleName);
-var contentsDirPath = Path.Combine(bundleDirPath, "Contents");
-
-try
-{
- // Copy icons into the .app's Resources folder
- Directory.CreateDirectory(Path.Combine(contentsDirPath, "Resources"));
- File.Copy(iconsFilePathArg, Path.Combine(contentsDirPath, "Resources", "AppIcon.icns"), true);
-
- // Generate the Info.plist metadata file with the app information
- // lang=xml
- var plistContent = $"""
-
-
-
-
- CFBundleDisplayName
- {AppName}
- CFBundleName
- {AppName}
- CFBundleExecutable
- {AppName}
- NSHumanReadableCopyright
- {AppCopyright}
- CFBundleIdentifier
- {AppIdentifier}
- CFBundleSpokenName
- {AppSpokenName}
- CFBundleIconFile
- {AppIconName}
- CFBundleIconName
- {AppIconName}
- CFBundleVersion
- {fullVersionArg}
- CFBundleShortVersionString
- {shortVersionArg}
- NSHighResolutionCapable
-
- CFBundlePackageType
- APPL
-
-
- """;
-
- await File.WriteAllTextAsync(Path.Combine(contentsDirPath, "Info.plist"), plistContent);
-
- // Delete the previous bundle if it exists
- var existingBundlePath = Path.Combine(publishDirPath, BundleName);
- if (Directory.Exists(existingBundlePath))
- Directory.Delete(existingBundlePath, true);
-
- // Move all files from the publish directory into the MacOS directory
- Directory.CreateDirectory(Path.Combine(contentsDirPath, "MacOS"));
- foreach (var entryPath in Directory.GetFileSystemEntries(publishDirPath))
+ public async ValueTask ExecuteAsync(IConsole console)
{
- var destinationPath = Path.Combine(contentsDirPath, "MacOS", Path.GetFileName(entryPath));
+ // Set up paths
+ var publishDirPath = Path.GetFullPath(PublishDirPath);
+ var tempDirPath = Path.GetFullPath(
+ Path.Combine(publishDirPath, "../publish-macos-app-temp")
+ );
- if (Directory.Exists(entryPath))
- Directory.Move(entryPath, destinationPath);
- else
- File.Move(entryPath, destinationPath);
+ // Ensure the temporary directory is clean before use in case a previous run crashed
+ if (Directory.Exists(tempDirPath))
+ Directory.Delete(tempDirPath, true);
+
+ var bundleDirPath = Path.Combine(tempDirPath, BundleName);
+ var contentsDirPath = Path.Combine(bundleDirPath, "Contents");
+
+ try
+ {
+ // Copy icons into the .app's Resources folder
+ Directory.CreateDirectory(Path.Combine(contentsDirPath, "Resources"));
+ File.Copy(
+ IconsFilePath,
+ Path.Combine(contentsDirPath, "Resources", "AppIcon.icns"),
+ true
+ );
+
+ // Generate the Info.plist metadata file with the app information
+ // lang=xml
+ var plistContent = $"""
+
+
+
+
+ CFBundleDisplayName
+ {AppName}
+ CFBundleName
+ {AppName}
+ CFBundleExecutable
+ {AppName}
+ NSHumanReadableCopyright
+ {AppCopyright}
+ CFBundleIdentifier
+ {AppIdentifier}
+ CFBundleSpokenName
+ {AppSpokenName}
+ CFBundleIconFile
+ {AppIconName}
+ CFBundleIconName
+ {AppIconName}
+ CFBundleVersion
+ {FullVersion}
+ CFBundleShortVersionString
+ {ShortVersion}
+ NSHighResolutionCapable
+
+ CFBundlePackageType
+ APPL
+
+
+ """;
+
+ await File.WriteAllTextAsync(Path.Combine(contentsDirPath, "Info.plist"), plistContent);
+
+ // Delete the previous bundle if it exists
+ var existingBundlePath = Path.Combine(publishDirPath, BundleName);
+ if (Directory.Exists(existingBundlePath))
+ Directory.Delete(existingBundlePath, true);
+
+ // Move all files from the publish directory into the MacOS directory
+ Directory.CreateDirectory(Path.Combine(contentsDirPath, "MacOS"));
+ foreach (var entryPath in Directory.GetFileSystemEntries(publishDirPath))
+ {
+ var destinationPath = Path.Combine(
+ contentsDirPath,
+ "MacOS",
+ Path.GetFileName(entryPath)
+ );
+
+ if (Directory.Exists(entryPath))
+ Directory.Move(entryPath, destinationPath);
+ else
+ File.Move(entryPath, destinationPath);
+ }
+
+ // Move the final bundle into the publish directory for upload
+ Directory.Move(bundleDirPath, Path.Combine(publishDirPath, BundleName));
+ }
+ finally
+ {
+ // Clean up the temporary directory
+ if (Directory.Exists(tempDirPath))
+ Directory.Delete(tempDirPath, true);
+ }
}
-
- // Move the final bundle into the publish directory for upload
- Directory.Move(bundleDirPath, Path.Combine(publishDirPath, BundleName));
-}
-finally
-{
- // Clean up the temporary directory
- if (Directory.Exists(tempDirPath))
- Directory.Delete(tempDirPath, true);
}