#:package CliFx@2.3.6 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 { [CommandParameter(0, Name = "publish-dir", Description = "Path to the publish output directory.")] public required string PublishDirPath { get; init; } [CommandParameter(1, Name = "icons-file", Description = "Path to the .icns icons file.")] public required string IconsFilePath { get; init; } [CommandParameter(2, Name = "full-version", Description = "Full version string (e.g. '1.2.3.4').")] public required string FullVersion { get; init; } [CommandParameter(3, Name = "short-version", Description = "Short version string (e.g. '1.2.3').")] public required string ShortVersion { get; init; } public ValueTask ExecuteAsync(IConsole console) { // Setup paths var publishDirPath = Path.GetFullPath(PublishDirPath); var tempDirPath = Path.GetFullPath( Path.Combine(publishDirPath, "../publish-macos-app-temp") ); var bundleName = "DiscordChatExporter.app"; var bundleDirPath = Path.Combine(tempDirPath, bundleName); var contentsDirPath = Path.Combine(bundleDirPath, "Contents"); var macosDirPath = Path.Combine(contentsDirPath, "MacOS"); var resourcesDirPath = Path.Combine(contentsDirPath, "Resources"); try { // Initialize the bundle's directory structure Directory.CreateDirectory(bundleDirPath); Directory.CreateDirectory(contentsDirPath); Directory.CreateDirectory(macosDirPath); Directory.CreateDirectory(resourcesDirPath); // Copy icons into the .app's Resources folder File.Copy( IconsFilePath, Path.Combine(resourcesDirPath, "AppIcon.icns"), overwrite: true ); // Generate the Info.plist metadata file with the app information var plistContent = $""" CFBundleDisplayName DiscordChatExporter CFBundleName DiscordChatExporter CFBundleExecutable DiscordChatExporter NSHumanReadableCopyright © Oleksii Holub CFBundleIdentifier me.Tyrrrz.DiscordChatExporter CFBundleSpokenName Discord Chat Exporter CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleVersion {FullVersion} CFBundleShortVersionString {ShortVersion} NSHighResolutionCapable CFBundlePackageType APPL """; File.WriteAllText(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, recursive: true); // Move all files from the publish directory into the MacOS directory foreach (var entry in Directory.GetFileSystemEntries(publishDirPath)) { var destination = Path.Combine(macosDirPath, Path.GetFileName(entry)); if (Directory.Exists(entry)) Directory.Move(entry, destination); else File.Move(entry, destination); } // 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, recursive: true); } return default; } }