Example #1
0
 /// <summary>Output any added error messages to Console</summary>
 /// <exception cref="CommandException">Throw if any errors were added</exception>
 public void ThrowIfErrors()
 {
     if (_errors.Count > 0)
     {
         Write.ErrorExit($"Invalid configuration to run '{_name}' command", _errors.ToArray());
         throw new CommandException("Invalid config for InitCommand");
     }
 }
Example #2
0
    public void ErrorExit_WritesExtraLine()
    {
        using var mockConsole = new FakeConsole();

        Write.ErrorExit("MyError");

        Assert.Equal(
            $"{Red}ERROR: MyError{Reset}{NL}{Red}Exiting{Reset}{NL}",
            mockConsole.GetOuput()
            );
    }
Example #3
0
    private static UploadInitiateData InitiateUploadRequest(Config.Config config, string filepath)
    {
        var response = HttpClient.Send(config.Api.StartUploadMedia(filepath));

        HandleRequestError("initializing the upload", response, HttpStatusCode.Created);

        using var responseReader = new StreamReader(response.Content.ReadAsStream());
        var responseContent = responseReader.ReadToEnd();
        var uploadData      = UploadInitiateData.Deserialize(responseContent);

        if (uploadData is null)
        {
            Write.ErrorExit("Undeserializable InitiateUploadRequest response:", Dim(responseContent));
            throw new PublishCommandException();
        }

        if (uploadData.Metadata?.Filename is null || uploadData.Metadata?.UUID is null)
        {
            Write.ErrorExit("No valid Metadata found in InitiateUploadRequest response:", Dim(responseContent));
            throw new PublishCommandException();
        }

        if (uploadData.UploadUrls is null)
        {
            Write.ErrorExit("No valid UploadUrls found in InitiateUploadRequest response:", Dim(responseContent));
            throw new PublishCommandException();
        }

        string[] suffixes    = { "B", "KB", "MB", "GB", "TB" };
        int      suffixIndex = 0;
        long     size        = uploadData.Metadata.Size;

        while (size >= 1024 && suffixIndex < suffixes.Length)
        {
            size /= 1024;
            suffixIndex++;
        }

        var details = $"({size}{suffixes[suffixIndex]}) in {uploadData.UploadUrls.Length} chunks...";

        Write.WithNL($"Uploading {Cyan(uploadData.Metadata.Filename)} {details}", after: true);

        return(uploadData);
    }
Example #4
0
    private static void HandleRequestError(
        string step,
        HttpResponseMessage response,
        HttpStatusCode expectedStatus = HttpStatusCode.OK
        )
    {
        if (response.StatusCode == expectedStatus)
        {
            return;
        }

        using var responseReader = new StreamReader(response.Content.ReadAsStream());
        Write.ErrorExit(
            $"Unexpected response from the server while {step}:",
            $"Status code: {response.StatusCode:D} {response.StatusCode}",
            Dim(responseReader.ReadToEnd())
            );
        throw new PublishCommandException();
    }
Example #5
0
    public override bool Validate()
    {
        if (!base.Validate())
        {
            return(false);
        }

        if (!(File is null))
        {
            var filePath = Path.GetFullPath(File);
            if (!System.IO.File.Exists(filePath))
            {
                Write.ErrorExit(
                    $"Package file not found, looked from: {White(Dim(filePath))}",
                    "Package defined with --file argument must exist."
                    );
                return(false);
            }
        }

        return(true);
    }
Example #6
0
    private static void PublishPackageRequest(Config.Config config, string uploadUuid)
    {
        var response = HttpClient.Send(config.Api.SubmitPackage(uploadUuid));

        HandleRequestError("publishing package", response);

        using var responseReader = new StreamReader(response.Content.ReadAsStream());
        var responseContent = responseReader.ReadToEnd();
        var jsonData        = PublishData.Deserialize(responseContent);

        if (jsonData?.PackageVersion?.DownloadUrl is null)
        {
            Write.ErrorExit(
                "Field package_version.download_url missing from PublishPackageRequest response:",
                Dim(responseContent)
                );
            throw new PublishCommandException();
        }

        Write.Success($"Successfully published {Cyan($"{config.PackageMeta.Namespace}-{config.PackageMeta.Name}")}");
        Write.Line($"It's available at {Cyan(jsonData.PackageVersion.DownloadUrl)}");
    }
