using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using XFESystem.Documents; if (args.Length is < 1 or > 2 || args.Length == 2 && args[1] != "--code-reference") throw new ArgumentException("Specify a fresh reference fixture directory and optional --code-reference."); var output = Path.GetFullPath(args[0]); if (Directory.Exists(output)) throw new IOException("The reference directory must be new."); Directory.CreateDirectory(output); var markdown = WebsiteAssets.ReadText("Fixtures/WebsiteParity.md"); var builder = new DocumentPreviewBuilder(new()); await File.WriteAllTextAsync(Path.Combine(output, "Fixture.md"), markdown); await File.WriteAllTextAsync(Path.Combine(output, "Dark.html"), builder.MarkdownWeb(markdown, "Website parity", dark: true).Html); await File.WriteAllTextAsync(Path.Combine(output, "Light.html"), builder.MarkdownWeb(markdown, "Website parity", dark: false).Html); await File.WriteAllTextAsync(Path.Combine(output, "SourceManifest.json"), WebsiteAssets.ReadText("Exact/SourceManifest.json")); if (args.Length == 2) { string? root = AppContext.BaseDirectory; while (root is not null && !File.Exists(Path.Combine(root, "XFESystem.slnx"))) root = Path.GetDirectoryName(root); if (root is null) throw new InvalidOperationException("Cannot identify the production sample source."); var sourcePath = Path.Combine(root, "Source", "XFESystem.Browser", "BrowserResourceSource.cs"); var sourceBytes = await File.ReadAllBytesAsync(sourcePath); var sourceText = Encoding.UTF8.GetString(sourceBytes); var literal = Regex.Match(sourceText, @"else if\(!globalThis\.xfeMonacoFileMode\)xfeMonacoEditor\.setValue\(language==='csharp'\?'((?:\\.|[^'\\])*)':"); if (!literal.Success) throw new InvalidOperationException("The production C# sample shape changed; update the explicit reference extractor."); // This is a string decoder, not evaluation of either JavaScript or C#. var code = DecodeJavaScriptString(literal.Groups[1].Value); var codeSha = Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(code))); await File.WriteAllTextAsync(Path.Combine(output, "CSharpSample.cs"), code); await File.WriteAllTextAsync(Path.Combine(output, "CodeDark.html"), builder.CodeWeb(code, "MonacoSample.cs", dark: true).Html); await File.WriteAllTextAsync(Path.Combine(output, "CodeLight.html"), builder.CodeWeb(code, "MonacoSample.cs", dark: false).Html); await File.WriteAllTextAsync(Path.Combine(output, "CSharpSample.json"), JsonSerializer.Serialize(new { Kind = "ProductionMonacoDefaultCSharpSample", Component = "WebsiteStaticCSharpCodeBlock", SourcePath = sourcePath, SourceSha256 = Convert.ToHexStringLower(SHA256.HashData(sourceBytes)), CodeSha256 = codeSha, LineCount = code.Split('\n').Length, ExtractedFrom = "xfeMonacoSetLanguage('csharp') default string; source code is not executed" }, new JsonSerializerOptions { WriteIndented = true })); } var resources = new List(); foreach (var entry in WebsiteAssets.WebResources) { var path = Path.Combine(output, "Assets", entry.AssetName.Replace('/', Path.DirectorySeparatorChar)); Directory.CreateDirectory(Path.GetDirectoryName(path)!); using var stream = WebsiteAssets.Open(entry.AssetName); await using var file = File.Create(path); await stream.CopyToAsync(file); resources.Add(new { entry.UrlPath, entry.AssetName, entry.ContentType }); } await File.WriteAllTextAsync(Path.Combine(output, "Resources.json"), JsonSerializer.Serialize(resources, new JsonSerializerOptions { WriteIndented = true })); Console.WriteLine(output); static string DecodeJavaScriptString(string literal) { var decoded = new StringBuilder(); for (int index = 0; index < literal.Length; index++) { char current = literal[index]; if (current != '\\') { decoded.Append(current); continue; } if (++index == literal.Length) throw new FormatException("Incomplete sample string escape."); decoded.Append(literal[index] switch { 'n' => '\n', 'r' => '\r', 't' => '\t', 'b' => '\b', 'f' => '\f', 'v' => '\v', '\\' => '\\', '\'' => '\'', '"' => '"', _ => throw new FormatException("Unsupported sample escape; do not guess or execute it.") }); } return decoded.ToString(); }