diff --git a/implement/pine/Elm/ElmPackageSource.cs b/implement/pine/Elm/ElmPackageSource.cs new file mode 100644 index 00000000..c4c683b4 --- /dev/null +++ b/implement/pine/Elm/ElmPackageSource.cs @@ -0,0 +1,173 @@ +using Pine.Core; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Pine.Elm; + +/// +/// Provides functionality to load Elm packages by either retrieving a locally cached ZIP file +/// or downloading it from GitHub. The loaded package contents are returned as an in-memory +/// dictionary mapping path segments to file contents. If no valid local cache is found, +/// the package is fetched from GitHub and saved to the first cache directory for future use. +/// +/// Usage: +/// +/// var packageFiles = await ElmPackageSource.LoadElmPackageAsync("agu-z/elm-zip", "3.0.1"); +/// // Access file contents by path segments from the returned dictionary +/// +/// +public class ElmPackageSource +{ + /// + /// Default local cache directories (example: ~/.cache/elm-package). + /// You can add more directories if you want a search path. + /// + public static IReadOnlyList LocalCacheDirectoriesDefault => + [ + Path.Combine(Filesystem.CacheDirectory, "elm-package"), + ]; + + /// + /// Public entry point that uses the default cache directories. + /// + public static async Task, ReadOnlyMemory>> LoadElmPackageAsync( + string packageName, + string versionId) + { + return await LoadElmPackageAsync(packageName, versionId, LocalCacheDirectoriesDefault); + } + + /// + /// Tries to load from any of the given local cache directories; if not found, downloads from GitHub and saves to the first cache dir. + /// + public static async Task, ReadOnlyMemory>> LoadElmPackageAsync( + string packageName, + string versionId, + IReadOnlyList localCacheDirectories) + { + // 1) Try to load from each cache directory, in order: + foreach (var cacheDirectory in localCacheDirectories) + { + // Construct the path for the cached file, for example "agu-z-elm-zip@3.0.1.zip". + var localZipPath = GetLocalZipPath(cacheDirectory, packageName, versionId); + + if (File.Exists(localZipPath)) + { + try + { + // Attempt to load and parse the ZIP from disk. + var data = await File.ReadAllBytesAsync(localZipPath); + + var fromCache = LoadElmPackageFromZipBytes(data); + + return fromCache; + } + catch + { + // If anything failed (e.g. file is corrupt), ignore and keep going. + } + } + } + + // 2) No valid cache found — download from GitHub: + var zipData = await DownloadPackageZipAsync(packageName, versionId); + + var fromGitHub = LoadElmPackageFromZipBytes(zipData); + + // 3) Write the ZIP to the *first* cache directory, if present (and possible). + if (localCacheDirectories.Count > 0) + { + var firstCache = localCacheDirectories[0]; + + try + { + Directory.CreateDirectory(firstCache); // Ensure the directory exists. + + var localZipPath = GetLocalZipPath(firstCache, packageName, versionId); + + await File.WriteAllBytesAsync(localZipPath, zipData); + } + catch + { + // Caching is non-critical. Swallow any exception here (e.g. no write permission). + } + } + + return fromGitHub; + } + + /// + /// Given the raw bytes of a ZIP file, extract all files and return them as a dictionary + /// from path segments to the file contents. + /// + private static IReadOnlyDictionary, ReadOnlyMemory> LoadElmPackageFromZipBytes( + byte[] zipBytes) + { + var result = new Dictionary, ReadOnlyMemory>( + EnumerableExtension.EqualityComparer>()); + + using var memoryStream = new MemoryStream(zipBytes); + + using var archive = new System.IO.Compression.ZipArchive(memoryStream, ZipArchiveMode.Read); + + foreach (var entry in archive.Entries) + { + // If it's a directory entry, skip + if (string.IsNullOrEmpty(entry.Name)) + continue; + + using var entryStream = entry.Open(); + using var entryMemoryStream = new MemoryStream(); + entryStream.CopyTo(entryMemoryStream); + + var fileContents = entryMemoryStream.ToArray(); + + // Split the full name into segments + var pathSegments = entry.FullName.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + result[pathSegments] = fileContents; + } + + return result; + } + + + /// + /// Downloads the ZIP from GitHub as a byte array. + /// + private static async Task DownloadPackageZipAsync(string packageName, string versionId) + { + // Example: "https://github.com/agu-z/elm-zip/archive/refs/tags/3.0.1.zip" + + var downloadUrl = "https://github.com/" + packageName.Trim('/') + "/archive/refs/tags/" + versionId + ".zip"; + + using var httpClient = new HttpClient(); + + using var zipStream = await httpClient.GetStreamAsync(downloadUrl); + + // Copy to memory + using var memoryStream = new MemoryStream(); + + await zipStream.CopyToAsync(memoryStream); + + return memoryStream.ToArray(); + } + + /// + /// Constructs a local ZIP file path: e.g. "agu-z-elm-zip@3.0.1.zip" in the given cache directory. + /// Replaces slashes so the filename is filesystem-safe. + /// + private static string GetLocalZipPath(string cacheDirectory, string packageName, string versionId) + { + var safePkgName = packageName.Replace('/', '-'); + + // Example: "agu-z-elm-zip@3.0.1.zip" + var fileName = $"{safePkgName}@{versionId}.zip"; + + return Path.Combine(cacheDirectory, fileName); + } +} diff --git a/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm b/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm index 42e0144d..e0b31ef4 100644 --- a/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm +++ b/implement/pine/Elm/elm-compiler/src/CompileElmApp.elm @@ -703,53 +703,18 @@ loweredForBlobEntryPoint : -> AppFiles -> Result (List (LocatedInSourceFiles CompilationError)) ( AppFiles, ElmMakeEntryPointStruct ) loweredForBlobEntryPoint { compilationRootFilePath, compilationRootModule } sourceFiles = - ([ compilationRootModule.fileText - , String.trim """ -blob_main_as_base64 : String -blob_main_as_base64 = - blobMain - |> Base64.fromBytes - |> Maybe.withDefault "Failed to encode as Base64" - - -{-| Support function-level dead code elimination () Elm code needed to inform the Elm compiler about our entry points. --} -main : Program Int {} String -main = - Platform.worker - { init = always ( {}, Cmd.none ) - , update = always (always ( blob_main_as_base64 |> always {}, Cmd.none )) - , subscriptions = always Sub.none - } -""" - ] - |> String.join "\n\n" - |> addImportsInElmModuleText [ ( [ "Base64" ], Nothing ) ] - |> Result.mapError - (\err -> - [ LocatedInSourceFiles - { filePath = compilationRootFilePath - , locationInModuleText = Elm.Syntax.Range.emptyRange - } - (OtherCompilationError ("Failed to add import: " ++ err)) - ] - ) - ) - |> Result.map - (\rootModuleText -> - ( sourceFiles - |> updateFileContentAtPath (always (fileContentFromString rootModuleText)) compilationRootFilePath - , { elmMakeJavaScriptFunctionName = - ((compilationRootModule.parsedSyntax.moduleDefinition - |> Elm.Syntax.Node.value - |> Elm.Syntax.Module.moduleName - ) - ++ [ "blob_main_as_base64" ] - ) - |> String.join "." - } + Ok + ( sourceFiles + , { elmMakeJavaScriptFunctionName = + ((compilationRootModule.parsedSyntax.moduleDefinition + |> Elm.Syntax.Node.value + |> Elm.Syntax.Module.moduleName + ) + ++ [ "blobMain" ] ) - ) + |> String.join "." + } + ) sourceFileFunctionNameStart : String diff --git a/implement/pine/Elm019/ElmPackage.cs b/implement/pine/Elm019/ElmPackage.cs new file mode 100644 index 00000000..a8d05e0b --- /dev/null +++ b/implement/pine/Elm019/ElmPackage.cs @@ -0,0 +1,110 @@ +using Pine.Core; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace Pine.Elm019; + +public class ElmPackage +{ + record ParsedModule( + IReadOnlyList ModuleName, + IImmutableSet> ImportedModulesNames); + + public static IReadOnlyDictionary, (ReadOnlyMemory fileContent, string moduleText, IReadOnlyList moduleName)> + ExposedModules( + IReadOnlyDictionary, ReadOnlyMemory> packageSourceFiles) + { + Dictionary, (ReadOnlyMemory fileContent, string moduleText, IReadOnlyList moduleName)> exposedModules = + new(EnumerableExtension.EqualityComparer>()); + + var elmJsonFile = + /* + * If package files come from GitHub zip archive, then elm.json file might not be at the root but in + * a directory named after the repository, e.g. "elm-flate-2.0.5". + * */ + packageSourceFiles + .OrderBy(kv => kv.Key.Count) + .FirstOrDefault(kv => kv.Key.Last() is "elm.json"); + + if (elmJsonFile.Key is null) + { + return exposedModules; + } + + var elmJson = JsonSerializer.Deserialize(elmJsonFile.Value.Span); + + Dictionary, (IReadOnlyList filePath, ReadOnlyMemory fileContent, string moduleText, ParsedModule parsedModule)> parsedModulesByName = + new(EnumerableExtension.EqualityComparer>()); + + foreach (var (filePath, fileContent) in packageSourceFiles) + { + try + { + var moduleText = Encoding.UTF8.GetString(fileContent.Span); + + if (ElmTime.ElmSyntax.ElmModule.ParseModuleName(moduleText).IsOkOrNull() is not { } moduleName) + { + continue; + } + + parsedModulesByName[moduleName] = + (filePath, + fileContent, + moduleText, + new ParsedModule( + ModuleName: moduleName, + ImportedModulesNames: [.. ElmTime.ElmSyntax.ElmModule.ParseModuleImportedModulesNames(moduleText)])); + } + catch (Exception) + { + } + } + + IReadOnlySet> ListImportsOfModuleTransitive(IReadOnlyList moduleName) + { + var queue = + new Queue>([moduleName]); + + var set = + new HashSet>(EnumerableExtension.EqualityComparer>()); + + while (queue.TryDequeue(out var currentModuleName)) + { + if (set.Add(currentModuleName) && + parsedModulesByName.TryGetValue(currentModuleName, out var currentModule)) + { + foreach (var importedModuleName in currentModule.parsedModule.ImportedModulesNames) + { + queue.Enqueue(importedModuleName); + } + } + } + + return set; + } + + foreach (var exposedModuleNameFlat in elmJson.ExposedModules) + { + var exposedModuleName = exposedModuleNameFlat.Split('.').ToImmutableList(); + + var exposedNameIncludingDependencies = + ListImportsOfModuleTransitive(exposedModuleName) + .Prepend(exposedModuleName); + + foreach (var moduleName in exposedNameIncludingDependencies) + { + if (parsedModulesByName.TryGetValue(moduleName, out var module)) + { + exposedModules[module.filePath] = + (module.fileContent, module.moduleText, module.parsedModule.ModuleName); + } + } + } + + return exposedModules; + } +} diff --git a/implement/pine/ElmSyntax/ElmModule.cs b/implement/pine/ElmSyntax/ElmModule.cs index f9c864da..b87c4a75 100644 --- a/implement/pine/ElmSyntax/ElmModule.cs +++ b/implement/pine/ElmSyntax/ElmModule.cs @@ -146,6 +146,21 @@ public class DelegateComparer(Func func) : IComparer public int Compare(T? x, T? y) => func(x, y); } + public static Result> ParseModuleName(ReadOnlyMemory moduleContent) + { + try + { + var moduleText = + System.Text.Encoding.UTF8.GetString(moduleContent.Span); + + return ParseModuleName(moduleText); + } + catch (Exception exception) + { + return "Failed decoding text: " + exception.Message; + } + } + public static Result> ParseModuleName(string moduleText) { { diff --git a/implement/pine/Program.cs b/implement/pine/Program.cs index 3fd43d3e..80545b98 100644 --- a/implement/pine/Program.cs +++ b/implement/pine/Program.cs @@ -9,6 +9,7 @@ using Pine.Elm; using Pine.Elm.Platform; using Pine.Elm019; +using Pine.ElmInteractive; using Pine.PineVM; using System; using System.Collections.Generic; @@ -2278,91 +2279,268 @@ private record LoadForMakeResult( { workingDirectoryRelative ??= []; - var pathToFileWithElmEntryPointFromWorkingDir = - pathToFileWithElmEntryPoint.Skip(workingDirectoryRelative.Count).ToImmutableList(); + IReadOnlyList pathToFileWithElmEntryPointFromWorkingDir = + [.. pathToFileWithElmEntryPoint.Skip(workingDirectoryRelative.Count)]; - return + var loweringResult = ElmAppCompilation.AsCompletelyLoweredElmApp( - sourceFiles: sourceFiles.ToImmutableDictionary(), + sourceFiles: sourceFiles.ToImmutableDictionary(), + workingDirectoryRelative: workingDirectoryRelative, + interfaceConfig: new ElmAppInterfaceConfig(compilationRootFilePath: pathToFileWithElmEntryPoint)); + + var entryPointSourceFile = + sourceFiles[pathToFileWithElmEntryPoint]; + + var entryPointSourceFileText = + Encoding.UTF8.GetString(entryPointSourceFile.Span); + + var entryPointModuleNameResult = + ElmSyntax.ElmModule.ParseModuleName(entryPointSourceFileText); + + if (entryPointModuleNameResult.IsErrOrNull() is { } entryPointModuleNameErr) + { + return + "Failed to parse module name from entry point file: " + entryPointModuleNameErr; + } + + if (entryPointModuleNameResult.IsOkOrNull() is not { } entryPointModuleNameOk) + { + throw new Exception( + "Unexpected entry point module name result type: " + entryPointModuleNameResult); + } + + if (loweringResult.IsErrOrNull() is { } loweringErr) + { + return + "Failed lowering Elm code with " + loweringErr.Count + " error(s):\n" + + ElmAppCompilation.CompileCompilationErrorsDisplayText(loweringErr); + } + + if (loweringResult.IsOkOrNull() is not { } loweringOk) + { + throw new Exception("Unexpected lowering result type: " + loweringResult); + } + + var sourceFilesAfterLowering = loweringOk.result.compiledFiles; + + Result continueWithClassicEntryPoint() + { + return + Elm019Binaries.ElmMake( + sourceFilesAfterLowering, workingDirectoryRelative: workingDirectoryRelative, - interfaceConfig: new ElmAppInterfaceConfig(compilationRootFilePath: pathToFileWithElmEntryPoint)) - .MapError(err => - "Failed lowering Elm code with " + err.Count + " error(s):\n" + - ElmAppCompilation.CompileCompilationErrorsDisplayText(err)) - .AndThen(loweringOk => - { - var sourceFilesAfterLowering = loweringOk.result.compiledFiles; + pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir, + outputFileName: outputFileName.Replace('\\', '/').Split('/').Last(), + elmMakeCommandAppendix: elmMakeCommandAppendix); + } - Result continueWithClassicEntryPoint() + Result continueWithBlobEntryPoint( + CompilerSerialInterface.ElmMakeEntryPointStruct entryPointStruct) + { + /* + * TODO: select elm.json for the given entry point + * */ + + var elmJsonFiles = + sourceFilesAfterLowering + .Where(entry => entry.Key.Last() is "elm.json") + .ToImmutableDictionary(); + + var elmJsonAggregateDependencies = + elmJsonFiles + .SelectMany(elmJsonFile => + { + try { + var elmJsonParsed = + System.Text.Json.JsonSerializer.Deserialize(elmJsonFile.Value.Span); + return - Elm019Binaries.ElmMake( - sourceFilesAfterLowering, - workingDirectoryRelative: workingDirectoryRelative, - pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir, - outputFileName: outputFileName.Replace('\\', '/').Split('/').Last(), - elmMakeCommandAppendix: elmMakeCommandAppendix); + new[] + { + elmJsonParsed?.Dependencies.Direct, + elmJsonParsed?.Dependencies.Indirect, + elmJsonParsed?.Dependencies.Flat + } + .WhereNotNull(); } + catch (Exception e) + { + Console.WriteLine("Failed to parse elm.json file: " + e); - Result continueWithBlobEntryPoint( - CompilerSerialInterface.ElmMakeEntryPointStruct entryPointStruct) + return []; + } + }) + .SelectMany(dependency => dependency) + .ToImmutableDictionary( + keySelector: dependency => dependency.Key, + elementSelector: + dependency => { + var packageFiles = + ElmPackageSource.LoadElmPackageAsync(dependency.Key, dependency.Value).Result; + + return packageFiles; + }); + + var sourceElmModulesNames = + sourceFilesAfterLowering + .Where(entry => entry.Key.Last().EndsWith(".elm", StringComparison.OrdinalIgnoreCase)) + .Select(entry => ElmSyntax.ElmModule.ParseModuleName(entry.Value).WithDefault(null)) + .WhereNotNull() + .ToImmutableHashSet(EnumerableExtension.EqualityComparer>()); + + var packagesModulesNames = + new HashSet>(EnumerableExtension.EqualityComparer>()); + + var sourceFilesWithMergedPackages = + elmJsonAggregateDependencies + .Aggregate( + seed: sourceFilesAfterLowering, + func: (aggregate, package) => + { + if (package.Key is "elm/core" || + package.Key is "elm/json" || + package.Key is "elm/bytes" || + package.Key is "elm/parser") + { + return aggregate; + } + + var packageExposedModuleFiles = + ElmPackage.ExposedModules(package.Value); + return - Elm019Binaries.ElmMakeToJavascript( - sourceFilesAfterLowering, - workingDirectoryRelative: workingDirectoryRelative, - pathToFileWithElmEntryPoint: pathToFileWithElmEntryPointFromWorkingDir, - elmMakeCommandAppendix: elmMakeCommandAppendix) - .AndThen(makeJavascriptOk => + packageExposedModuleFiles + .Aggregate( + seed: aggregate, + func: (innerAggregate, packageElmModuleFile) => + { + var relativePath = packageElmModuleFile.Key; + + var moduleName = packageElmModuleFile.Value.moduleName; + + if (sourceElmModulesNames.Contains(moduleName)) { - var javascriptFromElmMake = - Encoding.UTF8.GetString(makeJavascriptOk.producedFile.Span); + Console.WriteLine( + "Skipping Elm module file " + + string.Join("/", relativePath) + + " from package " + package.Key + " because it is already present in the source files."); - var javascriptMinusCrashes = - ProcessFromElm019Code.JavascriptMinusCrashes(javascriptFromElmMake); + return innerAggregate; + } - var functionNameInElm = entryPointStruct.elmMakeJavaScriptFunctionName; + if (packagesModulesNames.Contains(moduleName)) + { + Console.WriteLine( + "Skipping Elm module file " + + string.Join("/", relativePath) + + " from package " + package.Key + " because it is already present in the packages."); - var listFunctionToPublish = - new[] - { - (functionNameInElm: functionNameInElm, - publicName: "blob_main_as_base64", - arity: 0), - }; + return innerAggregate; + } - var finalJs = - ProcessFromElm019Code.PublishFunctionsFromJavascriptFromElmMake( - javascriptMinusCrashes, - listFunctionToPublish); + packagesModulesNames.Add(moduleName); - using var javascriptEngine = IJavaScriptEngine.BuildJavaScriptEngine(); + return + innerAggregate.SetItem( + ["dependencies", .. package.Key.Split('/'), .. packageElmModuleFile.Key], + packageElmModuleFile.Value.fileContent); + }); + }); - javascriptEngine.Evaluate(finalJs); + var elmCompilerFromBundle = + BundledElmEnvironments.BundledElmCompilerCompiledEnvValue() + ?? + throw new Exception("Failed to load Elm compiler from bundle."); - var blobBase64 = (string)javascriptEngine.Evaluate("blob_main_as_base64"); + var elmCompiler = + ElmCompiler.ElmCompilerFromEnvValue(elmCompilerFromBundle) + .Extract(err => throw new Exception(err)); - return - Result.ok( - new Elm019Binaries.ElmMakeOk( - producedFile: Convert.FromBase64String(blobBase64))); - }); - } + var pineVMCache = new PineVMCache(); - return - loweringOk.result.rootModuleEntryPointKind - .MapError(err => "Failed to get entry point main declaration: " + err) - .AndThen(rootModuleEntryPointKind => - rootModuleEntryPointKind switch - { - CompilerSerialInterface.ElmMakeEntryPointKind.ClassicMakeEntryPoint => - continueWithClassicEntryPoint(), - CompilerSerialInterface.ElmMakeEntryPointKind.BlobMakeEntryPoint blob => - continueWithBlobEntryPoint(blob.EntryPointStruct), + var pineVM = + new PineVM(evalCache: pineVMCache.EvalCache); - _ => throw new NotImplementedException() - }); - }); + var parseCache = new PineVMParseCache(); + + var elmCompilerCache = new ElmCompilerCache(); + + var compileResult = + InteractiveSessionPine.CompileInteractiveEnvironment( + appCodeTree: + PineValueComposition.SortedTreeFromSetOfBlobsWithStringPath(sourceFilesWithMergedPackages), + overrideSkipLowering: true, + elmCompiler: elmCompiler); + + if (compileResult.IsErrOrNull() is { } compileErr) + { + return + "Failed to compile Elm interactive env: " + compileErr; + } + + if (compileResult.IsOkOrNull() is not { } compileOk) + { + throw new Exception("Unexpected compile result type: " + compileResult); + } + + var parseFromEnvResult = + ElmInteractiveEnvironment.ParseFunctionFromElmModule( + interactiveEnvironment: compileOk, + moduleName: string.Join(".", entryPointModuleNameOk.ToArray()), + "blobMain", + parseCache); + + { + if (parseFromEnvResult.IsErrOrNull() is { } parseErr) + { + return "Failed to parse Elm module: " + parseErr; + } + } + + if (parseFromEnvResult.IsOkOrNullable() is not { } parseFromEnvOk) + { + throw new Exception("Unexpected parse result type: " + parseFromEnvResult); + } + + var parseDeclResult = ElmValueEncoding.PineValueAsElmValue(parseFromEnvOk.declValue, null, null); + + if (parseDeclResult.IsErrOrNull() is { } parseDeclErr) + { + return "Failed to parse Elm value: " + parseDeclErr; + } + + if (parseDeclResult.IsOkOrNull() is not { } parseDeclOk) + { + throw new Exception("Unexpected parse result type: " + parseDeclResult); + } + + if (parseDeclOk is not ElmValue.ElmBytes bytesValue) + { + return "Expected Elm bytes value, but got: " + parseDeclOk; + } + + return + new Elm019Binaries.ElmMakeOk(producedFile: bytesValue.Value); + } + + return + loweringOk.result.rootModuleEntryPointKind + .MapError(err => "Failed to get entry point main declaration: " + err) + .AndThen(rootModuleEntryPointKind => + rootModuleEntryPointKind switch + { + CompilerSerialInterface.ElmMakeEntryPointKind.ClassicMakeEntryPoint => + continueWithClassicEntryPoint(), + + CompilerSerialInterface.ElmMakeEntryPointKind.BlobMakeEntryPoint blob => + continueWithBlobEntryPoint(blob.EntryPointStruct), + + _ => + throw new NotImplementedException( + "Unexpected root module entry point kind: " + rootModuleEntryPointKind), + }); } public static IEnumerable DescribeCompositionForHumans( diff --git a/implement/test-elm-time/ProgramCommandMakeTests.cs b/implement/test-elm-time/ProgramCommandMakeTests.cs index 1b6ffb97..aa7380c5 100644 --- a/implement/test-elm-time/ProgramCommandMakeTests.cs +++ b/implement/test-elm-time/ProgramCommandMakeTests.cs @@ -31,24 +31,11 @@ public void TestCommandMake_with_blobMain() "dependencies": { "direct": { "TSFoster/elm-bytes-extra": "1.3.0", - "agu-z/elm-zip": "3.0.1", - "danfishgold/base64-bytes": "1.1.0", - "elm/browser": "1.0.2", "elm/bytes": "1.0.8", "elm/core": "1.0.5", - "elm/html": "1.0.0", - "elm/json": "1.1.3", - "elm/time": "1.0.0", - "elm/url": "1.0.0", - "mdgriffith/elm-ui": "1.1.8" + "elm/json": "1.1.3" }, "indirect": { - "elm/parser": "1.1.0", - "elm/virtual-dom": "1.0.3", - "elm-community/list-extra": "8.7.0", - "folkertdev/elm-flate": "2.0.5", - "justinmimbs/date": "4.0.1", - "justinmimbs/time-extra": "1.1.1" } }, "test-dependencies": { @@ -71,17 +58,20 @@ import Bytes.Extra blobMain : Bytes.Bytes blobMain = - Bytes.Extra.fromByteValues [ 0, 1, 3, 4 ] + Bytes.Extra.fromByteValues [ 0, 1, 3, 4, 71 ] """, }, }; - var outputBlob = GetOutputFileContentForCommandMake( - projectFiles: projectFiles.Select(file => ((IReadOnlyList)file.path, (ReadOnlyMemory)Encoding.UTF8.GetBytes(file.content))).ToImmutableList(), - entryPointFilePath: ["src", "Build.elm"]); + var outputBlob = + GetOutputFileContentForCommandMake( + projectFiles: + [.. projectFiles + .Select(file => ((IReadOnlyList)file.path, (ReadOnlyMemory)Encoding.UTF8.GetBytes(file.content)))], + entryPointFilePath: ["src", "Build.elm"]); - CollectionAssert.AreEqual(new byte[] { 0, 1, 3, 4 }, outputBlob.Span.ToArray()); + CollectionAssert.AreEqual(new byte[] { 0, 1, 3, 4, 71 }, outputBlob.Span.ToArray()); } private static ReadOnlyMemory GetOutputFileContentForCommandMake( @@ -98,12 +88,13 @@ private static ReadOnlyMemory GetOutputFileContentForCommandMake( IImmutableDictionary, ReadOnlyMemory> projectFiles, IReadOnlyList entryPointFilePath) { - var makeResult = Program.Make( - sourceFiles: projectFiles, - workingDirectoryRelative: null, - pathToFileWithElmEntryPoint: entryPointFilePath, - outputFileName: "should-not-matter", - elmMakeCommandAppendix: null); + var makeResult = + Program.Make( + sourceFiles: projectFiles, + workingDirectoryRelative: null, + pathToFileWithElmEntryPoint: entryPointFilePath, + outputFileName: "should-not-matter", + elmMakeCommandAppendix: null); return makeResult.Extract(fromErr: err => throw new Exception("Failed make command:\n" + err)).producedFile; }