-
-
Notifications
You must be signed in to change notification settings - Fork 230
Expand file tree
/
Copy pathdev.cs
More file actions
executable file
·229 lines (191 loc) · 7.51 KB
/
dev.cs
File metadata and controls
executable file
·229 lines (191 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env dotnet
#:package Cocona@2.2.0
#nullable enable
using System.Diagnostics;
using System.Runtime.InteropServices;
using Cocona;
// Simple dev helper CLI implemented as a .NET file-based app using Cocona.
// Usage examples (run from repo root):
// ./dev.cs --help
// ./dev.cs cleanslate
// ./dev.cs cleanslate Sentry-CI-Build-macOS.slnf
// ./dev.cs subup
// ./dev.cs wrest
// ./dev.cs nrest
// ./dev.cs aiup
// ./dev.cs cleanslate --dry-run
//
// This is intended to be run from the repo root so that git/dotnet
// commands operate on this repository.
var app = CoconaApp.Create();
app.AddCommands<DevCommands>();
app.Run();
// ----------------- Options & Commands -----------------
public class GlobalOptions : ICommandParameterSet
{
[Option("dry-run", new[] { 'n' }, Description = "Print commands instead of executing them.")]
public bool DryRun { get; set; }
}
public class DevCommands
{
[Command("cleanslate", Description = "Clean repo, update submodules, and restore the solution.")]
public async Task<int> CleanSlateAsync(
[Argument("solution", Description = "Solution file to restore. Defaults to platform-specific CI solution if omitted.")] string? solution = null,
GlobalOptions options = default!)
{
solution ??= DevConfig.DefaultSolution;
Console.WriteLine($"[dev] cleanslate for solution: {solution}");
var steps = new (string Description, string FileName, string Arguments)[]
{
("git clean", "git", "clean -dfx"),
("git submodule update", "git", "submodule update --init --recursive"),
("dotnet restore", "dotnet", $"restore \"{solution}\""),
("npx @sentry/dotagents install", "npx", "@sentry/dotagents install")
};
foreach (var (description, fileName, arguments) in steps)
{
int code = await RunStepAsync(description, fileName, arguments, options.DryRun);
if (code != 0)
{
Console.Error.WriteLine($"[dev] Step '{description}' failed with exit code {code}.");
return code;
}
}
return 0;
}
[Command("subup", Description = "Update git submodules recursively.")]
public Task<int> SubmoduleUpdateAsync(GlobalOptions options = default!)
{
Console.WriteLine("[dev] Updating git submodules (recursive)");
return RunStepAsync("git submodule update", "git", "submodule update --init --recursive", options.DryRun);
}
[Command("wrest", Description = "Run 'dotnet workload restore' (with sudo on Unix if available).")]
public async Task<int> WorkloadRestoreAsync(GlobalOptions options = default!)
{
Console.WriteLine("[dev] Restoring dotnet workloads");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// No sudo on Windows
return await RunStepAsync("dotnet workload restore", "dotnet", "workload restore", options.DryRun);
}
bool sudoAvailable = await IsCommandAvailableAsync("sudo");
if (sudoAvailable)
{
return await RunStepAsync("sudo dotnet workload restore", "sudo", "dotnet workload restore", options.DryRun);
}
Console.WriteLine("[dev] 'sudo' not found; running 'dotnet workload restore' without sudo.");
return await RunStepAsync("dotnet workload restore", "dotnet", "workload restore", options.DryRun);
}
[Command("aiup", Description = "Install/update AI agent files via @sentry/dotagents.")]
public Task<int> AiUpdateAsync(GlobalOptions options = default!)
{
Console.WriteLine("[dev] Installing/updating AI agent files");
return RunStepAsync("npx @sentry/dotagents install", "npx", "@sentry/dotagents install", options.DryRun);
}
[Command("nrest", Description = "Restore the default CI solution.")]
public Task<int> SolutionRestoreAsync(
[Argument("solution", Description = "Solution file to restore. Defaults to platform-specific CI solution if omitted.")] string? solution = null,
GlobalOptions options = default!)
{
solution ??= DevConfig.DefaultSolution;
Console.WriteLine($"[dev] Restoring solution: {solution}");
return RunStepAsync("dotnet restore", "dotnet", $"restore \"{solution}\"", options.DryRun);
}
private static async Task<int> RunStepAsync(string description, string fileName, string arguments, bool dryRun)
{
Console.WriteLine($"==> {description}: {fileName} {arguments}");
return await RunProcessAsync(fileName, arguments, dryRun);
}
private static async Task<int> RunProcessAsync(string fileName, string arguments, bool dryRun)
{
if (dryRun)
{
Console.WriteLine($"[DRY RUN] {fileName} {arguments}");
return 0;
}
// On Windows, .cmd/.bat files (e.g. npx.cmd) can't be launched via CreateProcess directly;
// route through cmd.exe so the shell resolves them correctly.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
arguments = $"/c {fileName} {arguments}";
fileName = "cmd.exe";
}
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = false,
};
using var process = new Process { StartInfo = startInfo };
try
{
if (!process.Start())
{
Console.Error.WriteLine($"[dev] Failed to start process: {fileName}");
return 1;
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"[dev] Exception starting process '{fileName}': {ex.Message}");
return 1;
}
await process.WaitForExitAsync();
return process.ExitCode;
}
private static async Task<bool> IsCommandAvailableAsync(string command)
{
// Use 'where.exe' on Windows, 'which' on Unix
var finder = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "where.exe" : "which";
var startInfo = new ProcessStartInfo
{
FileName = finder,
Arguments = command,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
using var process = new Process { StartInfo = startInfo };
try
{
if (!process.Start())
{
return false;
}
}
catch
{
return false;
}
// Consume streams to avoid pipe buffer deadlock, then wait for exit
await Task.WhenAll(
process.StandardOutput.ReadToEndAsync(),
process.StandardError.ReadToEndAsync(),
process.WaitForExitAsync());
return process.ExitCode == 0;
}
}
// Central configuration/constants for the dev script.
public static class DevConfig
{
public static string DefaultSolution
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "Sentry-CI-Build-Windows.slnf";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "Sentry-CI-Build-Linux.slnf";
}
// Fallback: macOS solution
return "Sentry-CI-Build-macOS.slnf";
}
}
}