Example #7
0
    public virtual bool Validate()
    {
        if (String.IsNullOrWhiteSpace(ConfigPath))
        {
            Write.ErrorExit("Invalid value for --config-path argument");
            return(false);
        }

        var isInitCommand = this is InitOptions;
        var fullPath      = Path.GetFullPath(ConfigPath);

        if (!isInitCommand && !File.Exists(fullPath))
        {
            Write.ErrorExit(
                $"Configuration file not found, looked from: {White(Dim(fullPath))}",
                "A project configuration file is required for this command.",
                "You can initialize one with the 'init' command or define its location with --config-path argument."
                );
            return(false);
        }

        return(true);
    }
Example #8
0
    public static int PublishFile(Config.Config config, string filepath)
    {
        Write.WithNL($"Publishing {Cyan(filepath)}", before: true, after: true);

        if (!File.Exists(filepath))
        {
            Write.ErrorExit(
                "File selected for publish was not found",
                $"Looked from: {White(Dim(filepath))}"
                );
            return(1);
        }

        UploadInitiateData uploadData;

        try
        {
            uploadData = InitiateUploadRequest(config, filepath);
        }
        catch (PublishCommandException)
        {
            return(1);
        }

        Task <CompletedUpload.CompletedPartData>[] uploadTasks;

        try
        {
            uploadTasks = uploadData.UploadUrls !.Select(  // Validated in InitiateUploadRequest
                partData => UploadChunk(partData, filepath)
                ).ToArray();
        }
        catch (PublishCommandException)
        {
            return(1);
        }

        string uploadUuid = uploadData.Metadata?.UUID !;  // Validated in InitiateUploadRequest

        try
        {
            var spinner = new ProgressSpinner("chunks uploaded", uploadTasks);
            spinner.Start().GetAwaiter().GetResult();
        }
        catch (SpinnerException)
        {
            HttpClient.Send(config.Api.AbortUploadMedia(uploadUuid));
            return(1);
        }

        var uploadedParts = uploadTasks.Select(x => x.Result).ToArray();

        try
        {
            var response = HttpClient.Send(
                config.Api.FinishUploadMedia(
                    new CompletedUpload
            {
                Parts = uploadedParts
            },
                    uploadUuid
                    )
                );

            HandleRequestError("Finishing usermedia upload", response);
            Write.Success("Successfully finalized the upload");
        }
        catch (PublishCommandException)
        {
            return(1);
        }

        try
        {
            PublishPackageRequest(config, uploadUuid);
        }
        catch (PublishCommandException)
        {
            return(1);
        }

        return(0);
    }
Example #9
0
    private static async Task <CompletedUpload.CompletedPartData> UploadChunk(UploadInitiateData.UploadPartData part, string filepath)
    {
        try
        {
            await Task.Yield();

            await using var stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
            stream.Seek(part.Offset, SeekOrigin.Begin);

            byte[]    hash;
            var       chunk     = new MemoryStream();
            const int blocksize = 65536;

            using (var reader = new BinaryReader(stream, Encoding.Default, true))
            {
                using (var md5 = MD5.Create())
                {
                    md5.Initialize();
                    var length = part.Length;

                    while (length > blocksize)
                    {
                        length -= blocksize;
                        var bytes = reader.ReadBytes(blocksize);
                        md5.TransformBlock(bytes, 0, blocksize, null, 0);
                        await chunk.WriteAsync(bytes);
                    }

                    var finalBytes = reader.ReadBytes(length);
                    md5.TransformFinalBlock(finalBytes, 0, length);

                    if (md5.Hash is null)
                    {
                        Write.ErrorExit($"MD5 hashing failed for part #{part.PartNumber})");
                        throw new PublishCommandException();
                    }

                    hash = md5.Hash;
                    await chunk.WriteAsync(finalBytes);

                    chunk.Position = 0;
                }
            }

            var request = new HttpRequestMessage(HttpMethod.Put, part.Url);
            request.Content = new StreamContent(chunk);
            request.Content.Headers.ContentMD5    = hash;
            request.Content.Headers.ContentLength = part.Length;

            using var response = await HttpClient.SendAsync(request);

            try
            {
                response.EnsureSuccessStatusCode();
            }
            catch
            {
                Write.Empty();
                Write.ErrorExit(await response.Content.ReadAsStringAsync());
                throw new PublishCommandException();
            }

            if (response.Headers.ETag is null)
            {
                Write.Empty();
                Write.ErrorExit($"Response contained no ETag for part #{part.PartNumber}");
                throw new PublishCommandException();
            }

            return(new CompletedUpload.CompletedPartData()
            {
                ETag = response.Headers.ETag.Tag,
                PartNumber = part.PartNumber
            });
        }
        catch (Exception e)
        {
            Write.Empty();
            Write.ErrorExit($"Exception occured while uploading file chunk #{part.PartNumber}:", e.ToString());
            throw new PublishCommandException();
        }
    }
