← All source files

V11 / ca6f28eff240

WorkspaceProjectExtensions.cs

Lilith/Core/Workspace/Extensions/WorkspaceProjectExtensions.cs

Changed in this version
CS
using Lilith.Core.Projects;

namespace Lilith.Core.Workspace.Extensions;

internal static class WorkspaceProjectExtensions
{
    internal static ProjectBundle ReadProjectBundle(this WorkspaceRequest request, WorkspaceOptions options)
    {
        if (request.Content.Length > 200000) throw new InvalidDataException("Project proposal exceeds 200000 characters.");
        var bundle = ProjectJson.Parse<ProjectBundle>(request.Content);
        if (bundle.Files is null || bundle.Files.Length is < 3 or > 16 || bundle.Files.Sum(f => f.Content?.Length ?? 0) > 160000 ||
            bundle.Files.Select(f => f.Path).Distinct(StringComparer.OrdinalIgnoreCase).Count() != bundle.Files.Length)
            throw new InvalidDataException("Invalid project publication bundle.");
        string target = options.ResolvePath(request.Path);
        foreach (var file in bundle.Files)
        {
            if (file.Content is null) throw new InvalidDataException("Missing project file content.");
            string path = options.ResolvePath(request.Path + "/" + file.Path);
            if (!path.StartsWith(target + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
                Path.GetExtension(file.Path).ToLowerInvariant() is not (".cs" or ".csproj" or ".html" or ".css" or ".js" or ".txt" or ".json"))
                throw new InvalidDataException("Invalid project publication path.");
        }
        if (!bundle.Files.Any(f => f.Path == "lilith-project.json")) throw new InvalidDataException("Missing project manifest.");
        return bundle;
    }

    internal static async Task PublishProjectAsync(this WorkspaceRequest request, WorkspaceOptions options, string target, string operationId)
    {
        var bundle = request.ReadProjectBundle(options);
        string temporary = options.GetMetadataPath("project-staging", operationId);
        if (Directory.Exists(temporary)) throw new IOException("A previous incomplete publication is retained for review.");
        Directory.CreateDirectory(temporary);
        foreach (var file in bundle.Files)
        {
            string path = Path.Combine(temporary, file.Path.Replace('/', Path.DirectorySeparatorChar));
            Directory.CreateDirectory(Path.GetDirectoryName(path)!);
            await File.WriteAllTextAsync(path, file.Content, CancellationToken.None);
        }
        // Same workspace volume: publication is a single directory rename and never overwrites a project.
        Directory.Move(temporary, target);
    }
}