using System.Diagnostics; using ArcaneLibs; using ReferenceClientProxyImplementation.Configuration; namespace ReferenceClientProxyImplementation.Patches.Implementations; public partial class FormatJsFilePatch(ProxyConfiguration config) : IPatch { public int GetOrder() => -100; public string GetName() => "Format JS file"; public bool Applies(string relativeName, byte[] content) => relativeName.EndsWith(".js") || relativeName.EndsWith(".css") || relativeName.EndsWith(".html"); public async Task Execute(string relativeName, byte[] content) { var cachePath = Path.Combine(config.TestClient.RevisionPath, "formatted", relativeName); if (File.Exists(cachePath)) { Console.WriteLine($"Using cached formatted file for {relativeName}"); return await File.ReadAllBytesAsync(cachePath); } // temporary: add some newlines var stringContent = System.Text.Encoding.UTF8.GetString(content); // stringContent = stringContent.Replace("function(){", "function() {\n"); content = System.Text.Encoding.UTF8.GetBytes(stringContent); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!); var tmpPath = $"{Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp"}/{Random.Shared.NextInt64()}_{Path.GetFileName(relativeName)}"; await File.WriteAllBytesAsync(tmpPath, content); var sw = Stopwatch.StartNew(); ProcessStartInfo psi; // Biome doesn't support HTML and struggles with upstream emitting Sass directives if (relativeName.EndsWith(".html") || relativeName.EndsWith(".css")) psi = new ProcessStartInfo(config.AssetCache.PrettierPath, $"-w --print-width 240 {tmpPath}") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; else psi = new ProcessStartInfo(config.AssetCache.PrettierPath, $"-w --print-width 240 {tmpPath}") { // psi = new ProcessStartInfo(config.AssetCache.BiomePath, $"format --write --line-width 240 --files-max-size=100000000 {tmpPath}") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(psi); if (process == null) { throw new InvalidOperationException("Failed to start the formatting process."); } // var stdout = await process.StandardOutput.ReadToEndAsync(); // var stderr = await process.StandardError.ReadToEndAsync(); // await process.WaitForExitAsync(); Dictionary stdoutLines = new(); Dictionary stderrLines = new(); process.OutputDataReceived += (sender, args) => { if (args.Data != null) { stdoutLines[(ulong)sw.ElapsedMilliseconds] = args.Data; Console.Write("O"); } }; process.ErrorDataReceived += (sender, args) => { if (args.Data != null) { stderrLines[(ulong)sw.ElapsedMilliseconds] = args.Data; Console.Write("E"); } }; process.BeginOutputReadLine(); process.BeginErrorReadLine(); await process.WaitForExitAsync(); Console.WriteLine($"Formatted {relativeName} in {sw.ElapsedMilliseconds}ms: {process.ExitCode}"); if (process.ExitCode != 0) { Console.WriteLine($"Failed to format {relativeName} in {sw.ElapsedMilliseconds}ms: {process.ExitCode}"); Console.WriteLine("Standard Output:\n" + string.Join("\n", stdoutLines.OrderBy(kv => kv.Key).Select(kv => $"[{kv.Key}ms] {kv.Value}"))); Console.WriteLine("Standard Error:\n" + string.Join("\n", stderrLines.OrderBy(kv => kv.Key).Select(kv => $"[{kv.Key}ms] {kv.Value}"))); throw new Exception($"Failed to exec({psi.FileName} {psi.Arguments}): {string.Join("\n", stderrLines.OrderBy(kv => kv.Key).Select(kv => kv.Value))}"); } var result = await File.ReadAllBytesAsync(tmpPath); File.Move(tmpPath, cachePath); return result; } }