Example #10
0
    public static int DoBuild(Config.Config config)
    {
        var packageId = config.GetPackageId();

        Write.WithNL($"Building {Cyan(packageId)}", after: true);

        var readmePath = config.GetPackageReadmePath();

        if (!File.Exists(readmePath))
        {
            Write.ErrorExit($"Readme not found from the declared path: {White(Dim(readmePath))}");
            return(1);
        }

        var iconPath = config.GetPackageIconPath();

        if (!File.Exists(iconPath))
        {
            Write.ErrorExit($"Icon not found from the declared path: {White(Dim(iconPath))}");
            return(1);
        }

        var outDir = config.GetBuildOutputDir();

        if (!Directory.Exists(outDir))
        {
            Directory.CreateDirectory(outDir);
        }
        var filename = config.GetBuildOutputFile();

        Write.Line($"Output path {Cyan(filename)}");

        var encounteredIssues = false;

        var plan = new ArchivePlan(config);

        Write.Header("Planning for files to include in build");

        plan.AddPlan("icon.png", () => File.ReadAllBytes(iconPath));
        plan.AddPlan("README.md", () => File.ReadAllBytes(readmePath));
        plan.AddPlan("manifest.json", () => Encoding.UTF8.GetBytes(SerializeManifest(config)));

        if (config.BuildConfig.CopyPaths is not null)
        {
            foreach (var pathMap in config.BuildConfig.CopyPaths)
            {
                Write.WithNL($"Mapping {Dim(pathMap.From)} to {Dim($"/{pathMap.To}")}", before: true);
                encounteredIssues |= !AddPathToArchivePlan(plan, pathMap.From, pathMap.To);
            }
        }

        if (plan.HasErrors)
        {
            Write.Empty();
            Write.ErrorExit(
                "Build was aborted due to errors identified in planning phase",
                "Adjust your configuration so no issues are present"
                );
            return(1);
        }

        Write.Header("Writing configured files");

        using (var outputFile = File.Open(filename, FileMode.Create))
        {
            using (var archive = new ZipArchive(outputFile, ZipArchiveMode.Create))
            {
                var isWindows = OperatingSystem.IsWindows();
                foreach (var entry in plan)
                {
                    Write.Light($"Writing /{entry.Key}");
                    var archiveEntry = archive.CreateEntry(entry.Key, CompressionLevel.Optimal);
                    if (!isWindows)
                    {
                        // https://github.com/dotnet/runtime/issues/17912#issuecomment-641594638
                        // modifed solution to use a constant instead of a string conversion
                        archiveEntry.ExternalAttributes |= 0b110110100 << 16; // rw-rw-r-- permissions
                    }
                    using (var writer = new BinaryWriter(archiveEntry.Open()))
                    {
                        writer.Write(entry.Value());
                    }
                }
            }
        }

        Write.Empty();

        if (encounteredIssues || plan.HasWarnings)
        {
            Write.Note("Some issues were encountered when building, see output for more details");
            return(1);
        }
        else
        {
            Write.Success($"Successfully built {Cyan(packageId)}");
            return(0);
        }
    }