123 lines
4.7 KiB
C#
123 lines
4.7 KiB
C#
using System.Diagnostics;
|
|
using System.Text.Json;
|
|
using Sdt.Config;
|
|
|
|
namespace Sdt.Core;
|
|
|
|
public sealed class ToolProbeService : IToolProbe
|
|
{
|
|
public async Task<ProbeResult> ProbeAsync(
|
|
string tool,
|
|
string projectRoot,
|
|
DevToolConfig? config = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var direct = await ProbeDirectAsync(tool, config, cancellationToken).ConfigureAwait(false);
|
|
if (direct.IsAvailable)
|
|
return direct;
|
|
|
|
var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "diag.py");
|
|
if (scriptPath is null)
|
|
return direct;
|
|
|
|
if (!(await ProbeDirectAsync("python", config, cancellationToken).ConfigureAwait(false)).IsAvailable)
|
|
return direct;
|
|
|
|
try
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = PythonResolver.ResolveExecutable(),
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
WorkingDirectory = projectRoot,
|
|
};
|
|
psi.ArgumentList.Add(scriptPath);
|
|
psi.ArgumentList.Add("probe");
|
|
psi.ArgumentList.Add("--tool");
|
|
psi.ArgumentList.Add(tool);
|
|
psi.ArgumentList.Add("--json");
|
|
|
|
using var process = new Process { StartInfo = psi };
|
|
process.Start();
|
|
var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
|
if (process.ExitCode != 0)
|
|
return new ProbeResult(tool, false, Details: stderr.Trim());
|
|
|
|
var parsed = JsonSerializer.Deserialize<DiagProbeJson>(stdout);
|
|
if (parsed is null)
|
|
return new ProbeResult(tool, false, Details: "diag.py returned invalid JSON");
|
|
|
|
return new ProbeResult(parsed.Tool ?? tool, parsed.Available, parsed.Version, parsed.Details);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ProbeResult(tool, false, Details: ex.Message);
|
|
}
|
|
}
|
|
|
|
private static async Task<ProbeResult> ProbeDirectAsync(string tool, DevToolConfig? config, CancellationToken cancellationToken)
|
|
{
|
|
var command = tool.ToLowerInvariant() switch
|
|
{
|
|
"python" => PythonResolver.ResolveExecutable(),
|
|
"dotnet" => "dotnet",
|
|
"node" => "node",
|
|
"npm" => "npm",
|
|
"cargo" => "cargo",
|
|
"tauri" => "tauri",
|
|
"git" => "git",
|
|
"docker" => "docker",
|
|
_ => tool,
|
|
};
|
|
var resolution = CommandResolver.ResolveWithTrace(command, config, tool);
|
|
command = resolution.Resolved;
|
|
|
|
var versionArg = command is "python" ? "--version" : "--version";
|
|
try
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = command,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
};
|
|
psi.ArgumentList.Add(versionArg);
|
|
|
|
using var process = new Process { StartInfo = psi };
|
|
process.Start();
|
|
var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
if (process.ExitCode != 0)
|
|
{
|
|
var failDetails = string.IsNullOrWhiteSpace(stderr) ? stdout.Trim() : stderr.Trim();
|
|
var trace = $"{resolution.Source}: {resolution.Resolved}";
|
|
return new ProbeResult(tool, false, Details: string.IsNullOrWhiteSpace(failDetails) ? trace : $"{trace} | {failDetails}");
|
|
}
|
|
|
|
var version = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim();
|
|
return new ProbeResult(tool, true, Version: version, Details: $"{resolution.Source}: {resolution.Resolved}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ProbeResult(tool, false, Details: $"{resolution.Source}: {resolution.Resolved} | {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private sealed class DiagProbeJson
|
|
{
|
|
public string? Tool { get; init; }
|
|
public bool Available { get; init; }
|
|
public string? Version { get; init; }
|
|
public string? Details { get; init; }
|
|
}
|
|
}
|