/// <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"); } }
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() ); }
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); }
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(); }
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); }
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)}"); }
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); }
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); }
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(); } }
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); } }