Forge
csharp3407750f
1using System.Diagnostics;
2using System.Text;
3using AgentForge.Plugin.Ci.Ir;
4
5namespace AgentForge.Plugin.Ci.Execution;
6
7internal static class ForgeCiTaskRunner
8{
9 internal sealed record ForgeCiTaskRunResult(bool Success, string Log);
10
11 internal static async Task<ForgeCiTaskRunResult> ExecuteAsync(
12 ForgeCiPlanStep step,
13 string workspacePath,
14 CancellationToken cancellationToken)
15 {
16 if (string.Equals(Environment.GetEnvironmentVariable("FORGE_CI_DRY_RUN"), "1", StringComparison.Ordinal))
17 {
18 return new ForgeCiTaskRunResult(
19 true,
20 $"[dry-run] {step.TaskId} ({step.DisplayName}) skipped.");
21 }
22
23 return step.TaskId switch
24 {
25 "script" => await RunScriptAsync(step, workspacePath, cancellationToken).ConfigureAwait(false),
26 "dotnet.restore" => await RunDotnetAsync("restore", workspacePath, cancellationToken).ConfigureAwait(false),
27 "dotnet.build" => await RunDotnetAsync(BuildArgs(step), workspacePath, cancellationToken).ConfigureAwait(false),
28 "dotnet.test" => await RunDotnetAsync("test", workspacePath, cancellationToken).ConfigureAwait(false),
29 _ => new ForgeCiTaskRunResult(false, $"Unknown task '{step.TaskId}'."),
30 };
31 }
32
33 private static string BuildArgs(ForgeCiPlanStep step)
34 {
35 if (step.Inputs.TryGetValue("configuration", out var configuration)
36 && !string.IsNullOrWhiteSpace(configuration))
37 {
38 var resolved = configuration.Replace("$(Configuration)", "Release", StringComparison.Ordinal);
39 return $"build -c {resolved}";
40 }
41
42 return "build";
43 }
44
45 private static async Task<ForgeCiTaskRunResult> RunScriptAsync(
46 ForgeCiPlanStep step,
47 string workspacePath,
48 CancellationToken cancellationToken)
49 {
50 if (!step.Inputs.TryGetValue("command", out var command) || string.IsNullOrWhiteSpace(command))
51 return new ForgeCiTaskRunResult(false, "script task requires inputs.command.");
52
53 Directory.CreateDirectory(workspacePath);
54 return await RunProcessAsync(
55 OperatingSystem.IsWindows() ? "cmd.exe" : "/bin/sh",
56 OperatingSystem.IsWindows() ? $"/c {command}" : $"-c \"{command}\"",
57 workspacePath,
58 cancellationToken).ConfigureAwait(false);
59 }
60
61 private static Task<ForgeCiTaskRunResult> RunDotnetAsync(
62 string arguments,
63 string workspacePath,
64 CancellationToken cancellationToken)
65 {
66 Directory.CreateDirectory(workspacePath);
67 return RunProcessAsync("dotnet", arguments, workspacePath, cancellationToken);
68 }
69
70 private static async Task<ForgeCiTaskRunResult> RunProcessAsync(
71 string fileName,
72 string arguments,
73 string workingDirectory,
74 CancellationToken cancellationToken)
75 {
76 var psi = new ProcessStartInfo
77 {
78 FileName = fileName,
79 Arguments = arguments,
80 WorkingDirectory = workingDirectory,
81 RedirectStandardOutput = true,
82 RedirectStandardError = true,
83 UseShellExecute = false,
84 CreateNoWindow = true,
85 };
86
87 using var process = new Process { StartInfo = psi };
88 var output = new StringBuilder();
89 process.OutputDataReceived += (_, e) =>
90 {
91 if (e.Data is not null)
92 output.AppendLine(e.Data);
93 };
94 process.ErrorDataReceived += (_, e) =>
95 {
96 if (e.Data is not null)
97 output.AppendLine(e.Data);
98 };
99
100 try
101 {
102 if (!process.Start())
103 return new ForgeCiTaskRunResult(false, $"Failed to start {fileName}.");
104
105 process.BeginOutputReadLine();
106 process.BeginErrorReadLine();
107 await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
108
109 var log = output.Length == 0 ? $"(exit {process.ExitCode})" : output.ToString();
110 return new ForgeCiTaskRunResult(process.ExitCode == 0, log);
111 }
112 catch (Exception ex)
113 {
114 return new ForgeCiTaskRunResult(false, ex.Message);
115 }
116 }
117}
118
View only · write via MCP/CIDE