From 824e1011ca7571775fa9888b1d66b77a9e1adba3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:08:41 +0000 Subject: [PATCH] Revert CliFx removal; fix macOS CI hang by setting WorkingDirectory on Exec The macOS CI timeout was caused by `dotnet run` detecting the `.csproj` file in the working directory and running the GUI project instead of the `.csx` script. The GUI app would launch, try to initialize Metal/Skia graphics (no display on CI), and hang until the 10-minute job timeout. Fix: Set `WorkingDirectory="$([System.IO.Path]::GetTempPath())"` on the `` element so `dotnet run` runs from a clean directory with no project file, correctly treating the `.csx` as a file-based app. Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Agent-Logs-Url: https://github.com/Tyrrrz/DiscordChatExporter/sessions/4e5ffe4c-7b2e-44b9-9477-5ea3d50e0ec0 --- .../DiscordChatExporter.Gui.csproj | 3 +- .../Publish-MacOSBundle.csx | 207 ++++++++++-------- 2 files changed, 118 insertions(+), 92 deletions(-) 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); }