diff --git a/src/Fallout.Cli/Program.Trigger.cs b/src/Fallout.Cli/Commands/TriggerCommand.cs
similarity index 67%
rename from src/Fallout.Cli/Program.Trigger.cs
rename to src/Fallout.Cli/Commands/TriggerCommand.cs
index 1497e5626..e0d5deb47 100644
--- a/src/Fallout.Cli/Program.Trigger.cs
+++ b/src/Fallout.Cli/Commands/TriggerCommand.cs
@@ -1,16 +1,19 @@
-using System;
-using System.Linq;
using Fallout.Common;
using Fallout.Common.Git;
using Fallout.Common.IO;
using Fallout.Common.Tools.Git;
using Fallout.Common.Utilities;
-namespace Fallout.Cli;
+namespace Fallout.Cli.Commands;
-partial class Program
+///
+/// fallout :trigger: pushes an empty commit with the given message to trigger a remote build.
+///
+public sealed class TriggerCommand : IFalloutCommand
{
- public static int Trigger(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ public string Name => "trigger";
+
+ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
{
var repository = GitRepository.FromLocalDirectory(rootDirectory.NotNull()).NotNull("No Git repository");
Assert.NotNull(repository.Branch, "Git repository must not be detached");
diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs
index 0ce3d7e55..25c5839e1 100644
--- a/src/Fallout.Cli/Program.cs
+++ b/src/Fallout.Cli/Program.cs
@@ -53,6 +53,7 @@ private static void RegisterCommands(IServiceCollection services)
{
// Real command types — issue #392 converts one legacy handler per PR.
services.AddSingleton();
+ services.AddSingleton();
// Legacy handlers still living on Program, adapted until they are extracted into command
// types. Each conversion deletes one line here plus its Program.X.cs partial.
@@ -64,7 +65,6 @@ private static void RegisterCommands(IServiceCollection services)
services.AddSingleton(new DelegateCommand("complete", Complete));
services.AddSingleton(new DelegateCommand("get-configuration", GetConfiguration));
services.AddSingleton(new DelegateCommand("secrets", Secrets));
- services.AddSingleton(new DelegateCommand("trigger", Trigger));
services.AddSingleton(new DelegateCommand("GetNextDirectory", GetNextDirectory));
services.AddSingleton(new DelegateCommand("PopDirectory", PopDirectory));
services.AddSingleton(new DelegateCommand("PushWithCurrentRootDirectory", PushWithCurrentRootDirectory));
diff --git a/tests/Fallout.Cli.Tests/Commands/TriggerCommandTests.cs b/tests/Fallout.Cli.Tests/Commands/TriggerCommandTests.cs
new file mode 100644
index 000000000..2426f6e8b
--- /dev/null
+++ b/tests/Fallout.Cli.Tests/Commands/TriggerCommandTests.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+using Fallout.Cli.Commands;
+using Fallout.Common.IO;
+using FluentAssertions;
+using Xunit;
+
+namespace Fallout.Cli.Tests.Commands;
+
+public class TriggerCommandTests
+{
+ [Fact]
+ public void Name_IsTrigger()
+ => new TriggerCommand().Name.Should().Be("trigger");
+
+ [Fact]
+ public void Execute_OutsideGitRepository_Throws()
+ {
+ var dir = (AbsolutePath)Path.Combine(Path.GetTempPath(), "fallout-trigger-" + Guid.NewGuid().ToString("N"));
+ dir.CreateDirectory();
+ try
+ {
+ var action = () => new TriggerCommand().Execute(["a message"], dir, buildScript: null);
+
+ // No resolvable Git repository at a throwaway temp dir → the command fails rather than
+ // pushing anything.
+ action.Should().Throw();
+ }
+ finally
+ {
+ Directory.Delete(dir, recursive: true);
+ }
+ }
+}