diff --git a/MSStore.CLI.UnitTests/BaseCommandLineTest.cs b/MSStore.CLI.UnitTests/BaseCommandLineTest.cs index 52a0379..a3ecba7 100644 --- a/MSStore.CLI.UnitTests/BaseCommandLineTest.cs +++ b/MSStore.CLI.UnitTests/BaseCommandLineTest.cs @@ -372,7 +372,7 @@ internal void AddFakeAccount(AccountEnrollment? accountEnrollment) }); } - protected void AddDefaultFakeSubmission() + protected void AddDefaultFakeSubmission(string listingDescription = "BaseListingDescription") { var fakeSubmission = new DevCenterSubmission { @@ -406,7 +406,7 @@ protected void AddDefaultFakeSubmission() { BaseListing = new BaseListing { - Description = "BaseListingDescription" + Description = listingDescription } } } @@ -764,6 +764,10 @@ protected void SetupBasedOnTestDataProjectSubPath(DirectoryInfo dirInfo, string[ var outputCapture = new OutputCapture(Console.Out); var errorCapture = RefreshAnsiConsole(); + // Only stdout is redirected: the error capture is reached exclusively through + // ErrorAnsiConsole, mirroring how Program.cs keeps the two streams apart. + Console.SetOut(outputCapture); + AnsiConsole.Console = AnsiConsole.Create(new AnsiConsoleSettings { Ansi = AnsiSupport.Yes, @@ -871,7 +875,6 @@ internal sealed class OutputCapture : TextWriter, IDisposable public OutputCapture(TextWriter textWriter) { _stdOutWriter = textWriter; - Console.SetOut(this); Captured = new StringWriter(); } diff --git a/MSStore.CLI.UnitTests/PackageCommandUnitTests.cs b/MSStore.CLI.UnitTests/PackageCommandUnitTests.cs index d49a356..9ca0b87 100644 --- a/MSStore.CLI.UnitTests/PackageCommandUnitTests.cs +++ b/MSStore.CLI.UnitTests/PackageCommandUnitTests.cs @@ -99,7 +99,7 @@ public async Task PackageCommandForUWPAppsShouldCallMSBuildWithOutputParameterIf ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(customPath); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(customPath); ExternalCommandExecutor.VerifyAll(); } @@ -216,7 +216,7 @@ public async Task PackageCommandForWinUIAppsShouldCallMSBuildWithOutputParameter ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(customPath); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(customPath); ExternalCommandExecutor.VerifyAll(); } @@ -295,7 +295,7 @@ public async Task PackageCommandForMauiAppsShouldCallMSBuildWithOutputParameterI ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(customPath); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(customPath); ExternalCommandExecutor.VerifyAll(); } @@ -375,7 +375,7 @@ public async Task PackageCommandForFlutterAppsShouldCallFlutter() ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(path); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(path); } [TestMethod] @@ -426,7 +426,7 @@ public async Task PackageCommandForFlutterAppsShouldCallFlutterWithOutputParamet ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(customPath); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(customPath); } private void SetupPubGet(DirectoryInfo dirInfo) @@ -478,7 +478,7 @@ public async Task PackageCommandForElectronNpmAppsShouldCallElectronNpm() ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(path); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(path); } [TestMethod] @@ -514,7 +514,7 @@ public async Task PackageCommandForElectronYarnAppsShouldCallElectronYarn() ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(path); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(path); } [TestMethod] @@ -566,7 +566,29 @@ public async Task PackageCommandForReactNativeNpmAppsShouldCallMSBuild(string ma ]); result.Error.Should().Contain("The packaged app is here:"); - result.Output.Should().Contain(path); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Should().ContainSingle().Which.Should().Contain(path); + } + + [TestMethod] + public async Task PackageCommandForPWAsShouldPrintAnAbsolutePathForARelativeArgument() + { + var path = CopyFilesRecursively("PWAProject"); + + // CopyFilesRecursively hands back a path relative to the test working directory, and + // the PWA packager is the only one that echoes the argument straight back as its + // output directory instead of deriving it from a built file. So this is the case + // where DirectoryInfo.ToString() would emit a bare relative path to stdout. + Path.IsPathFullyQualified(path).Should().BeFalse(); + + var result = await ParseAndInvokeAsync( + [ + "package", + path + ]); + + result.Error.Should().Contain("The packaged app is here:"); + result.Output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) + .Should().ContainSingle().Which.Should().Be(Path.GetFullPath(path)); } } } \ No newline at end of file diff --git a/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs b/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs index 150960c..f133dc7 100644 --- a/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs +++ b/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Text.Json; using MSStore.API.Packaged.Models; namespace MSStore.CLI.UnitTests @@ -68,6 +69,40 @@ public async Task PackagedSubmissionGetCommand() result.Output.Should().Contain("\"FileUploadUrl\": \"https://azureblob.com/fileupload\""); } + [TestMethod] + public async Task PackagedSubmissionGetCommandShouldNotWrapJsonOutput() + { + // Longer than the width the test console renders at, and sprinkled with characters + // that the serializer escapes as \uXXXX, so a wrap would both break the JSON and + // corrupt the description. + var longDescription = string.Concat( + Enumerable.Repeat("Sync your mail & calendar across every device without a fuss. ", 12)); + + AddDefaultFakeSubmission(longDescription); + + FakeApps[0].LastPublishedApplicationSubmission = new ApplicationSubmissionInfo + { + Id = "123456789" + }; + + var result = await ParseAndInvokeAsync( + [ + "submission", + "get", + FakeApps[0].Id! + ]); + + using var json = JsonDocument.Parse(result.Output); + + json.RootElement + .GetProperty("Listings") + .GetProperty("en-us") + .GetProperty("BaseListing") + .GetProperty("Description") + .GetString() + .Should().Be(longDescription); + } + [TestMethod] public async Task PackagedSubmissionGetListingAssetsCommand() { diff --git a/MSStore.CLI.UnitTests/SubmissionCommandUnpackagedUnitTests.cs b/MSStore.CLI.UnitTests/SubmissionCommandUnpackagedUnitTests.cs index 00d9f54..e5381f6 100644 --- a/MSStore.CLI.UnitTests/SubmissionCommandUnpackagedUnitTests.cs +++ b/MSStore.CLI.UnitTests/SubmissionCommandUnpackagedUnitTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Text.Json; using MSStore.API.Models; namespace MSStore.CLI.UnitTests @@ -68,6 +69,50 @@ public async Task UnpackagedSubmissionGetCommand() result.Output.Should().Contain("\"PackageId\": \"12345\""); } + [TestMethod] + public async Task UnpackagedSubmissionGetCommandShouldNotWrapJsonOutput() + { + // Longer than the width the test console renders at, and sprinkled with characters + // that the serializer escapes as \uXXXX, so a wrap would both break the JSON and + // corrupt the description. + var longDescription = string.Concat( + Enumerable.Repeat("Sync your mail & calendar across every device without a fuss. ", 12)); + + FakeStoreAPI + .Setup(x => x.GetDraftAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new ResponseWrapper + { + IsSuccess = true, + ResponseData = new ListingsMetadataResponse + { + Listings = + [ + new Listing + { + Language = "en-us", + Description = longDescription + } + ] + } + }); + + var result = await ParseAndInvokeAsync( + [ + "submission", + "get", + Guid.Empty.ToString() + ]); + + using var json = JsonDocument.Parse(result.Output); + + json.RootElement + .GetProperty("ResponseData") + .GetProperty("Listings")[0] + .GetProperty("Description") + .GetString() + .Should().Be(longDescription); + } + [TestMethod] public async Task UnpackagedSubmissionGetListingAssetsCommand() { diff --git a/MSStore.CLI.UnitTests/TestData/PWAProject/pwaAppInfo.json b/MSStore.CLI.UnitTests/TestData/PWAProject/pwaAppInfo.json new file mode 100644 index 0000000..a8a9c6a --- /dev/null +++ b/MSStore.CLI.UnitTests/TestData/PWAProject/pwaAppInfo.json @@ -0,0 +1,4 @@ +{ + "appId": "9PXXXXXXXXXX", + "uri": "https://www.microsoft.com" +} diff --git a/MSStore.CLI/Commands/Apps/GetCommand.cs b/MSStore.CLI/Commands/Apps/GetCommand.cs index 5fc7720..7de740e 100644 --- a/MSStore.CLI/Commands/Apps/GetCommand.cs +++ b/MSStore.CLI/Commands/Apps/GetCommand.cs @@ -94,7 +94,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio } else { - AnsiConsole.WriteLine(JsonSerializer.Serialize(app, app.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(app, app.GetType(), SourceGenerationContext.GetCustom(true))); return await _telemetryClient.TrackCommandEventAsync(0, ct); } } diff --git a/MSStore.CLI/Commands/Flights/CreateCommand.cs b/MSStore.CLI/Commands/Flights/CreateCommand.cs index e2ac23d..1aea410 100644 --- a/MSStore.CLI/Commands/Flights/CreateCommand.cs +++ b/MSStore.CLI/Commands/Flights/CreateCommand.cs @@ -111,7 +111,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio if (flight != null) { - AnsiConsole.WriteLine(JsonSerializer.Serialize(flight, SourceGenerationContext.GetCustom(true).DevCenterFlight)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flight, SourceGenerationContext.GetCustom(true).DevCenterFlight)); return await _telemetryClient.TrackCommandEventAsync(0, ct); } diff --git a/MSStore.CLI/Commands/Flights/GetCommand.cs b/MSStore.CLI/Commands/Flights/GetCommand.cs index e0d4682..3a8966a 100644 --- a/MSStore.CLI/Commands/Flights/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/GetCommand.cs @@ -75,7 +75,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio if (flight != null) { - AnsiConsole.WriteLine(JsonSerializer.Serialize(flight, SourceGenerationContext.GetCustom(true).DevCenterFlight)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flight, SourceGenerationContext.GetCustom(true).DevCenterFlight)); return await _telemetryClient.TrackCommandEventAsync(0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs b/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs index 8f1ebd5..c269834 100644 --- a/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs @@ -88,7 +88,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs index e07e642..48a4bf7 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs @@ -101,7 +101,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs index d0e3625..09372a0 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs @@ -101,7 +101,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs index b7316d4..e72311c 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs @@ -101,7 +101,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs index 767bd01..332c74f 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs @@ -119,7 +119,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs b/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs index 67e0e90..89c9918 100644 --- a/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs @@ -128,7 +128,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(updatedFlightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); + StandardOutput.WriteLine(JsonSerializer.Serialize(updatedFlightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/PackageCommand.cs b/MSStore.CLI/Commands/PackageCommand.cs index 09a4403..17762c2 100644 --- a/MSStore.CLI/Commands/PackageCommand.cs +++ b/MSStore.CLI/Commands/PackageCommand.cs @@ -100,7 +100,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio if (returnCode == 0 && outputDirectory != null) { _ansiConsole.WriteLine($"The packaged app is here:"); - AnsiConsole.MarkupLine($"[green bold]{outputDirectory}[/]"); + StandardOutput.WriteLine(outputDirectory.FullName); } return await _telemetryClient.TrackCommandEventAsync(returnCode, props, ct); diff --git a/MSStore.CLI/Commands/Submission/GetCommand.cs b/MSStore.CLI/Commands/Submission/GetCommand.cs index 007f1a6..e8f7a3a 100644 --- a/MSStore.CLI/Commands/Submission/GetCommand.cs +++ b/MSStore.CLI/Commands/Submission/GetCommand.cs @@ -115,7 +115,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(submission, submission.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(submission, submission.GetType(), SourceGenerationContext.GetCustom(true))); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs b/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs index d88748b..06fb98a 100644 --- a/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs +++ b/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs @@ -95,7 +95,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio var baseListing = listing.Value?.BaseListing; if (baseListing != null) { - AnsiConsole.WriteLine(JsonSerializer.Serialize(baseListing, baseListing.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(baseListing, baseListing.GetType(), SourceGenerationContext.GetCustom(true))); } } @@ -103,7 +103,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio } else if (ret is ListingAssetsResponse draft) { - AnsiConsole.WriteLine(JsonSerializer.Serialize(draft, draft.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(draft, draft.GetType(), SourceGenerationContext.GetCustom(true))); return await _telemetryClient.TrackCommandEventAsync(0, ct); } diff --git a/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs index b7c8763..57812a1 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs @@ -99,7 +99,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs index d633309..984a5d3 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs @@ -109,7 +109,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs index 3ea9bc7..0c6f4c8 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs @@ -99,7 +99,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs index 40671cb..71fdcd7 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs @@ -117,7 +117,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); + StandardOutput.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/StatusCommand.cs b/MSStore.CLI/Commands/Submission/StatusCommand.cs index 8799c63..e0258ad 100644 --- a/MSStore.CLI/Commands/Submission/StatusCommand.cs +++ b/MSStore.CLI/Commands/Submission/StatusCommand.cs @@ -85,7 +85,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio } else { - AnsiConsole.WriteLine(JsonSerializer.Serialize(status, status.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(status, status.GetType(), SourceGenerationContext.GetCustom(true))); } return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); diff --git a/MSStore.CLI/Commands/Submission/UpdateCommand.cs b/MSStore.CLI/Commands/Submission/UpdateCommand.cs index 26adcdb..2a8fe05 100644 --- a/MSStore.CLI/Commands/Submission/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Submission/UpdateCommand.cs @@ -165,7 +165,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs b/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs index c89fa6e..b1c87c2 100644 --- a/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs +++ b/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs @@ -87,7 +87,7 @@ public override async Task InvokeAsync(ParseResult parseResult, Cancellatio return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - AnsiConsole.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); + StandardOutput.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } diff --git a/MSStore.CLI/Helpers/IProjectConfiguratorExtensions.cs b/MSStore.CLI/Helpers/IProjectConfiguratorExtensions.cs index 794b7d3..8f55dda 100644 --- a/MSStore.CLI/Helpers/IProjectConfiguratorExtensions.cs +++ b/MSStore.CLI/Helpers/IProjectConfiguratorExtensions.cs @@ -21,10 +21,10 @@ internal static async Task ValidateImagesAsync(this IProjectConfigurator configu var defaultImages = ProjectImagesHelper.GetDefaultImagesUsedByApp(ansiConsole, appImages, projectSpecificDefaultImages, imageConverter, logger); if (defaultImages.Count > 0) { - AnsiConsole.MarkupLine($"[bold yellow]The following images are using the default values and should be updated:[/]"); + ansiConsole.MarkupLine($"[bold yellow]The following images are using the default values and should be updated:[/]"); foreach (var image in defaultImages) { - AnsiConsole.MarkupLine($"[bold yellow] {image}[/]"); + ansiConsole.MarkupLine($"[bold yellow] {image}[/]"); } } } diff --git a/MSStore.CLI/Helpers/StandardOutput.cs b/MSStore.CLI/Helpers/StandardOutput.cs new file mode 100644 index 0000000..1de2162 --- /dev/null +++ b/MSStore.CLI/Helpers/StandardOutput.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace MSStore.CLI.Helpers +{ + internal static class StandardOutput + { + /// + /// Writes machine-readable output (JSON payloads, paths) directly to stdout. + /// + /// The text to write. + /// + /// This deliberately bypasses Spectre.Console's : its renderer + /// word-wraps at the console width (falling back to 80 columns when stdout is redirected), which injects + /// raw newline characters inside JSON string values and produces invalid JSON. + /// + public static void WriteLine(string value) + { + Console.Out.WriteLine(value); + Console.Out.Flush(); + } + } +} diff --git a/MSStore.CLI/ProjectConfigurators/WinUIProjectConfigurator.cs b/MSStore.CLI/ProjectConfigurators/WinUIProjectConfigurator.cs index 2f14099..0bae67d 100644 --- a/MSStore.CLI/ProjectConfigurators/WinUIProjectConfigurator.cs +++ b/MSStore.CLI/ProjectConfigurators/WinUIProjectConfigurator.cs @@ -71,7 +71,7 @@ public override async Task CanConfigureAsync(string pathOrUrl, Cancellatio version = AppXManifestManager.UpdateManifestVersion(manifestFile.FullName, version); - var bundleUploadFile = await AnsiConsole.Status().StartAsync("Building MSIX...", async ctx => + var bundleUploadFile = await ErrorAnsiConsole.Status().StartAsync("Building MSIX...", async ctx => { try {