From e741605c42e1f61dd195a8c55e97c79841ed889b Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 16 Feb 2024 22:56:36 +0500 Subject: [PATCH] Add support for JSON serialization (#507) --- doc/new-locale.md | 1 + doc/new.md | 1 + doc/show.md | 1 + doc/update-locale.md | 1 + doc/update.md | 1 + src/WingetCreateCLI/Commands/BaseCommand.cs | 63 ++-- src/WingetCreateCLI/Commands/NewCommand.cs | 8 +- .../Commands/NewLocaleCommand.cs | 9 +- src/WingetCreateCLI/Commands/ShowCommand.cs | 15 +- src/WingetCreateCLI/Commands/UpdateCommand.cs | 19 +- .../Commands/UpdateLocaleCommand.cs | 7 + src/WingetCreateCLI/Models/SettingsModel.cs | 27 ++ src/WingetCreateCLI/Program.cs | 22 ++ .../Properties/Resources.Designer.cs | 27 ++ src/WingetCreateCLI/Properties/Resources.resx | 9 + .../Schemas/settings.schema.0.1.json | 18 ++ src/WingetCreateCLI/UserSettings.cs | 15 + src/WingetCreateCore/Common/GitHub.cs | 18 +- src/WingetCreateCore/Common/Serialization.cs | 282 ++++-------------- .../Interfaces/IManifestSerializer.cs | 41 +++ src/WingetCreateCore/Models/Manifests.cs | 13 +- .../Serializers/JsonSerializer.cs | 102 +++++++ .../Serializers/YamlSerializer.cs | 260 ++++++++++++++++ .../WingetCreateTests/E2ETests/E2ETests.cs | 38 ++- .../Multifile.Json.MsixTest.installer.json | 21 ++ .../Multifile.Json.MsixTest.json | 7 + .../Multifile.Json.MsixTest.locale.en-GB.json | 11 + .../Multifile.Json.MsixTest.locale.en-US.json | 11 + .../Multifile.Yaml.MsixTest.installer.yaml} | 2 +- ...Multifile.Yaml.MsixTest.locale.en-GB.yaml} | 2 +- ...Multifile.Yaml.MsixTest.locale.en-US.yaml} | 2 +- .../Multifile.Yaml.MsixTest.yaml} | 2 +- .../TestPublisher.FullJsonSingleton1_1.json | 111 +++++++ .../TestPublisher.FullJsonSingleton1_2.json | 127 ++++++++ .../TestPublisher.FullJsonSingleton1_4.json | 144 +++++++++ .../TestPublisher.FullJsonSingleton1_5.json | 153 ++++++++++ ...> TestPublisher.FullYamlSingleton1_1.yaml} | 2 +- ...> TestPublisher.FullYamlSingleton1_2.yaml} | 2 +- ...> TestPublisher.FullYamlSingleton1_4.yaml} | 2 +- ...> TestPublisher.FullYamlSingleton1_5.yaml} | 2 +- .../WingetCreateE2E.Json.ExeTest.json | 34 +++ .../WingetCreateE2E.Json.MsiTest.json | 31 ++ .../WingetCreateE2E.Json.PortableTest.json | 33 ++ .../WingetCreateE2E.Json.ZipTest.json | 36 +++ ...yaml => WingetCreateE2E.Yaml.ExeTest.yaml} | 2 +- ...yaml => WingetCreateE2E.Yaml.MsiTest.yaml} | 2 +- ...=> WingetCreateE2E.Yaml.PortableTest.yaml} | 2 +- ...yaml => WingetCreateE2E.Yaml.ZipTest.yaml} | 2 +- .../WingetCreateTests/TestConstants.cs | 138 +++++++-- .../UnitTests/CharacterValidationTests.cs | 62 ++-- .../UnitTests/GitHubTests.cs | 2 + .../UnitTests/LocaleCommandsTests.cs | 2 +- .../UnitTests/SettingsCommandTests.cs | 3 + .../UnitTests/UpdateCommandTests.cs | 88 ++++-- .../WingetCreateTests.csproj | 60 +++- 55 files changed, 1720 insertions(+), 376 deletions(-) create mode 100644 src/WingetCreateCore/Interfaces/IManifestSerializer.cs create mode 100644 src/WingetCreateCore/Serializers/JsonSerializer.cs create mode 100644 src/WingetCreateCore/Serializers/YamlSerializer.cs create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.installer.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-GB.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-US.json rename src/WingetCreateTests/WingetCreateTests/Resources/{Multifile.MsixTest/Multifile.MsixTest.installer.yaml => Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.installer.yaml} (91%) rename src/WingetCreateTests/WingetCreateTests/Resources/{Multifile.MsixTest/Multifile.MsixTest.locale.en-GB.yaml => Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-GB.yaml} (82%) rename src/WingetCreateTests/WingetCreateTests/Resources/{Multifile.MsixTest/Multifile.MsixTest.locale.en-US.yaml => Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-US.yaml} (82%) rename src/WingetCreateTests/WingetCreateTests/Resources/{Multifile.MsixTest/Multifile.MsixTest.yaml => Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.yaml} (65%) create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_1.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_2.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_4.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_5.json rename src/WingetCreateTests/WingetCreateTests/Resources/{TestPublisher.FullSingleton1_1.yaml => TestPublisher.FullYamlSingleton1_1.yaml} (97%) rename src/WingetCreateTests/WingetCreateTests/Resources/{TestPublisher.FullSingleton1_2.yaml => TestPublisher.FullYamlSingleton1_2.yaml} (97%) rename src/WingetCreateTests/WingetCreateTests/Resources/{TestPublisher.FullSingleton1_4.yaml => TestPublisher.FullYamlSingleton1_4.yaml} (98%) rename src/WingetCreateTests/WingetCreateTests/Resources/{TestPublisher.FullSingleton1_5.yaml => TestPublisher.FullYamlSingleton1_5.yaml} (98%) create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ExeTest.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.MsiTest.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.PortableTest.json create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ZipTest.json rename src/WingetCreateTests/WingetCreateTests/Resources/{WingetCreateE2E.ExeTest.yaml => WingetCreateE2E.Yaml.ExeTest.yaml} (93%) rename src/WingetCreateTests/WingetCreateTests/Resources/{WingetCreateE2E.MsiTest.yaml => WingetCreateE2E.Yaml.MsiTest.yaml} (92%) rename src/WingetCreateTests/WingetCreateTests/Resources/{WingetCreateE2E.PortableTest.yaml => WingetCreateE2E.Yaml.PortableTest.yaml} (92%) rename src/WingetCreateTests/WingetCreateTests/Resources/{WingetCreateE2E.ZipTest.yaml => WingetCreateE2E.Yaml.ZipTest.yaml} (93%) diff --git a/doc/new-locale.md b/doc/new-locale.md index 9d50b5a1..e6d09e67 100644 --- a/doc/new-locale.md +++ b/doc/new-locale.md @@ -27,6 +27,7 @@ The following arguments are available: | **-l, --locale** | The package locale to create a new manifest for. If not provided, the tool will prompt you for this value. | **-r, --reference-locale** | Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used. | **-o, --out** | The output directory where the newly created manifests will be saved locally. +| **-f,--format** | Output format of the manifest. Default is "yaml". | | **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | | **-?, --help** | Gets additional help on this command | diff --git a/doc/new.md b/doc/new.md index 96f69a79..56993b21 100644 --- a/doc/new.md +++ b/doc/new.md @@ -27,6 +27,7 @@ The following arguments are available: | Argument | Description | |--------------|-------------| | **-o,--out** | The output directory where the newly created manifests will be saved locally | +| **-f,--format** | Output format of the manifest. Default is "yaml". | | **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | | **-?, --help** | Gets additional help on this command | diff --git a/doc/show.md b/doc/show.md index 6317be89..2265ed76 100644 --- a/doc/show.md +++ b/doc/show.md @@ -27,6 +27,7 @@ The following arguments are available: | **-d, --defaultlocale-manifest** | Switch to display the default locale manifest. | **-l, --locale-manifests** | Switch to display all locale manifests. | **--version-manifest** | Switch to display the version manifest. +| **-f,--format** | Output format of the manifest. Default is "yaml". | | **-t, --token** | GitHub personal access token used for authenticated access to the GitHub API. It is recommended to provide a token to get a higher [API rate limit](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). | **-?, --help** | Gets additional help on this command. | diff --git a/doc/update-locale.md b/doc/update-locale.md index 5822f02f..3fc33118 100644 --- a/doc/update-locale.md +++ b/doc/update-locale.md @@ -26,6 +26,7 @@ The following arguments are available: | **-v, --version** | The version of the package to update the locale for. Default is the latest version. | **-l, --locale** | The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from. | **-o, --out** | The output directory where the newly created manifests will be saved locally. +| **-f,--format** | Output format of the manifest. Default is "yaml". | | **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | | **-?, --help** | Gets additional help on this command | diff --git a/doc/update.md b/doc/update.md index 3b937bc0..c8f0210a 100644 --- a/doc/update.md +++ b/doc/update.md @@ -76,6 +76,7 @@ The following arguments are available: | **-s, --submit** | Boolean value for submitting to the Windows Package Manager repo. If true, updated manifest will be submitted directly using the provided GitHub Token | **-r, --replace** | Boolean value for replacing an existing manifest from the Windows Package Manager repo. Optionally provide a version or else the latest version will be replaced. Default is false. | **-p, --prtitle** | The title of the pull request submitted to GitHub. +| **-f,--format** | Output format of the manifest. Default is "yaml". | | **-t, --token** | GitHub personal access token used for direct submission to the Windows Package Manager repo. If no token is provided, tool will prompt for GitHub login credentials. | **-?, --help** | Gets additional help on this command. | diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 96182a7c..c6548542 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.WingetCreateCLI.Commands using System.Reflection; using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; @@ -55,11 +56,21 @@ public abstract class BaseCommand /// private static readonly Dictionary DownloadedInstallers = new(); + /// + /// Gets the extension of the output manifest files. + /// + public static string Extension => Serialization.ManifestSerializer.AssociatedFileExtension; + /// /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. /// public virtual string GitHubToken { get; set; } + /// + /// Gets or sets the format of the output manifest files and preview. + /// + public virtual ManifestFormat Format { get; set; } = UserSettings.ManifestFormat; + /// /// Gets or sets the winget repo owner to use. /// @@ -116,7 +127,6 @@ public async Task LoadGitHubClient(bool requireToken = false) { Logger.Trace("No token parameter, reading cached token"); this.GitHubToken = GitHubOAuth.ReadTokenCache(); - if (string.IsNullOrEmpty(this.GitHubToken)) { if (requireToken) @@ -184,18 +194,18 @@ protected static string SaveManifestDirToLocalPath( fullDirPath = Path.Combine(outputDir, manifestDir); } - string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest); - string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest); - string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest); + string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest, Extension); + string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest, Extension); + string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest, Extension); - File.WriteAllText(Path.Combine(fullDirPath, versionManifestFileName), versionManifest.ToYaml()); - File.WriteAllText(Path.Combine(fullDirPath, installerManifestFileName), installerManifest.ToYaml()); - File.WriteAllText(Path.Combine(fullDirPath, defaultLocaleManifestFileName), defaultLocaleManifest.ToYaml()); + File.WriteAllText(Path.Combine(fullDirPath, versionManifestFileName), versionManifest.ToManifestString()); + File.WriteAllText(Path.Combine(fullDirPath, installerManifestFileName), installerManifest.ToManifestString()); + File.WriteAllText(Path.Combine(fullDirPath, defaultLocaleManifestFileName), defaultLocaleManifest.ToManifestString()); foreach (LocaleManifest localeManifest in localeManifests) { - string localeManifestFileName = Manifests.GetFileName(localeManifest); - File.WriteAllText(Path.Combine(fullDirPath, localeManifestFileName), localeManifest.ToYaml()); + string localeManifestFileName = Manifests.GetFileName(localeManifest, Extension); + File.WriteAllText(Path.Combine(fullDirPath, localeManifestFileName), localeManifest.ToManifestString()); } Console.WriteLine(); @@ -212,21 +222,21 @@ protected static string SaveManifestDirToLocalPath( /// A boolean value indicating whether validation of the manifests was successful. protected static bool ValidateManifestsInTempDir(Manifests manifests) { - string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest); - string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest); - string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest); + string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest, Extension); + string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest, Extension); + string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest, Extension); string randomDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(randomDirPath); - File.WriteAllText(Path.Combine(randomDirPath, versionManifestFileName), manifests.VersionManifest.ToYaml()); - File.WriteAllText(Path.Combine(randomDirPath, installerManifestFileName), manifests.InstallerManifest.ToYaml()); - File.WriteAllText(Path.Combine(randomDirPath, defaultLocaleManifestFileName), manifests.DefaultLocaleManifest.ToYaml()); + File.WriteAllText(Path.Combine(randomDirPath, versionManifestFileName), manifests.VersionManifest.ToManifestString()); + File.WriteAllText(Path.Combine(randomDirPath, installerManifestFileName), manifests.InstallerManifest.ToManifestString()); + File.WriteAllText(Path.Combine(randomDirPath, defaultLocaleManifestFileName), manifests.DefaultLocaleManifest.ToManifestString()); foreach (LocaleManifest localeManifest in manifests.LocaleManifests) { - string localeManifestFileName = Manifests.GetFileName(localeManifest); - File.WriteAllText(Path.Combine(randomDirPath, localeManifestFileName), localeManifest.ToYaml()); + string localeManifestFileName = Manifests.GetFileName(localeManifest, Extension); + File.WriteAllText(Path.Combine(randomDirPath, localeManifestFileName), localeManifest.ToManifestString()); } bool result = ValidateManifest(randomDirPath); @@ -403,11 +413,11 @@ protected static void DisplayManifestPreview(Manifests manifests) { Logger.Debug(Resources.GenerateManifestPreview_Message); Logger.Info(Resources.VersionManifestPreview_Message); - Console.WriteLine(manifests.VersionManifest.ToYaml()); + Console.WriteLine(manifests.VersionManifest.ToManifestString()); Logger.Info(Resources.InstallerManifestPreview_Message); - Console.WriteLine(manifests.InstallerManifest.ToYaml()); + Console.WriteLine(manifests.InstallerManifest.ToManifestString()); Logger.Info(Resources.DefaultLocaleManifestPreview_Message); - Console.WriteLine(manifests.DefaultLocaleManifest.ToYaml()); + Console.WriteLine(manifests.DefaultLocaleManifest.ToManifestString()); } /// @@ -569,7 +579,7 @@ protected static void PromptOptionalProperties(T manifest, List optio protected static void DisplayDefaultLocaleManifest(DefaultLocaleManifest defaultLocaleManifest) { Logger.InfoLocalized(nameof(Resources.DefaultLocaleManifest_Message), defaultLocaleManifest.PackageLocale); - Console.WriteLine(defaultLocaleManifest.ToYaml(true)); + Console.WriteLine(defaultLocaleManifest.ToManifestString(true)); } /// @@ -581,7 +591,7 @@ protected static void DisplayLocaleManifests(List localeManifest foreach (var localeManifest in localeManifests) { Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); - Console.WriteLine(localeManifest.ToYaml(true)); + Console.WriteLine(localeManifest.ToManifestString(true)); } } @@ -710,6 +720,15 @@ protected async Task CheckGitHubTokenAndSetClient() /// A representing the success of the asynchronous operation. protected async Task GitHubSubmitManifests(Manifests manifests, string prTitle = null, bool shouldReplace = false, string replaceVersion = null) { + // Community repo only supports yaml submissions. + if (this.WingetRepo == DefaultWingetRepo && + this.WingetRepoOwner == DefaultWingetRepoOwner && + this.Format != ManifestFormat.Yaml) + { + Logger.ErrorLocalized(nameof(Resources.FormatNotSupportedForDefaultRepo_Error)); + return false; + } + if (string.IsNullOrEmpty(this.GitHubToken)) { Logger.WarnLocalized(nameof(Resources.NoTokenProvided_Message)); diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index 8393607d..fd5734d1 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.WingetCreateCLI.Commands using CommandLine; using CommandLine.Text; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; @@ -24,7 +25,6 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Version; - using Newtonsoft.Json; using Sharprompt; /// @@ -74,6 +74,12 @@ public static IEnumerable Examples [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] public string OutputDir { get; set; } + /// + /// Gets or sets the format of the output manifest files. + /// + [Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))] + public override ManifestFormat Format { get => base.Format; set => base.Format = value; } + /// /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. /// diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 98667ab0..6a25119a 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.WingetCreateCLI.Commands using CommandLine; using CommandLine.Text; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; @@ -85,6 +86,12 @@ public static IEnumerable Examples [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] public string OutputDir { get; set; } + /// + /// Gets or sets the format of the output manifest files. + /// + [Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))] + public override ManifestFormat Format { get => base.Format; set => base.Format = value; } + /// /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. /// @@ -329,7 +336,7 @@ private void DisplayGeneratedLocales(List newLocales) foreach (var localeManifest in newLocales) { Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); - Console.WriteLine(localeManifest.ToYaml(true)); + Console.WriteLine(localeManifest.ToManifestString(true)); } } } diff --git a/src/WingetCreateCLI/Commands/ShowCommand.cs b/src/WingetCreateCLI/Commands/ShowCommand.cs index 8715ee48..555f68ec 100644 --- a/src/WingetCreateCLI/Commands/ShowCommand.cs +++ b/src/WingetCreateCLI/Commands/ShowCommand.cs @@ -9,14 +9,13 @@ namespace Microsoft.WingetCreateCLI.Commands using CommandLine; using CommandLine.Text; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; using Microsoft.WingetCreateCore; using Microsoft.WingetCreateCore.Models; - using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; - using Microsoft.WingetCreateCore.Models.Locale; using Microsoft.WingetCreateCore.Models.Singleton; using Microsoft.WingetCreateCore.Models.Version; @@ -76,6 +75,12 @@ public static IEnumerable Examples [Option("version-manifest", Required = false, HelpText = "VersionManifest_HelpText", ResourceType = typeof(Resources))] public bool ShowVersionManifest { get; set; } + /// + /// Gets or sets the format of the output manifest preview. + /// + [Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))] + public override ManifestFormat Format { get => base.Format; set => base.Format = value; } + /// /// Gets or sets the GitHub token for authenticated access to GitHub API. /// @@ -143,19 +148,19 @@ private static void ShowAllManifests(Manifests manifests) private static void DisplayInstallerManifest(InstallerManifest installerManifest) { Logger.InfoLocalized(nameof(Resources.InstallerManifest_Message)); - Console.WriteLine(installerManifest.ToYaml(true)); + Console.WriteLine(installerManifest.ToManifestString(true)); } private static void DisplayVersionManifest(VersionManifest versionManifest) { Logger.InfoLocalized(nameof(Resources.VersionManifest_Message)); - Console.WriteLine(versionManifest.ToYaml(true)); + Console.WriteLine(versionManifest.ToManifestString(true)); } private static void DisplaySingletonManifest(SingletonManifest singletonManifest) { Logger.InfoLocalized(nameof(Resources.SingletonManifest_Message)); - Console.WriteLine(singletonManifest.ToYaml(true)); + Console.WriteLine(singletonManifest.ToManifestString(true)); } private void ParseArgumentsAndShowManifest(Manifests manifests) diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index 75d2658c..a182ad47 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -13,6 +13,7 @@ namespace Microsoft.WingetCreateCLI.Commands using CommandLine; using CommandLine.Text; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; @@ -96,6 +97,12 @@ public static IEnumerable Examples [Option('i', "interactive", Required = false, HelpText = "InteractiveUpdate_HelpText", ResourceType = typeof(Resources))] public bool Interactive { get; set; } + /// + /// Gets or sets the format of the output manifest files. + /// + [Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))] + public override ManifestFormat Format { get => base.Format; set => base.Format = value; } + /// /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. /// @@ -590,20 +597,20 @@ private static bool VerifyUpdatedInstallerHash(Manifests oldManifest, InstallerM private static void DisplayManifestsAsMenuSelection(Manifests manifests) { Console.Clear(); - string versionFileName = Manifests.GetFileName(manifests.VersionManifest); - string installerFileName = Manifests.GetFileName(manifests.InstallerManifest); + string versionFileName = Manifests.GetFileName(manifests.VersionManifest, Extension); + string installerFileName = Manifests.GetFileName(manifests.InstallerManifest, Extension); string versionManifestMenuItem = $"{manifests.VersionManifest.ManifestType.ToUpper()}: " + versionFileName; string installerManifestMenuItem = $"{manifests.InstallerManifest.ManifestType.ToUpper()}: " + installerFileName; while (true) { // Need to update locale manifest file name each time as PackageLocale can change - string defaultLocaleMenuItem = $"{manifests.DefaultLocaleManifest.ManifestType.ToUpper()}: " + Manifests.GetFileName(manifests.DefaultLocaleManifest); + string defaultLocaleMenuItem = $"{manifests.DefaultLocaleManifest.ManifestType.ToUpper()}: " + Manifests.GetFileName(manifests.DefaultLocaleManifest, Extension); List selectionList = new List { versionManifestMenuItem, installerManifestMenuItem, defaultLocaleMenuItem }; Dictionary localeManifestMap = new Dictionary(); foreach (LocaleManifest localeManifest in manifests.LocaleManifests) { - string localeManifestFileName = $"{localeManifest.ManifestType.ToUpper()}: " + Manifests.GetFileName(localeManifest); + string localeManifestFileName = $"{localeManifest.ManifestType.ToUpper()}: " + Manifests.GetFileName(localeManifest, Extension); localeManifestMap.Add(localeManifestFileName, localeManifest); selectionList.Add(localeManifestFileName); } @@ -622,7 +629,7 @@ private static void DisplayManifestsAsMenuSelection(Manifests manifests) } else if (selectedItem == defaultLocaleMenuItem) { - PromptHelper.PromptPropertiesWithMenu(manifests.DefaultLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(manifests.DefaultLocaleManifest)); + PromptHelper.PromptPropertiesWithMenu(manifests.DefaultLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(manifests.DefaultLocaleManifest, Extension)); } else if (selectedItem == Resources.Done_MenuItem) { @@ -631,7 +638,7 @@ private static void DisplayManifestsAsMenuSelection(Manifests manifests) else { var selectedLocaleManifest = localeManifestMap[selectedItem]; - PromptHelper.PromptPropertiesWithMenu(selectedLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(selectedLocaleManifest)); + PromptHelper.PromptPropertiesWithMenu(selectedLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(selectedLocaleManifest, Extension)); } } } diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs index 54bd1bcd..4d9a0e8f 100644 --- a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -12,6 +12,7 @@ namespace Microsoft.WingetCreateCLI.Commands using CommandLine; using CommandLine.Text; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; @@ -65,6 +66,12 @@ public static IEnumerable Examples [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] public string OutputDir { get; set; } + /// + /// Gets or sets the format of the output manifest files. + /// + [Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))] + public override ManifestFormat Format { get => base.Format; set => base.Format = value; } + /// /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. /// diff --git a/src/WingetCreateCLI/Models/SettingsModel.cs b/src/WingetCreateCLI/Models/SettingsModel.cs index c9b84efd..c07ebf37 100644 --- a/src/WingetCreateCLI/Models/SettingsModel.cs +++ b/src/WingetCreateCLI/Models/SettingsModel.cs @@ -48,6 +48,18 @@ public partial class WindowsPackageManagerRepository public string Name { get; set; } = "winget-pkgs"; + } + + /// Output manifest settings + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Manifest + { + /// Specifies the format of the manifest file + [Newtonsoft.Json.JsonProperty("format", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public ManifestFormat Format { get; set; } = Microsoft.WingetCreateCLI.Models.Settings.ManifestFormat.Yaml; + + } /// Visual settings @@ -80,10 +92,25 @@ public partial class SettingsManifest [System.ComponentModel.DataAnnotations.Required] public WindowsPackageManagerRepository WindowsPackageManagerRepository { get; set; } = new WindowsPackageManagerRepository(); + [Newtonsoft.Json.JsonProperty("Manifest", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public Manifest Manifest { get; set; } = new Manifest(); + [Newtonsoft.Json.JsonProperty("Visual", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] [System.ComponentModel.DataAnnotations.Required] public Visual Visual { get; set; } = new Visual(); } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v11.0.0.0)")] + public enum ManifestFormat + { + [System.Runtime.Serialization.EnumMember(Value = @"yaml")] + Yaml = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"json")] + Json = 1, + + } } \ No newline at end of file diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 2dae8b96..85f4d453 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -37,6 +37,7 @@ private static async Task Main(string[] args) { config.HelpWriter = null; config.CaseSensitive = false; + config.CaseInsensitiveEnumValues = true; }); var types = new Type[] @@ -97,11 +98,32 @@ private static async Task Main(string[] args) } } + try + { + Serialization.SetManifestSerializer(command.Format.ToString()); + } + catch (ArgumentException ex) + { + Logger.ErrorLocalized(nameof(Resources.InvalidManifestFormat_ErrorMessage)); + TelemetryManager.Log.WriteEvent(new GlobalExceptionEvent + { + ExceptionType = ex.GetType().ToString(), + ErrorMessage = ex.Message, + StackTrace = ex.StackTrace, + }); + return 1; + } + try { WingetCreateCore.Serialization.ProducedBy = string.Join(" ", Constants.ProgramName, Utils.GetEntryAssemblyVersion()); return await command.Execute() ? 0 : 1; } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidManifestFormat_ErrorMessage)); + return 1; + } catch (Exception ex) { TelemetryManager.Log.WriteEvent(new GlobalExceptionEvent diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 5aada1c7..092129ec 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1023,6 +1023,15 @@ public static string FilterMenuItems_Message { } } + /// + /// Looks up a localized string similar to Format not supported. The Windows Package Manager Community repository accepts only "yaml" manifest submissions.. + /// + public static string FormatNotSupportedForDefaultRepo_Error { + get { + return ResourceManager.GetString("FormatNotSupportedForDefaultRepo_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Generating a preview of your manifests.... /// @@ -1491,6 +1500,15 @@ public static string InvalidLocale_ErrorMessage { } } + /// + /// Looks up a localized string similar to The provided manifest format is invalid.. + /// + public static string InvalidManifestFormat_ErrorMessage { + get { + return ResourceManager.GetString("InvalidManifestFormat_ErrorMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid token provided, please generate a new GitHub token and try again.. /// @@ -1680,6 +1698,15 @@ public static string ManifestDocumentation_HelpText { } } + /// + /// Looks up a localized string similar to Output format of the manifest. Default is "yaml".. + /// + public static string ManifestFormat_HelpText { + get { + return ResourceManager.GetString("ManifestFormat_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Manifest saved to {0}. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 41fc1445..94069e0a 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -1342,6 +1342,15 @@ The replace version cannot be equal to the submit version. + + The provided manifest format is invalid. + + + Output format of the manifest. Default is "yaml". + + + Format not supported. The Windows Package Manager Community repository accepts only "yaml" manifest submissions. + Clear the cached GitHub token diff --git a/src/WingetCreateCLI/Schemas/settings.schema.0.1.json b/src/WingetCreateCLI/Schemas/settings.schema.0.1.json index cd72df3e..3349f5b7 100644 --- a/src/WingetCreateCLI/Schemas/settings.schema.0.1.json +++ b/src/WingetCreateCLI/Schemas/settings.schema.0.1.json @@ -50,6 +50,22 @@ }, "additionalProperties": false }, + "Manifest": { + "description": "Output manifest settings", + "type": "object", + "properties": { + "format": { + "description": "Specifies the format of the manifest file", + "type": "string", + "default": "yaml", + "enum": [ + "yaml", + "json" + ] + } + }, + "additionalProperties": false + }, "Visual": { "description": "Visual settings", "type": "object", @@ -73,12 +89,14 @@ "Telemetry": { "$ref": "#/definitions/Telemetry" }, "CleanUp": { "$ref": "#/definitions/CleanUp" }, "WindowsPackageManagerRepository": { "$ref": "#/definitions/WindowsPackageManagerRepository" }, + "Manifest": { "$ref": "#/definitions/Manifest" }, "Visual": { "$ref": "#/definitions/Visual" } }, "required": [ "Telemetry", "CleanUp", "WindowsPackageManagerRepository", + "Manifest", "Visual" ], "additionalProperties": false diff --git a/src/WingetCreateCLI/UserSettings.cs b/src/WingetCreateCLI/UserSettings.cs index c717bd7d..0ff37970 100644 --- a/src/WingetCreateCLI/UserSettings.cs +++ b/src/WingetCreateCLI/UserSettings.cs @@ -105,6 +105,20 @@ public static string WindowsPackageManagerRepositoryName } } + /// + /// Gets or sets the format of the manifest file. + /// + public static ManifestFormat ManifestFormat + { + get => Settings.Manifest.Format; + + set + { + Settings.Manifest.Format = value; + SaveSettings(); + } + } + /// /// Gets or sets a value indicating whether paths displayed on the console are substituted with environment variables. /// @@ -212,6 +226,7 @@ private static void LoadSettings() Telemetry = new Models.Settings.Telemetry(), CleanUp = new CleanUp(), WindowsPackageManagerRepository = new WindowsPackageManagerRepository(), + Manifest = new Manifest(), Visual = new Visual(), }; } diff --git a/src/WingetCreateCore/Common/GitHub.cs b/src/WingetCreateCore/Common/GitHub.cs index 831a22c3..ddb47267 100644 --- a/src/WingetCreateCore/Common/GitHub.cs +++ b/src/WingetCreateCore/Common/GitHub.cs @@ -82,7 +82,8 @@ public async Task> GetAppVersions() // Microsoft/PowerToys/0.15.2.yaml, or // Microsoft/VisualStudio/Community/16.0.30011.22.yaml string publisher = i.PathTokens[0]; - string version = i.PathTokens[^1].Replace(".yaml", string.Empty, StringComparison.OrdinalIgnoreCase); + string extension = Path.GetExtension(i.Path); + string version = i.PathTokens[^1].Replace(extension, string.Empty, StringComparison.OrdinalIgnoreCase); string app = string.Join('.', i.PathTokens[1..^1]); return new PublisherAppVersion(publisher, app, version, $"{publisher}.{app}", i.Path); }) @@ -97,6 +98,7 @@ public async Task> GetAppVersions() /// Manifest as a string. public async Task> GetManifestContentAsync(string packageId, string version = null) { + List validExtensions = new List { ".yaml", ".json" }; string versionDirectoryPath = await this.GetVersionDirectoryPath(packageId, version); if (string.IsNullOrEmpty(versionDirectoryPath)) @@ -105,7 +107,7 @@ public async Task> GetManifestContentAsync(string packageId, string } var packageContents = (await this.github.Repository.Content.GetAllContents(this.wingetRepoOwner, this.wingetRepo, versionDirectoryPath)) - .Where(c => c.Type != ContentType.Dir && Path.GetExtension(c.Name).EqualsIC(".yaml")); + .Where(c => c.Type != ContentType.Dir && validExtensions.Any(ext => Path.GetExtension(c.Name).EqualsIC(ext))); // If all contents of version directory are directories themselves, user must've provided an invalid packageId. if (!packageContents.Any()) @@ -143,18 +145,18 @@ public Task SubmitPullRequestAsync(Manifests manifests, bool submit { id = manifests.SingletonManifest.PackageIdentifier; version = manifests.SingletonManifest.PackageVersion; - contents.Add(manifests.SingletonManifest.PackageIdentifier, manifests.SingletonManifest.ToYaml()); + contents.Add(manifests.SingletonManifest.PackageIdentifier, manifests.SingletonManifest.ToManifestString()); } else { id = manifests.VersionManifest.PackageIdentifier; version = manifests.VersionManifest.PackageVersion; - contents = manifests.LocaleManifests.ToDictionary(locale => $"{id}.locale.{locale.PackageLocale}", locale => locale.ToYaml()); + contents = manifests.LocaleManifests.ToDictionary(locale => $"{id}.locale.{locale.PackageLocale}", locale => locale.ToManifestString()); - contents.Add(id, manifests.VersionManifest.ToYaml()); - contents.Add($"{id}.installer", manifests.InstallerManifest.ToYaml()); - contents.Add($"{id}.locale.{manifests.DefaultLocaleManifest.PackageLocale}", manifests.DefaultLocaleManifest.ToYaml()); + contents.Add(id, manifests.VersionManifest.ToManifestString()); + contents.Add($"{id}.installer", manifests.InstallerManifest.ToManifestString()); + contents.Add($"{id}.locale.{manifests.DefaultLocaleManifest.PackageLocale}", manifests.DefaultLocaleManifest.ToManifestString()); } return this.SubmitPRAsync(id, version, contents, submitToFork, prTitle, shouldReplace, replaceVersion); @@ -347,7 +349,7 @@ await retryPolicy.ExecuteAsync(async () => foreach (KeyValuePair item in contents) { - string file = $"{appPath}/{item.Key}.yaml"; + string file = $"{appPath}/{item.Key}{Serialization.ManifestSerializer.AssociatedFileExtension}"; nt.Tree.Add(new NewTreeItem { Path = file, Mode = "100644", Type = TreeType.Blob, Content = item.Value }); } diff --git a/src/WingetCreateCore/Common/Serialization.cs b/src/WingetCreateCore/Common/Serialization.cs index a020ac0e..101cb563 100644 --- a/src/WingetCreateCore/Common/Serialization.cs +++ b/src/WingetCreateCore/Common/Serialization.cs @@ -7,62 +7,45 @@ namespace Microsoft.WingetCreateCore using System.Collections.Generic; using System.IO; using System.Linq; - using System.Reflection; - using System.Runtime.Serialization; using System.Text; + using Microsoft.WingetCreateCore.Interfaces; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Locale; using Microsoft.WingetCreateCore.Models.Singleton; using Microsoft.WingetCreateCore.Models.Version; - using Newtonsoft.Json; - using YamlDotNet.Core; - using YamlDotNet.Core.Events; - using YamlDotNet.Serialization; - using YamlDotNet.Serialization.EventEmitters; - using YamlDotNet.Serialization.NamingConventions; - using YamlDotNet.Serialization.TypeInspectors; + using Microsoft.WingetCreateCore.Serializers; /// /// Provides functionality for the serialization of JSON objects to yaml. /// public static class Serialization { + static Serialization() + { + // Default to yaml serializer. + ManifestSerializer = new YamlSerializer(); + } + /// - /// Gets or sets the application that produced the manifest, will be added to comment header. + /// Gets or sets the manifest serializer to be used for serializing output manifest. /// - public static string ProducedBy { get; set; } + public static IManifestSerializer ManifestSerializer { get; set; } /// - /// Helper to build a YAML serializer. + /// Gets implementation types of available in the current app domain. /// - /// ISerializer object. - public static ISerializer CreateSerializer() - { - var serializer = new SerializerBuilder() - .WithQuotingNecessaryStrings() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .WithTypeConverter(new YamlStringEnumConverter()) - .WithEmissionPhaseObjectGraphVisitor(args => new YamlSkipPropertyVisitor(args.InnerVisitor)) - .WithEventEmitter(nextEmitter => new MultilineScalarFlowStyleEmitter(nextEmitter)) - .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull); - return serializer.Build(); - } + public static List AvailableSerializerTypes { get; } = + AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => typeof(IManifestSerializer).IsAssignableFrom(p) && !p.IsInterface) + .ToList(); /// - /// Helper to build a YAML deserializer. + /// Gets or sets the application that produced the manifest, will be added to comment header. /// - /// IDeserializer object. - public static IDeserializer CreateDeserializer() - { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(PascalCaseNamingConvention.Instance) - .WithTypeConverter(new YamlStringEnumConverter()) - .WithTypeInspector(inspector => new AliasTypeInspector(inspector)) - .IgnoreUnmatchedProperties(); - return deserializer.Build(); - } + public static string ProducedBy { get; set; } /// /// Deserialize a stream reader into a Manifest object. @@ -84,67 +67,46 @@ public static T DeserializeFromPath(string filePath) /// Manifest object populated and validated. public static T DeserializeFromString(string value) { - var deserializer = Serialization.CreateDeserializer(); - return deserializer.Deserialize(value); - } - - /// - /// Serialize an object to a YAML string. - /// - /// Object to serialize to YAML. - /// Type of object to serialize. - /// Value to indicate whether to omit the created by header. - /// Manifest in string value. - public static string ToYaml(this T value, bool omitCreatedByHeader = false) - where T : new() - { - var serializer = CreateSerializer(); - string manifestYaml = serializer.Serialize(value); - StringBuilder serialized = new StringBuilder(); - - if (!omitCreatedByHeader) + value = value.Trim(); + if (string.IsNullOrEmpty(value)) { - serialized.AppendLine($"# Created using {ProducedBy}"); + throw new ArgumentException("Manifest is empty."); } - string schemaTemplate = "# yaml-language-server: $schema=https://aka.ms/winget-manifest.{0}.{1}.schema.json"; - - switch (value) + // Early return if the value is already a json object or array. + if ((value.StartsWith("{") && value.EndsWith("}")) || + (value.StartsWith("[") && value.EndsWith("]"))) { - case Models.Singleton.SingletonManifest singletonManifest: - serialized.AppendLine(string.Format(schemaTemplate, singletonManifest.ManifestType, singletonManifest.ManifestVersion)); - break; - case Models.Version.VersionManifest versionManifest: - serialized.AppendLine(string.Format(schemaTemplate, versionManifest.ManifestType, versionManifest.ManifestVersion)); - break; - case Models.Installer.InstallerManifest installerManifest: - serialized.AppendLine(string.Format(schemaTemplate, installerManifest.ManifestType, installerManifest.ManifestVersion)); - break; - case Models.Locale.LocaleManifest localeManifest: - serialized.AppendLine(string.Format(schemaTemplate, localeManifest.ManifestType, localeManifest.ManifestVersion)); - break; - case Models.DefaultLocale.DefaultLocaleManifest defaultLocaleManifest: - serialized.AppendLine(string.Format(schemaTemplate, defaultLocaleManifest.ManifestType, defaultLocaleManifest.ManifestVersion)); - break; + return new JsonSerializer().ManifestDeserialize(value); } - serialized.AppendLine(); - serialized.Append(manifestYaml); + foreach (var serializerType in AvailableSerializerTypes) + { + var serializer = (IManifestSerializer)Activator.CreateInstance(serializerType); + try + { + return serializer.ManifestDeserialize(value); + } + catch (Exception) + { + // Ignore exception and try next serializer. + } + } - return serialized.ToString(); + throw new ArgumentException("Manifest is not in a valid format."); } /// - /// Deserializes yaml to JSON object. + /// Serialize an object to a manifest string. /// - /// yaml to deserialize to JSON object. - /// Manifest as a JSON object. - public static string ConvertYamlToJson(string yaml) + /// Object to serialize. + /// Type of object to serialize. + /// Value to indicate whether to omit the created by header. + /// Manifest in string value. + public static string ToManifestString(this T value, bool omitCreatedByHeader = false) + where T : new() { - var deserializer = CreateDeserializer(); - using var reader = new StringReader(yaml); - var yamlObject = deserializer.Deserialize(reader); - return JsonConvert.SerializeObject(yamlObject); + return ManifestSerializer.ToManifestString(value, omitCreatedByHeader); } /// @@ -154,8 +116,7 @@ public static string ConvertYamlToJson(string yaml) /// Serialized string. public static string Serialize(object value) { - var serializer = CreateSerializer(); - return serializer.Serialize(value); + return ManifestSerializer.ManifestSerialize(value); } /// @@ -199,147 +160,30 @@ public static Manifests DeserializeManifestContents(IEnumerable manifest } /// - /// Removes Byte Order Marker (BOM) prefix if exists in string. - /// - /// String to fix. - /// String without BOM prefix. - private static string RemoveBom(string value) - { - string bomMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - return value.StartsWith(bomMarkUtf8, StringComparison.OrdinalIgnoreCase) ? value.Remove(0, bomMarkUtf8.Length) : value; - } - - /// - /// Custom TypeInspector to priorize properties that have a defined YamlMemberAttribute for custom override. + /// Set the manifest serializer based on the provided value. /// - private class AliasTypeInspector : TypeInspectorSkeleton - { - private readonly ITypeInspector innerTypeDescriptor; - - public AliasTypeInspector(ITypeInspector innerTypeDescriptor) - { - this.innerTypeDescriptor = innerTypeDescriptor; - } - - /// - /// Because certain properties were generated incorrectly, we needed to create custom fields for those properties. - /// Therefore to resolve naming conflicts during deserialization, we prioritize fields that have the YamlMemberAttribute defined - /// as that attribute indicates an override. - /// - public override IEnumerable GetProperties(Type type, object container) - { - var propertyDescriptors = this.innerTypeDescriptor.GetProperties(type, container); - var aliasDefinedProps = type.GetProperties().ToList() - .Where(p => - { - var yamlMemberAttribute = p.GetCustomAttribute(); - return yamlMemberAttribute != null && !string.IsNullOrEmpty(yamlMemberAttribute.Alias); - }) - .ToList(); - - if (aliasDefinedProps.Any()) - { - var overriddenProps = propertyDescriptors - .Where(prop => aliasDefinedProps.Any(aliasProp => - prop.Name == aliasProp.GetCustomAttribute().Alias && // Use Alias name (ex. ReleaseDate) instead of property name (ex. ReleaseDateString). - prop.Type != aliasProp.PropertyType)) - .ToList(); - - // Remove overridden properties from the returned list of deserializable properties. - return propertyDescriptors - .Where(prop => !overriddenProps.Any(overridenProp => - prop.Name == overridenProp.Name && - prop.Type == overridenProp.Type)) - .ToList(); - } - else - { - return propertyDescriptors; - } - } - } - - private class YamlStringEnumConverter : IYamlTypeConverter - { - public bool Accepts(Type type) - { - Type u = Nullable.GetUnderlyingType(type); - return type.IsEnum || ((u != null) && u.IsEnum); - } - - public object ReadYaml(IParser parser, Type type) - { - Type u = Nullable.GetUnderlyingType(type); - if (u != null) - { - type = u; - } - - var parsedEnum = parser.Consume(); - var serializableValues = type.GetMembers() - .Select(m => new KeyValuePair(m.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault(), m)) - .Where(pa => !string.IsNullOrEmpty(pa.Key)).ToDictionary(pa => pa.Key, pa => pa.Value); - if (!serializableValues.ContainsKey(parsedEnum.Value)) - { - throw new YamlException(parsedEnum.Start, parsedEnum.End, $"Value '{parsedEnum.Value}' not found in enum '{type.Name}'"); - } - - return Enum.Parse(type, serializableValues[parsedEnum.Value].Name); - } - - public void WriteYaml(IEmitter emitter, object value, Type type) - { - var enumMember = type.GetMember(value.ToString()).FirstOrDefault(); - var yamlValue = enumMember?.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault() ?? value.ToString(); - emitter.Emit(new Scalar(yamlValue)); - } - } - - private class YamlSkipPropertyVisitor : YamlDotNet.Serialization.ObjectGraphVisitors.ChainedObjectGraphVisitor + /// String value denoting the output manifest format. + /// Exception thrown when an invalid serialization type is provided. + public static void SetManifestSerializer(string outputFormat) { - public YamlSkipPropertyVisitor(IObjectGraphVisitor nextVisitor) - : base(nextVisitor) - { - } - - public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) + // Set the serializer based on the provided value + ManifestSerializer = outputFormat.ToLower() switch { - if (key.Name == "AdditionalProperties") - { - return false; - } - - return base.EnterMapping(key, value, context); - } + "yaml" => new YamlSerializer(), + "json" => new JsonSerializer(), + _ => throw new ArgumentException($"Invalid serialization type: {outputFormat}"), + }; } /// - /// A custom emitter for YamlDotNet which ensures all multiline fields use a . + /// Removes Byte Order Marker (BOM) prefix if exists in string. /// - private class MultilineScalarFlowStyleEmitter : ChainedEventEmitter + /// String to fix. + /// String without BOM prefix. + private static string RemoveBom(string value) { - public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter) - : base(nextEmitter) - { - } - - public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) - { - if (typeof(string).IsAssignableFrom(eventInfo.Source.Type)) - { - var outString = eventInfo.Source.Value as string; - if (!string.IsNullOrEmpty(outString)) - { - bool isMultiLine = new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }.Any(outString.Contains); - if (isMultiLine) - { - eventInfo = new ScalarEventInfo(eventInfo.Source) { Style = ScalarStyle.Literal }; - } - } - } - - this.nextEmitter.Emit(eventInfo, emitter); - } + string bomMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + return value.StartsWith(bomMarkUtf8, StringComparison.OrdinalIgnoreCase) ? value.Remove(0, bomMarkUtf8.Length) : value; } } } diff --git a/src/WingetCreateCore/Interfaces/IManifestSerializer.cs b/src/WingetCreateCore/Interfaces/IManifestSerializer.cs new file mode 100644 index 00000000..7f1e7671 --- /dev/null +++ b/src/WingetCreateCore/Interfaces/IManifestSerializer.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCore.Interfaces +{ + /// + /// Interface for manifest serialization. + /// + public interface IManifestSerializer + { + /// + /// Gets the file extension associated with the serializer. + /// + string AssociatedFileExtension { get; } + + /// + /// Serializes the provided object and returns the serialized string. + /// + /// Object to be serialized. + /// Serialized string. + string ManifestSerialize(object value); + + /// + /// Deserialize a string into a Manifest object. + /// + /// Manifest type. + /// Manifest in string value. + /// Manifest object populated and validated. + T ManifestDeserialize(string value); + + /// + /// Serialize an object to a manifest string. + /// + /// Object to serialize. + /// Type of object to serialize. + /// Value to indicate whether to omit the created by header. + /// Manifest in string value. + string ToManifestString(T value, bool omitCreatedByHeader = false) + where T : new(); + } +} diff --git a/src/WingetCreateCore/Models/Manifests.cs b/src/WingetCreateCore/Models/Manifests.cs index 23126bee..ed120ea5 100644 --- a/src/WingetCreateCore/Models/Manifests.cs +++ b/src/WingetCreateCore/Models/Manifests.cs @@ -46,16 +46,17 @@ public class Manifests /// /// Manifest type. /// Manifest object model. + /// File extension to use depending on the serialization format. /// File name string of manifest. - public static string GetFileName(T manifest) + public static string GetFileName(T manifest, string extension) { return manifest switch { - InstallerManifest installerManifest => $"{installerManifest.PackageIdentifier}.installer.yaml", - VersionManifest versionManifest => $"{versionManifest.PackageIdentifier}.yaml", - DefaultLocaleManifest defaultLocaleManifest => $"{defaultLocaleManifest.PackageIdentifier}.locale.{defaultLocaleManifest.PackageLocale}.yaml", - LocaleManifest localeManifest => $"{localeManifest.PackageIdentifier}.locale.{localeManifest.PackageLocale}.yaml", - SingletonManifest singletonManifest => $"{singletonManifest.PackageIdentifier}.yaml", + InstallerManifest installerManifest => $"{installerManifest.PackageIdentifier}.installer{extension}", + VersionManifest versionManifest => $"{versionManifest.PackageIdentifier}{extension}", + DefaultLocaleManifest defaultLocaleManifest => $"{defaultLocaleManifest.PackageIdentifier}.locale.{defaultLocaleManifest.PackageLocale}{extension}", + LocaleManifest localeManifest => $"{localeManifest.PackageIdentifier}.locale.{localeManifest.PackageLocale}{extension}", + SingletonManifest singletonManifest => $"{singletonManifest.PackageIdentifier}{extension}", _ => throw new ArgumentException(nameof(manifest)), }; } diff --git a/src/WingetCreateCore/Serializers/JsonSerializer.cs b/src/WingetCreateCore/Serializers/JsonSerializer.cs new file mode 100644 index 00000000..4298fca4 --- /dev/null +++ b/src/WingetCreateCore/Serializers/JsonSerializer.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCore.Serializers +{ + using System; + using Microsoft.WingetCreateCore.Interfaces; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// Serializer class for JSON. + /// + public class JsonSerializer : IManifestSerializer + { + /// + public string AssociatedFileExtension => ".json"; + + /// + public T ManifestDeserialize(string value) + { + return JsonConvert.DeserializeObject(value); + } + + /// + public string ManifestSerialize(object value) + { + return JsonConvert.SerializeObject(value, Formatting.Indented); + } + + /// + public string ToManifestString(T value, bool omitCreatedByHeader = false) + where T : new() + { + var serializer = new Newtonsoft.Json.JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; + var jsonObject = JObject.FromObject(value, serializer); + string schemaTemplate = "https://aka.ms/winget-manifest.{0}.{1}.schema.json"; + JToken existingSchemaProperty = jsonObject.Property("$schema"); + + switch (value) + { + case Models.Singleton.SingletonManifest singletonManifest: + if (existingSchemaProperty != null) + { + jsonObject.Property("$schema").Value = string.Format(schemaTemplate, singletonManifest.ManifestType, singletonManifest.ManifestVersion); + } + else + { + jsonObject.AddFirst(new JProperty("$schema", string.Format(schemaTemplate, singletonManifest.ManifestType, singletonManifest.ManifestVersion))); + } + + break; + case Models.Version.VersionManifest versionManifest: + if (existingSchemaProperty != null) + { + jsonObject.Property("$schema").Value = string.Format(schemaTemplate, versionManifest.ManifestType, versionManifest.ManifestVersion); + } + else + { + jsonObject.AddFirst(new JProperty("$schema", string.Format(schemaTemplate, versionManifest.ManifestType, versionManifest.ManifestVersion))); + } + + break; + case Models.Installer.InstallerManifest installerManifest: + if (existingSchemaProperty != null) + { + jsonObject.Property("$schema").Value = string.Format(schemaTemplate, installerManifest.ManifestType, installerManifest.ManifestVersion); + } + else + { + jsonObject.AddFirst(new JProperty("$schema", string.Format(schemaTemplate, installerManifest.ManifestType, installerManifest.ManifestVersion))); + } + + break; + case Models.Locale.LocaleManifest localeManifest: + if (existingSchemaProperty != null) + { + jsonObject.Property("$schema").Value = string.Format(schemaTemplate, localeManifest.ManifestType, localeManifest.ManifestVersion); + } + else + { + jsonObject.AddFirst(new JProperty("$schema", string.Format(schemaTemplate, localeManifest.ManifestType, localeManifest.ManifestVersion))); + } + + break; + case Models.DefaultLocale.DefaultLocaleManifest defaultLocaleManifest: + if (existingSchemaProperty != null) + { + jsonObject.Property("$schema").Value = string.Format(schemaTemplate, defaultLocaleManifest.ManifestType, defaultLocaleManifest.ManifestVersion); + } + else + { + jsonObject.AddFirst(new JProperty("$schema", string.Format(schemaTemplate, defaultLocaleManifest.ManifestType, defaultLocaleManifest.ManifestVersion))); + } + + break; + } + + return jsonObject.ToString(Formatting.Indented) + Environment.NewLine; + } + } +} diff --git a/src/WingetCreateCore/Serializers/YamlSerializer.cs b/src/WingetCreateCore/Serializers/YamlSerializer.cs new file mode 100644 index 00000000..79de760e --- /dev/null +++ b/src/WingetCreateCore/Serializers/YamlSerializer.cs @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCore.Serializers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + using System.Text; + using Microsoft.WingetCreateCore.Interfaces; + using Newtonsoft.Json; + using YamlDotNet.Core; + using YamlDotNet.Core.Events; + using YamlDotNet.Serialization; + using YamlDotNet.Serialization.EventEmitters; + using YamlDotNet.Serialization.NamingConventions; + using YamlDotNet.Serialization.TypeInspectors; + + /// + /// Serializer class for YAML. + /// + public class YamlSerializer : IManifestSerializer + { + /// + public string AssociatedFileExtension => ".yaml"; + + /// + public T ManifestDeserialize(string value) + { + var deserializer = CreateYamlDeserializer(); + return deserializer.Deserialize(value); + } + + /// + public string ManifestSerialize(object value) + { + var serializer = CreateYamlSerializer(); + return serializer.Serialize(value); + } + + /// + public string ToManifestString(T value, bool omitCreatedByHeader = false) + where T : new() + { + var serializer = CreateYamlSerializer(); + string manifestYaml = serializer.Serialize(value); + StringBuilder serialized = new StringBuilder(); + + if (!omitCreatedByHeader) + { + serialized.AppendLine($"# Created using {Serialization.ProducedBy}"); + } + + string schemaTemplate = "# yaml-language-server: $schema=https://aka.ms/winget-manifest.{0}.{1}.schema.json"; + + switch (value) + { + case Models.Singleton.SingletonManifest singletonManifest: + serialized.AppendLine(string.Format(schemaTemplate, singletonManifest.ManifestType, singletonManifest.ManifestVersion)); + break; + case Models.Version.VersionManifest versionManifest: + serialized.AppendLine(string.Format(schemaTemplate, versionManifest.ManifestType, versionManifest.ManifestVersion)); + break; + case Models.Installer.InstallerManifest installerManifest: + serialized.AppendLine(string.Format(schemaTemplate, installerManifest.ManifestType, installerManifest.ManifestVersion)); + break; + case Models.Locale.LocaleManifest localeManifest: + serialized.AppendLine(string.Format(schemaTemplate, localeManifest.ManifestType, localeManifest.ManifestVersion)); + break; + case Models.DefaultLocale.DefaultLocaleManifest defaultLocaleManifest: + serialized.AppendLine(string.Format(schemaTemplate, defaultLocaleManifest.ManifestType, defaultLocaleManifest.ManifestVersion)); + break; + } + + serialized.AppendLine(); + serialized.Append(manifestYaml); + + return serialized.ToString(); + } + + /// + /// Deserializes yaml to JSON object. + /// + /// yaml to deserialize to JSON object. + /// Manifest as a JSON object. + public string ConvertYamlToJson(string yaml) + { + var deserializer = CreateYamlDeserializer(); + using var reader = new StringReader(yaml); + var yamlObject = deserializer.Deserialize(reader); + return JsonConvert.SerializeObject(yamlObject); + } + + /// + /// Helper to build a YAML serializer. + /// + /// ISerializer object. + private static ISerializer CreateYamlSerializer() + { + var serializer = new SerializerBuilder() + .WithQuotingNecessaryStrings() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithTypeConverter(new YamlStringEnumConverter()) + .WithEmissionPhaseObjectGraphVisitor(args => new YamlSkipPropertyVisitor(args.InnerVisitor)) + .WithEventEmitter(nextEmitter => new MultilineScalarFlowStyleEmitter(nextEmitter)) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull); + return serializer.Build(); + } + + /// + /// Helper to build a YAML deserializer. + /// + /// IDeserializer object. + private static IDeserializer CreateYamlDeserializer() + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithTypeConverter(new YamlStringEnumConverter()) + .WithTypeInspector(inspector => new AliasTypeInspector(inspector)) + .IgnoreUnmatchedProperties(); + return deserializer.Build(); + } + + /// + /// Custom TypeInspector to priorize properties that have a defined YamlMemberAttribute for custom override. + /// + private class AliasTypeInspector : TypeInspectorSkeleton + { + private readonly ITypeInspector innerTypeDescriptor; + + public AliasTypeInspector(ITypeInspector innerTypeDescriptor) + { + this.innerTypeDescriptor = innerTypeDescriptor; + } + + /// + /// Because certain properties were generated incorrectly, we needed to create custom fields for those properties. + /// Therefore to resolve naming conflicts during deserialization, we prioritize fields that have the YamlMemberAttribute defined + /// as that attribute indicates an override. + /// + public override IEnumerable GetProperties(Type type, object container) + { + var propertyDescriptors = this.innerTypeDescriptor.GetProperties(type, container); + var aliasDefinedProps = type.GetProperties().ToList() + .Where(p => + { + var yamlMemberAttribute = p.GetCustomAttribute(); + return yamlMemberAttribute != null && !string.IsNullOrEmpty(yamlMemberAttribute.Alias); + }) + .ToList(); + + if (aliasDefinedProps.Any()) + { + var overriddenProps = propertyDescriptors + .Where(prop => aliasDefinedProps.Any(aliasProp => + prop.Name == aliasProp.GetCustomAttribute().Alias && // Use Alias name (ex. ReleaseDate) instead of property name (ex. ReleaseDateString). + prop.Type != aliasProp.PropertyType)) + .ToList(); + + // Remove overridden properties from the returned list of deserializable properties. + return propertyDescriptors + .Where(prop => !overriddenProps.Any(overridenProp => + prop.Name == overridenProp.Name && + prop.Type == overridenProp.Type)) + .ToList(); + } + else + { + return propertyDescriptors; + } + } + } + + private class YamlStringEnumConverter : IYamlTypeConverter + { + public bool Accepts(Type type) + { + Type u = Nullable.GetUnderlyingType(type); + return type.IsEnum || ((u != null) && u.IsEnum); + } + + public object ReadYaml(IParser parser, Type type) + { + Type u = Nullable.GetUnderlyingType(type); + if (u != null) + { + type = u; + } + + var parsedEnum = parser.Consume(); + var serializableValues = type.GetMembers() + .Select(m => new KeyValuePair(m.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault(), m)) + .Where(pa => !string.IsNullOrEmpty(pa.Key)).ToDictionary(pa => pa.Key, pa => pa.Value); + if (!serializableValues.ContainsKey(parsedEnum.Value)) + { + throw new YamlException(parsedEnum.Start, parsedEnum.End, $"Value '{parsedEnum.Value}' not found in enum '{type.Name}'"); + } + + return Enum.Parse(type, serializableValues[parsedEnum.Value].Name); + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var enumMember = type.GetMember(value.ToString()).FirstOrDefault(); + var yamlValue = enumMember?.GetCustomAttributes(true).Select(ema => ema.Value).FirstOrDefault() ?? value.ToString(); + emitter.Emit(new Scalar(yamlValue)); + } + } + + private class YamlSkipPropertyVisitor : YamlDotNet.Serialization.ObjectGraphVisitors.ChainedObjectGraphVisitor + { + public YamlSkipPropertyVisitor(IObjectGraphVisitor nextVisitor) + : base(nextVisitor) + { + } + + public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) + { + if (key.Name == "AdditionalProperties") + { + return false; + } + + return base.EnterMapping(key, value, context); + } + } + + /// + /// A custom emitter for YamlDotNet which ensures all multiline fields use a . + /// + private class MultilineScalarFlowStyleEmitter : ChainedEventEmitter + { + public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter) + : base(nextEmitter) + { + } + + public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) + { + if (typeof(string).IsAssignableFrom(eventInfo.Source.Type)) + { + var outString = eventInfo.Source.Value as string; + if (!string.IsNullOrEmpty(outString)) + { + bool isMultiLine = new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }.Any(outString.Contains); + if (isMultiLine) + { + eventInfo = new ScalarEventInfo(eventInfo.Source) { Style = ScalarStyle.Literal }; + } + } + } + + this.nextEmitter.Emit(eventInfo, emitter); + } + } + } +} diff --git a/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs b/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs index 1d88b3e6..f75d58e3 100644 --- a/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs +++ b/src/WingetCreateTests/WingetCreateTests/E2ETests/E2ETests.cs @@ -8,6 +8,8 @@ namespace Microsoft.WingetCreateE2ETests using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Commands; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; + using Microsoft.WingetCreateCore; using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateTests; using Microsoft.WingetCreateUnitTests; @@ -41,32 +43,45 @@ public void Setup() /// /// The id used for looking up an existing manifest in the repository. /// Manifest to convert and submit. - /// The installar package associated with the manifest. + /// The installer package associated with the manifest. + /// The format of the manifest file. /// A representing the asynchronous test. - [TestCase(TestConstants.TestExePackageIdentifier, TestConstants.TestExeManifest, TestConstants.TestExeInstaller)] - [TestCase(TestConstants.TestMsiPackageIdentifier, TestConstants.TestMsiManifest, TestConstants.TestMsiInstaller)] - [TestCase(TestConstants.TestMultifileMsixPackageIdentifier, TestConstants.TestMultifileMsixManifestDir, TestConstants.TestMsixInstaller)] - [TestCase(TestConstants.TestPortablePackageIdentifier, TestConstants.TestPortableManifest, TestConstants.TestExeInstaller)] - [TestCase(TestConstants.TestZipPackageIdentifier, TestConstants.TestZipManifest, TestConstants.TestZipInstaller)] - public async Task SubmitAndUpdateInstaller(string packageId, string manifestName, string installerName) + // YAML E2E Tests + [TestCase(TestConstants.YamlConstants.TestExePackageIdentifier, TestConstants.YamlConstants.TestExeManifest, TestConstants.TestExeInstaller, TestConstants.YamlManifestFormat)] + [TestCase(TestConstants.YamlConstants.TestMsiPackageIdentifier, TestConstants.YamlConstants.TestMsiManifest, TestConstants.TestMsiInstaller, TestConstants.YamlManifestFormat)] + [TestCase(TestConstants.YamlConstants.TestMultifileMsixPackageIdentifier, TestConstants.YamlConstants.TestMultifileMsixManifestDir, TestConstants.TestMsixInstaller, TestConstants.YamlManifestFormat)] + [TestCase(TestConstants.YamlConstants.TestPortablePackageIdentifier, TestConstants.YamlConstants.TestPortableManifest, TestConstants.TestExeInstaller, TestConstants.YamlManifestFormat)] + [TestCase(TestConstants.YamlConstants.TestZipPackageIdentifier, TestConstants.YamlConstants.TestZipManifest, TestConstants.TestZipInstaller, TestConstants.YamlManifestFormat)] + + // JSON E2E Tests + [TestCase(TestConstants.JsonConstants.TestExePackageIdentifier, TestConstants.JsonConstants.TestExeManifest, TestConstants.TestExeInstaller, TestConstants.JsonManifestFormat)] + [TestCase(TestConstants.JsonConstants.TestMsiPackageIdentifier, TestConstants.JsonConstants.TestMsiManifest, TestConstants.TestMsiInstaller, TestConstants.JsonManifestFormat)] + [TestCase(TestConstants.JsonConstants.TestMultifileMsixPackageIdentifier, TestConstants.JsonConstants.TestMultifileMsixManifestDir, TestConstants.TestMsixInstaller, TestConstants.JsonManifestFormat)] + [TestCase(TestConstants.JsonConstants.TestPortablePackageIdentifier, TestConstants.JsonConstants.TestPortableManifest, TestConstants.TestExeInstaller, TestConstants.JsonManifestFormat)] + [TestCase(TestConstants.JsonConstants.TestZipPackageIdentifier, TestConstants.JsonConstants.TestZipManifest, TestConstants.TestZipInstaller, TestConstants.JsonManifestFormat)] + + public async Task SubmitAndUpdateInstaller(string packageId, string manifestName, string installerName, ManifestFormat format) { - await this.RunSubmitAndUpdateFlow(packageId, TestUtils.GetTestFile(manifestName), installerName); + await this.RunSubmitAndUpdateFlow(packageId, TestUtils.GetTestFile(manifestName), installerName, format); } /// /// Helper method for running the E2E test flow for the submit and update commands. /// /// A representing the asynchronous unit test. - private async Task RunSubmitAndUpdateFlow(string packageId, string manifestPath, string installerName) + private async Task RunSubmitAndUpdateFlow(string packageId, string manifestPath, string installerName, ManifestFormat format) { + string pullRequestTitle = $"{packageId} {PackageVersion} ({format})"; + Serialization.SetManifestSerializer(format.ToString()); SubmitCommand submitCommand = new SubmitCommand { GitHubToken = this.GitHubApiKey, WingetRepo = this.WingetPkgsTestRepo, WingetRepoOwner = this.WingetPkgsTestRepoOwner, Path = manifestPath, - PRTitle = packageId + ' ' + PackageVersion, + PRTitle = pullRequestTitle, SubmitPRToFork = this.SubmitPRToFork, + Format = format, OpenPRInBrowser = false, }; Assert.IsTrue(await submitCommand.Execute(), "Command should have succeeded"); @@ -91,7 +106,8 @@ await mergeRetryPolicy.ExecuteAsync(async () => WingetRepo = this.WingetPkgsTestRepo, WingetRepoOwner = this.WingetPkgsTestRepoOwner, SubmitPRToFork = this.SubmitPRToFork, - PRTitle = packageId + ' ' + PackageVersion, + PRTitle = pullRequestTitle, + Format = format, OpenPRInBrowser = false, }; diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.installer.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.installer.json new file mode 100644 index 00000000..0931fcf1 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.installer.json @@ -0,0 +1,21 @@ +{ + "PackageIdentifier": "Multifile.Json.MsixTest", + "PackageVersion": "1.2.3.4", + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestMsixInstaller.msixbundle", + "InstallerType": "msix", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC" + }, + { + "Architecture": "x86", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestMsixInstaller.msixbundle", + "InstallerType": "msix", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "Scope": "user" + } + ], + "ManifestType": "installer", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.json new file mode 100644 index 00000000..da9a3ec0 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.json @@ -0,0 +1,7 @@ +{ + "PackageIdentifier": "Multifile.Json.MsixTest", + "PackageVersion": "1.2.3.4", + "DefaultLocale": "en-US", + "ManifestType": "version", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-GB.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-GB.json new file mode 100644 index 00000000..d60dbab2 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-GB.json @@ -0,0 +1,11 @@ +{ + "PackageIdentifier": "Multifile.Json.MsixTest", + "PackageVersion": "1.2.3.4", + "PackageLocale": "en-GB", + "Publisher": "Multifile", + "PackageName": "MsixTest", + "License": "MIT", + "ShortDescription": "Testing WingetCreate with a multifile msix manifest", + "ManifestType": "locale", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-US.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-US.json new file mode 100644 index 00000000..fe63efbc --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.MsixTest/Multifile.Json.MsixTest.locale.en-US.json @@ -0,0 +1,11 @@ +{ + "PackageIdentifier": "Multifile.Json.MsixTest", + "PackageVersion": "1.2.3.4", + "PackageLocale": "en-US", + "Publisher": "Multifile", + "PackageName": "MsixTest", + "License": "MIT", + "ShortDescription": "Testing WingetCreate with a multifile msix manifest", + "ManifestType": "defaultLocale", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.installer.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.installer.yaml similarity index 91% rename from src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.installer.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.installer.yaml index 29533bac..1c80f057 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.installer.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.installer.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: Multifile.MsixTest +PackageIdentifier: Multifile.Yaml.MsixTest PackageVersion: 1.2.3.4 Installers: - Architecture: x64 diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-GB.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-GB.yaml similarity index 82% rename from src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-GB.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-GB.yaml index 1c6b623b..b8088651 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-GB.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-GB.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: Multifile.MsixTest +PackageIdentifier: Multifile.Yaml.MsixTest PackageVersion: 1.2.3.4 PackageLocale: en-GB Publisher: Multifile diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-US.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-US.yaml similarity index 82% rename from src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-US.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-US.yaml index c8e5e1af..3678614a 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.locale.en-US.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.locale.en-US.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: Multifile.MsixTest +PackageIdentifier: Multifile.Yaml.MsixTest PackageVersion: 1.2.3.4 PackageLocale: en-US Publisher: Multifile diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.yaml similarity index 65% rename from src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.yaml index ebb8bf8f..21c6decb 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.MsixTest/Multifile.MsixTest.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.MsixTest/Multifile.Yaml.MsixTest.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: Multifile.MsixTest +PackageIdentifier: Multifile.Yaml.MsixTest PackageVersion: 1.2.3.4 DefaultLocale: en-US ManifestType: version diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_1.json b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_1.json new file mode 100644 index 00000000..348147ef --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_1.json @@ -0,0 +1,111 @@ +{ + "PackageIdentifier": "TestPublisher.FullJsonSingleton1_1", + "PackageVersion": "0.1.2.3", + "PackageLocale": "en-US", + "DefaultLocale": "en-US", + "Publisher": "TestPublisher", + "PublisherUrl": "https://fakeTestUrl.com", + "PublisherSupportUrl": "https://fakeTestUrl.com", + "PrivacyUrl": "https://fakeTestUrl.com", + "Author": "fakeAuthor", + "PackageName": "Full Singleton 1.1 Test", + "PackageUrl": "https://fakeTestUrl.com", + "License": "MIT", + "LicenseUrl": "https://fakeTestUrl.com", + "Copyright": "fakeCopyright", + "CopyrightUrl": "https://fakeTestUrl.com", + "ShortDescription": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deserialized and updated.", + "Description": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deseri", + "Moniker": "testMoniker", + "Tags": [ + "testSingleton" + ], + "Agreements": [ + { + "AgreementLabel": "fakeAgreementLabel", + "AgreementUrl": "https://fakeTestUrl.com", + "Agreement": "fakeAgreementContent" + } + ], + "ReleaseNotes": "fakeReleaseNotes", + "ReleaseNotesUrl": "https://fakeTestUrl.com", + "Installers": [ + { + "MinimumOSVersion": "10.0.0.0", + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerType": "exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "ProductCode": "FakeProductCode", + "PackageFamilyName": "FakePackageFamilyName", + "InstallModes": [ + "silent" + ], + "InstallerSwitches": { + "Silent": "/s", + "Upgrade": "/u" + }, + "InstallerSuccessCodes": [ + 1 + ], + "ExpectedReturnCodes": [ + { + "InstallerReturnCode": 123, + "ReturnResponse": "alreadyInstalled" + } + ], + "UpgradeBehavior": "install", + "Commands": [ + "fakeCommand" + ], + "Protocols": [ + "fakeProtocol" + ], + "FileExtensions": [ + ".exe" + ], + "Dependencies": { + "WindowsFeatures": [ + "fakeValue" + ], + "WindowsLibraries": [ + "fakeValue" + ], + "PackageDependencies": [ + { + "MinimumVersion": "10.0.0.0" + } + ], + "ExternalDependencies": [ + "fakeValue" + ] + }, + "Markets": { + "AllowedMarkets": [ + "fakeAllowedMarket" + ], + "ExcludedMarkets": [ + "fakeExcludedMarket" + ] + }, + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ + "arm64" + ], + "AppsAndFeaturesEntries": [ + { + "DisplayName": "testName", + "Publisher": "testPublisher", + "DisplayVersion": "testVersion", + "UpgradeCode": "fakeProductCode" + } + ], + "ElevationRequirement": "elevationRequired", + "ReleaseDate": "2022-01-01" + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.1.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_2.json b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_2.json new file mode 100644 index 00000000..fb2352f3 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_2.json @@ -0,0 +1,127 @@ +{ + "PackageIdentifier": "TestPublisher.FullJsonSingleton1_2", + "PackageVersion": "0.1.2.3", + "PackageLocale": "en-US", + "DefaultLocale": "en-US", + "Publisher": "TestPublisher", + "PublisherUrl": "https://fakeTestUrl.com", + "PublisherSupportUrl": "https://fakeTestUrl.com", + "PrivacyUrl": "https://fakeTestUrl.com", + "Author": "fakeAuthor", + "PackageName": "Full Singleton 1.2 Test", + "PackageUrl": "https://fakeTestUrl.com", + "License": "MIT", + "LicenseUrl": "https://fakeTestUrl.com", + "Copyright": "fakeCopyright", + "CopyrightUrl": "https://fakeTestUrl.com", + "ShortDescription": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deserialized and updated.", + "Description": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deseri", + "Moniker": "testMoniker", + "Tags": [ + "testSingleton" + ], + "Agreements": [ + { + "AgreementLabel": "fakeAgreementLabel", + "AgreementUrl": "https://fakeTestUrl.com", + "Agreement": "fakeAgreementContent" + } + ], + "ReleaseNotes": "fakeReleaseNotes", + "ReleaseNotesUrl": "https://fakeTestUrl.com", + "PurchaseUrl": "https://fakeTestUrl.com", + "InstallationNotes": "fakeInstallationNotes", + "Documentations": [ + { + "DocumentLabel": "fakeDocumentLabel" + }, + { + "DocumentUrl": "fakeDocumentUrl" + } + ], + "Installers": [ + { + "MinimumOSVersion": "10.0.0.0", + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerType": "exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "ProductCode": "FakeProductCode", + "PackageFamilyName": "FakePackageFamilyName", + "InstallModes": [ + "silent" + ], + "InstallerSwitches": { + "Silent": "/s", + "Upgrade": "/u" + }, + "InstallerSuccessCodes": [ + 1 + ], + "ExpectedReturnCodes": [ + { + "InstallerReturnCode": 123, + "ReturnResponse": "alreadyInstalled", + "ReturnResponseUrl": "https://fakeTestUrl.com" + } + ], + "UpgradeBehavior": "install", + "Commands": [ + "fakeCommand" + ], + "Protocols": [ + "fakeProtocol" + ], + "FileExtensions": [ + ".exe" + ], + "Dependencies": { + "WindowsFeatures": [ + "fakeValue" + ], + "WindowsLibraries": [ + "fakeValue" + ], + "PackageDependencies": [ + { + "MinimumVersion": "10.0.0.0" + } + ], + "ExternalDependencies": [ + "fakeValue" + ] + }, + "Markets": { + "AllowedMarkets": [ + "fakeAllowedMarket" + ], + "ExcludedMarkets": [ + "fakeExcludedMarket" + ] + }, + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ + "arm64" + ], + "AppsAndFeaturesEntries": [ + { + "DisplayName": "testName", + "Publisher": "testPublisher", + "DisplayVersion": "testVersion", + "UpgradeCode": "fakeProductCode" + } + ], + "ElevationRequirement": "elevationRequired", + "ReleaseDate": "2022-01-01", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ + "log", + "location" + ] + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.2.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_4.json b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_4.json new file mode 100644 index 00000000..748d953a --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_4.json @@ -0,0 +1,144 @@ +{ + "PackageIdentifier": "TestPublisher.FullJsonSingleton1_4", + "PackageVersion": "0.1.2.3", + "PackageLocale": "en-US", + "DefaultLocale": "en-US", + "Publisher": "TestPublisher", + "PublisherUrl": "https://fakeTestUrl.com", + "PublisherSupportUrl": "https://fakeTestUrl.com", + "PrivacyUrl": "https://fakeTestUrl.com", + "Author": "fakeAuthor", + "PackageName": "Full Singleton 1.4 Test", + "PackageUrl": "https://fakeTestUrl.com", + "License": "MIT", + "LicenseUrl": "https://fakeTestUrl.com", + "Copyright": "fakeCopyright", + "CopyrightUrl": "https://fakeTestUrl.com", + "ShortDescription": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deserialized and updated.", + "Description": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deseri", + "Moniker": "testMoniker", + "Tags": [ + "testSingleton" + ], + "Agreements": [ + { + "AgreementLabel": "fakeAgreementLabel", + "AgreementUrl": "https://fakeTestUrl.com", + "Agreement": "fakeAgreementContent" + } + ], + "ReleaseNotes": "fakeReleaseNotes", + "ReleaseNotesUrl": "https://fakeTestUrl.com", + "PurchaseUrl": "https://fakeTestUrl.com", + "InstallationNotes": "fakeInstallationNotes", + "Documentations": [ + { + "DocumentLabel": "fakeDocumentLabel" + }, + { + "DocumentUrl": "fakeDocumentUrl" + } + ], + "Installers": [ + { + "MinimumOSVersion": "10.0.0.0", + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerType": "exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "ProductCode": "FakeProductCode", + "PackageFamilyName": "FakePackageFamilyName", + "InstallModes": [ + "silent" + ], + "InstallerSwitches": { + "Silent": "/s", + "Upgrade": "/u" + }, + "InstallerSuccessCodes": [ + 1 + ], + "ExpectedReturnCodes": [ + { + "InstallerReturnCode": 123, + "ReturnResponse": "alreadyInstalled", + "ReturnResponseUrl": "https://fakeTestUrl.com" + } + ], + "UpgradeBehavior": "install", + "Commands": [ + "fakeCommand" + ], + "Protocols": [ + "fakeProtocol" + ], + "FileExtensions": [ + ".exe" + ], + "Dependencies": { + "WindowsFeatures": [ + "fakeValue" + ], + "WindowsLibraries": [ + "fakeValue" + ], + "PackageDependencies": [ + { + "MinimumVersion": "10.0.0.0" + } + ], + "ExternalDependencies": [ + "fakeValue" + ] + }, + "Markets": { + "AllowedMarkets": [ + "fakeAllowedMarket" + ], + "ExcludedMarkets": [ + "fakeExcludedMarket" + ] + }, + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ + "arm64" + ], + "AppsAndFeaturesEntries": [ + { + "DisplayName": "testName", + "Publisher": "testPublisher", + "DisplayVersion": "testVersion", + "UpgradeCode": "fakeProductCode" + } + ], + "ElevationRequirement": "elevationRequired", + "ReleaseDate": "2022-01-01", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ + "log", + "location" + ], + "NestedInstallerFiles": [ + { + "RelativeFilePath": "RelativeFilePath", + "PortableCommandAlias": "PortableCommandAlias" + } + ], + "InstallationMetadata": { + "DefaultInstallLocation": "%ProgramFiles%\\TestApp", + "Files": [ + { + "RelativeFilePath": "main.exe", + "FileSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", + "FileType": "launch", + "InvocationParameter": "/arg" + } + ] + } + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.4.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_5.json b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_5.json new file mode 100644 index 00000000..69709d15 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_5.json @@ -0,0 +1,153 @@ +{ + "PackageIdentifier": "TestPublisher.FullJsonSingleton1_5", + "PackageVersion": "0.1.2.3", + "PackageLocale": "en-US", + "DefaultLocale": "en-US", + "Publisher": "TestPublisher", + "PublisherUrl": "https://fakeTestUrl.com", + "PublisherSupportUrl": "https://fakeTestUrl.com", + "PrivacyUrl": "https://fakeTestUrl.com", + "Author": "fakeAuthor", + "PackageName": "Full Singleton 1.5 Test", + "PackageUrl": "https://fakeTestUrl.com", + "License": "MIT", + "LicenseUrl": "https://fakeTestUrl.com", + "Copyright": "fakeCopyright", + "CopyrightUrl": "https://fakeTestUrl.com", + "ShortDescription": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deserialized and updated.", + "Description": "A manifest used to verify that all fields from a full 1.1 singleton manifest can be deseri", + "Moniker": "testMoniker", + "Tags": [ + "testSingleton" + ], + "Agreements": [ + { + "AgreementLabel": "fakeAgreementLabel", + "AgreementUrl": "https://fakeTestUrl.com", + "Agreement": "fakeAgreementContent" + } + ], + "ReleaseNotes": "fakeReleaseNotes", + "ReleaseNotesUrl": "https://fakeTestUrl.com", + "PurchaseUrl": "https://fakeTestUrl.com", + "InstallationNotes": "fakeInstallationNotes", + "Documentations": [ + { + "DocumentLabel": "fakeDocumentLabel" + }, + { + "DocumentUrl": "fakeDocumentUrl" + } + ], + "Icons": [ + { + "IconUrl": "https://fakeTestIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + } + ], + "Installers": [ + { + "MinimumOSVersion": "10.0.0.0", + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerType": "exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "ProductCode": "FakeProductCode", + "PackageFamilyName": "FakePackageFamilyName", + "InstallModes": [ + "silent" + ], + "InstallerSwitches": { + "Silent": "/s", + "Upgrade": "/u" + }, + "InstallerSuccessCodes": [ + 1 + ], + "ExpectedReturnCodes": [ + { + "InstallerReturnCode": 123, + "ReturnResponse": "alreadyInstalled", + "ReturnResponseUrl": "https://fakeTestUrl.com" + } + ], + "UpgradeBehavior": "install", + "Commands": [ + "fakeCommand" + ], + "Protocols": [ + "fakeProtocol" + ], + "FileExtensions": [ + ".exe" + ], + "Dependencies": { + "WindowsFeatures": [ + "fakeValue" + ], + "WindowsLibraries": [ + "fakeValue" + ], + "PackageDependencies": [ + { + "MinimumVersion": "10.0.0.0" + } + ], + "ExternalDependencies": [ + "fakeValue" + ] + }, + "Markets": { + "AllowedMarkets": [ + "fakeAllowedMarket" + ], + "ExcludedMarkets": [ + "fakeExcludedMarket" + ] + }, + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ + "arm64" + ], + "AppsAndFeaturesEntries": [ + { + "DisplayName": "testName", + "Publisher": "testPublisher", + "DisplayVersion": "testVersion", + "UpgradeCode": "fakeProductCode" + } + ], + "ElevationRequirement": "elevationRequired", + "ReleaseDate": "2022-01-01", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ + "log", + "location" + ], + "NestedInstallerFiles": [ + { + "RelativeFilePath": "RelativeFilePath", + "PortableCommandAlias": "PortableCommandAlias" + } + ], + "InstallationMetadata": { + "DefaultInstallLocation": "%ProgramFiles%\\TestApp", + "Files": [ + { + "RelativeFilePath": "main.exe", + "FileSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", + "FileType": "launch", + "InvocationParameter": "/arg" + } + ] + } + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.5.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_1.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_1.yaml similarity index 97% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_1.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_1.yaml index 04841e66..c1c3e45c 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_1.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_1.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: TestPublisher.FullSingleton1_1 +PackageIdentifier: TestPublisher.FullYamlSingleton1_1 PackageVersion: 0.1.2.3 PackageLocale: en-US DefaultLocale: en-US diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_2.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_2.yaml similarity index 97% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_2.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_2.yaml index d447b944..896c0084 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_2.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_2.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: TestPublisher.FullSingleton1_2 +PackageIdentifier: TestPublisher.FullYamlSingleton1_2 PackageVersion: 0.1.2.3 PackageLocale: en-US DefaultLocale: en-US diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_4.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_4.yaml similarity index 98% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_4.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_4.yaml index dc720ee7..309a404a 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_4.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_4.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: TestPublisher.FullSingleton1_4 +PackageIdentifier: TestPublisher.FullYamlSingleton1_4 PackageVersion: 0.1.2.3 PackageLocale: en-US DefaultLocale: en-US diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_5.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_5.yaml similarity index 98% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_5.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_5.yaml index 5161ad4b..963d655e 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullSingleton1_5.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_5.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: TestPublisher.FullSingleton1_5 +PackageIdentifier: TestPublisher.FullYamlSingleton1_5 PackageVersion: 0.1.2.3 PackageLocale: en-US DefaultLocale: en-US diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ExeTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ExeTest.json new file mode 100644 index 00000000..462c4931 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ExeTest.json @@ -0,0 +1,34 @@ +{ + "PackageIdentifier": "WingetCreateE2E.Json.ExeTest", + "PackageName": "ExeTest", + "PackageVersion": "1.2.3.4", + "Publisher": "WingetCreateE2E", + "Author": "Microsoft", + "License": "MIT", + "LicenseUrl": "https://github.com/microsoft/winget-create", + "MinimumOSVersion": "10.0.0.0", + "ShortDescription": "Testing WingetCreate with an exe installer", + "PackageLocale": "en-US", + "Tags": [ + "Windows Package Manager", + "winget create", + "package manager", + "utility", + "tool", + "test" + ], + "InstallerType": "exe", + "InstallerSwitches": { + "Silent": "--silent", + "SilentWithProgress": "--silent" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC" + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.MsiTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.MsiTest.json new file mode 100644 index 00000000..6cd01bd0 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.MsiTest.json @@ -0,0 +1,31 @@ +{ + "PackageIdentifier": "WingetCreateE2E.Json.MsiTest", + "PackageName": "MsiTest", + "PackageVersion": "1.2.3.4", + "Publisher": "WingetCreateE2E", + "Author": "Microsoft", + "License": "MIT", + "LicenseUrl": "https://github.com/microsoft/winget-create", + "MinimumOSVersion": "10.0.0.0", + "ShortDescription": "Testing WingetCreate with an msi installer", + "PackageLocale": "en-US", + "Tags": [ + "Windows Package Manager", + "winget create", + "package manager", + "utility", + "tool", + "test" + ], + "InstallerType": "msi", + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestMsiInstaller.msi", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "Scope": "machine" + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.PortableTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.PortableTest.json new file mode 100644 index 00000000..b6ab4c1d --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.PortableTest.json @@ -0,0 +1,33 @@ +{ + "PackageIdentifier": "WingetCreateE2E.Json.PortableTest", + "PackageName": "PortableTest", + "PackageVersion": "1.2.3.4", + "Publisher": "WingetCreateE2E", + "Author": "Microsoft", + "License": "MIT", + "LicenseUrl": "https://github.com/microsoft/winget-create", + "MinimumOSVersion": "10.0.0.0", + "ShortDescription": "Testing WingetCreate with a portable exe", + "PackageLocale": "en-US", + "Tags": [ + "Windows Package Manager", + "winget create", + "package manager", + "utility", + "tool", + "test" + ], + "InstallerType": "portable", + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "Commands": [ + "portableCommand" + ] + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.2.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ZipTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ZipTest.json new file mode 100644 index 00000000..e83272ea --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Json.ZipTest.json @@ -0,0 +1,36 @@ +{ + "PackageIdentifier": "WingetCreateE2E.Json.ZipTest", + "PackageName": "ZipTest", + "PackageVersion": "1.2.3.4", + "Publisher": "WingetCreateE2E", + "Author": "Microsoft", + "License": "MIT", + "LicenseUrl": "https://github.com/microsoft/winget-create", + "MinimumOSVersion": "10.0.0.0", + "ShortDescription": "Testing WingetCreate with a zip", + "PackageLocale": "en-US", + "Tags": [ + "Windows Package Manager", + "winget create", + "package manager", + "utility", + "tool", + "test" + ], + "InstallerType": "zip", + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestZipInstaller.zip", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC", + "NestedInstallerType": "msi", + "NestedInstallerFiles": [ + { + "RelativeFilePath": "WingetCreateTestMsiInstaller.msi" + } + ] + } + ], + "ManifestType": "singleton", + "ManifestVersion": "1.4.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ExeTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ExeTest.yaml similarity index 93% rename from src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ExeTest.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ExeTest.yaml index 09b6cfeb..9d08b37d 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ExeTest.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ExeTest.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: WingetCreateE2E.ExeTest +PackageIdentifier: WingetCreateE2E.Yaml.ExeTest PackageName: ExeTest PackageVersion: 1.2.3.4 Publisher: WingetCreateE2E diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.MsiTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.MsiTest.yaml similarity index 92% rename from src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.MsiTest.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.MsiTest.yaml index 0a9f2b47..7dba24d1 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.MsiTest.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.MsiTest.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: WingetCreateE2E.MsiTest +PackageIdentifier: WingetCreateE2E.Yaml.MsiTest PackageName: MsiTest PackageVersion: 1.2.3.4 Publisher: WingetCreateE2E diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.PortableTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.PortableTest.yaml similarity index 92% rename from src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.PortableTest.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.PortableTest.yaml index ed99dc7b..c1761805 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.PortableTest.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.PortableTest.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: WingetCreateE2E.PortableTest +PackageIdentifier: WingetCreateE2E.Yaml.PortableTest PackageName: PortableTest PackageVersion: 1.2.3.4 Publisher: WingetCreateE2E diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ZipTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ZipTest.yaml similarity index 93% rename from src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ZipTest.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ZipTest.yaml index d8435019..5bb40035 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.ZipTest.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/WingetCreateE2E.Yaml.ZipTest.yaml @@ -1,4 +1,4 @@ -PackageIdentifier: WingetCreateE2E.ZipTest +PackageIdentifier: WingetCreateE2E.Yaml.ZipTest PackageName: ZipTest PackageVersion: 1.2.3.4 Publisher: WingetCreateE2E diff --git a/src/WingetCreateTests/WingetCreateTests/TestConstants.cs b/src/WingetCreateTests/WingetCreateTests/TestConstants.cs index d80f83f2..da8419c0 100644 --- a/src/WingetCreateTests/WingetCreateTests/TestConstants.cs +++ b/src/WingetCreateTests/WingetCreateTests/TestConstants.cs @@ -3,6 +3,8 @@ namespace Microsoft.WingetCreateTests { + using Microsoft.WingetCreateCLI.Models.Settings; + /// /// Shared string constants to be used in test cases. /// @@ -49,53 +51,125 @@ public static class TestConstants public const string TestZipInstaller = "WingetCreateTestZipInstaller.zip"; /// - /// PackageIdentifier for test exe. + /// Format argument to be used for YAML submissions. /// - public const string TestExePackageIdentifier = "WingetCreateE2E.ExeTest"; + public const ManifestFormat YamlManifestFormat = ManifestFormat.Yaml; /// - /// PackageIdentifier for test msi. + /// Format argument to be used for JSON submissions. /// - public const string TestMsiPackageIdentifier = "WingetCreateE2E.MsiTest"; + public const ManifestFormat JsonManifestFormat = ManifestFormat.Json; /// - /// File name of the test msix manifest. + /// Test constants to use for YAML manifest tests. /// - public const string TestMultifileMsixPackageIdentifier = "Multifile.MsixTest"; + public static class YamlConstants + { + /// + /// PackageIdentifier for test exe. + /// + public const string TestExePackageIdentifier = "WingetCreateE2E.Yaml.ExeTest"; - /// - /// PackageIdentifier for test portable. - /// - public const string TestPortablePackageIdentifier = "WingetCreateE2E.PortableTest"; + /// + /// PackageIdentifier for test msi. + /// + public const string TestMsiPackageIdentifier = "WingetCreateE2E.Yaml.MsiTest"; - /// - /// PackageIdentifier for test zip. - /// - public const string TestZipPackageIdentifier = "WingetCreateE2E.ZipTest"; + /// + /// File name of the test msix manifest. + /// + public const string TestMultifileMsixPackageIdentifier = "Multifile.Yaml.MsixTest"; - /// - /// File name of the test exe manifest. - /// - public const string TestExeManifest = "WingetCreateE2E.ExeTest.yaml"; + /// + /// PackageIdentifier for test portable. + /// + public const string TestPortablePackageIdentifier = "WingetCreateE2E.Yaml.PortableTest"; - /// - /// File name of the test msi manifest. - /// - public const string TestMsiManifest = "WingetCreateE2E.MsiTest.yaml"; + /// + /// PackageIdentifier for test zip. + /// + public const string TestZipPackageIdentifier = "WingetCreateE2E.Yaml.ZipTest"; - /// - /// File name of the test portable manifest. - /// - public const string TestPortableManifest = "WingetCreateE2E.PortableTest.yaml"; + /// + /// File name of the test exe manifest. + /// + public const string TestExeManifest = "WingetCreateE2E.Yaml.ExeTest.yaml"; - /// - /// Path of the directory with the multifile msix test manifests. - /// - public const string TestMultifileMsixManifestDir = "Multifile.MsixTest"; + /// + /// File name of the test msi manifest. + /// + public const string TestMsiManifest = "WingetCreateE2E.Yaml.MsiTest.yaml"; + + /// + /// File name of the test portable manifest. + /// + public const string TestPortableManifest = "WingetCreateE2E.Yaml.PortableTest.yaml"; + + /// + /// Path of the directory with the multifile msix test manifests. + /// + public const string TestMultifileMsixManifestDir = "Multifile.Yaml.MsixTest"; + + /// + /// File name of the test zip manifest. + /// + public const string TestZipManifest = "WingetCreateE2E.Yaml.ZipTest.yaml"; + } /// - /// File name of the test zip manifest. + /// Test constants to use for JSON manifest tests. /// - public const string TestZipManifest = "WingetCreateE2E.ZipTest.yaml"; + public static class JsonConstants + { + /// + /// PackageIdentifier for test exe. + /// + public const string TestExePackageIdentifier = "WingetCreateE2E.Json.ExeTest"; + + /// + /// PackageIdentifier for test msi. + /// + public const string TestMsiPackageIdentifier = "WingetCreateE2E.Json.MsiTest"; + + /// + /// File name of the test msix manifest. + /// + public const string TestMultifileMsixPackageIdentifier = "Multifile.Json.MsixTest"; + + /// + /// PackageIdentifier for test portable. + /// + public const string TestPortablePackageIdentifier = "WingetCreateE2E.Json.PortableTest"; + + /// + /// PackageIdentifier for test zip. + /// + public const string TestZipPackageIdentifier = "WingetCreateE2E.Json.ZipTest"; + + /// + /// File name of the test exe manifest. + /// + public const string TestExeManifest = "WingetCreateE2E.Json.ExeTest.json"; + + /// + /// File name of the test msi manifest. + /// + public const string TestMsiManifest = "WingetCreateE2E.Json.MsiTest.json"; + + /// + /// File name of the test portable manifest. + /// + public const string TestPortableManifest = "WingetCreateE2E.Json.PortableTest.json"; + + /// + /// Path of the directory with the multifile msix test manifests. + /// + public const string TestMultifileMsixManifestDir = "Multifile.Json.MsixTest"; + + /// + /// File name of the test zip manifest. + /// + public const string TestZipManifest = "WingetCreateE2E.Json.ZipTest.json"; + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CharacterValidationTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CharacterValidationTests.cs index a040f89c..dec4b2ab 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CharacterValidationTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CharacterValidationTests.cs @@ -5,9 +5,10 @@ namespace Microsoft.WingetCreateUnitTests { using System; using System.IO; - using System.Linq; using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Interfaces; using Microsoft.WingetCreateCore.Models.Singleton; + using Microsoft.WingetCreateCore.Serializers; using NUnit.Framework; /// @@ -35,19 +36,22 @@ public class CharacterValidationTests [Test] public void VerifyTextSupport() { - string testManifestFilePath = Path.Combine(Path.GetTempPath(), "TestManifest.yaml"); - - foreach (string testString in this.testStrings) + foreach (var serializerType in Serialization.AvailableSerializerTypes) { - SingletonManifest manifest = new SingletonManifest { Description = testString }; - File.WriteAllText(testManifestFilePath, manifest.ToYaml()); + IManifestSerializer serializer = (IManifestSerializer)Activator.CreateInstance(serializerType); + string testManifestFilePath = Path.Combine(Path.GetTempPath(), $"TestManifest{serializer.AssociatedFileExtension}"); + foreach (string testString in this.testStrings) + { + SingletonManifest manifest = new SingletonManifest { Description = testString }; + File.WriteAllText(testManifestFilePath, serializer.ToManifestString(manifest)); - SingletonManifest testManifest = Serialization.DeserializeFromPath(testManifestFilePath); - Assert.AreEqual( - testString, - testManifest.Description, - string.Format("Unicode string: {0} failed to display correctly.", testString)); - File.Delete(testManifestFilePath); + SingletonManifest testManifest = Serialization.DeserializeFromPath(testManifestFilePath); + Assert.AreEqual( + testString, + testManifest.Description, + string.Format("Unicode string: {0} failed to display correctly.", testString)); + File.Delete(testManifestFilePath); + } } } @@ -55,7 +59,7 @@ public void VerifyTextSupport() /// Verifies that we aren't adding more newlines than expected. /// [Test] - public void VerfiyNewLineSupport() + public void VerifyNewLineSupport() { string[] stringsWithNewLines = { @@ -66,18 +70,30 @@ public void VerfiyNewLineSupport() "Me too!\x2029:)", }; - string testManifestFilePath = Path.Combine(Path.GetTempPath(), "TestManifest.yaml"); - - foreach (var i in stringsWithNewLines) + foreach (var serializerType in Serialization.AvailableSerializerTypes) { - SingletonManifest written = new SingletonManifest { Description = i }; - File.WriteAllText(testManifestFilePath, written.ToYaml()); - SingletonManifest read = Serialization.DeserializeFromPath(testManifestFilePath); + IManifestSerializer serializer = (IManifestSerializer)Activator.CreateInstance(serializerType); + string testManifestFilePath = Path.Combine(Path.GetTempPath(), $"TestManifest{serializer.AssociatedFileExtension}"); + + foreach (var i in stringsWithNewLines) + { + SingletonManifest written = new SingletonManifest { Description = i }; + File.WriteAllText(testManifestFilePath, serializer.ToManifestString(written)); + SingletonManifest read = Serialization.DeserializeFromPath(testManifestFilePath); + + if (serializer.GetType() == typeof(YamlSerializer)) + { + // we know that \r\n and \x85 characters are replaced with \n by YamlDotNet library. + var writtenFixed = string.Join('\n', written.Description.Split(new string[] { "\n", "\r\n", "\x85" }, StringSplitOptions.None)); + Assert.AreEqual(writtenFixed, read.Description, $"String {read.Description} had the wrong number of newlines :(."); + } + else + { + Assert.AreEqual(written.Description, read.Description, $"String {read.Description} had the wrong number of newlines :(."); + } - // we know when written that \r\n and \x85 characters are replaced with \n. - var writtenFixed = string.Join('\n', written.Description.Split(new string[] { "\n", "\r\n", "\x85" }, StringSplitOptions.None)); - Assert.AreEqual(writtenFixed, read.Description, $"String {read.Description} had the wrong number of newlines :(."); - File.Delete(testManifestFilePath); + File.Delete(testManifestFilePath); + } } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs index 1d34180b..83addab9 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.WingetCreateUnitTests using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.Singleton; + using Microsoft.WingetCreateCore.Serializers; using Microsoft.WingetCreateTests; using NUnit.Framework; using Octokit; @@ -38,6 +39,7 @@ public void Setup() { this.gitHub = new GitHub(this.GitHubApiKey, this.WingetPkgsTestRepoOwner, this.WingetPkgsTestRepo); Serialization.ProducedBy = "WingetCreateUnitTests"; + Serialization.ManifestSerializer = new YamlSerializer(); } /// diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs index 00e62e91..5f46d6ef 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs @@ -23,7 +23,7 @@ public class LocaleCommandsTests [Test] public void VerifyLocaleValidations() { - var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.MsixTest"); + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.Yaml.MsixTest"); Manifests manifests = Serialization.DeserializeManifestContents(initialManifestContent); // Verify with different casing. diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs index 0bce6f8b..422d96fd 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs @@ -100,12 +100,14 @@ public void VerifySavingSettings() int cleanUpDays = 30; string testRepoOwner = "testRepoOwner"; string testRepoName = "testRepoName"; + ManifestFormat manifestFormat = ManifestFormat.Json; UserSettings.TelemetryDisabled = !isTelemetryDisabled; UserSettings.CleanUpDisabled = !isCleanUpDisabled; UserSettings.AnonymizePaths = !arePathsAnonymized; UserSettings.CleanUpDays = cleanUpDays; UserSettings.WindowsPackageManagerRepositoryOwner = testRepoOwner; UserSettings.WindowsPackageManagerRepositoryName = testRepoName; + UserSettings.ManifestFormat = manifestFormat; UserSettings.ParseJsonFile(UserSettings.SettingsJsonPath, out SettingsManifest manifest); Assert.IsTrue(manifest.Telemetry.Disable == !isTelemetryDisabled, "Changed Telemetry setting was not reflected in the settings file."); Assert.IsTrue(manifest.CleanUp.Disable == !isCleanUpDisabled, "Changed CleanUp.Disable setting was not reflected in the settings file."); @@ -113,6 +115,7 @@ public void VerifySavingSettings() Assert.IsTrue(manifest.CleanUp.IntervalInDays == cleanUpDays, "Changed CleanUp.IntervalInDays setting was not reflected in the settings file."); Assert.IsTrue(manifest.WindowsPackageManagerRepository.Owner == testRepoOwner, "Changed WindowsPackageManagerRepository.Owner setting was not reflected in the settings file."); Assert.IsTrue(manifest.WindowsPackageManagerRepository.Name == testRepoName, "Changed WindowsPackageManagerRepository.Name setting was not reflected in the settings file."); + Assert.IsTrue(manifest.Manifest.Format == manifestFormat, "Changed Manifest.Format setting was not reflected in the settings file."); } /// diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs index cdf92714..8e37579c 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.WingetCreateUnitTests using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; + using Microsoft.WingetCreateCore.Serializers; using Microsoft.WingetCreateTests; using NUnit.Framework; @@ -37,6 +38,7 @@ public void OneTimeSetup() { this.testCommandEvent = new CommandExecutedEvent(); Logger.Initialize(); + Serialization.ManifestSerializer = new YamlSerializer(); } /// @@ -87,7 +89,7 @@ public async Task UpdateAndVerifyUpdatedProperties() { TestUtils.InitializeMockDownloads(TestConstants.TestMsiInstaller); string version = "1.2.3.4"; - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData(TestConstants.TestMsiPackageIdentifier, version, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData(TestConstants.YamlConstants.TestMsiPackageIdentifier, version, this.tempPath, null); var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); var initialInstaller = initialManifests.SingletonManifest.Installers.First(); @@ -642,7 +644,7 @@ public async Task UpdatesToLatestManifestVersion() public async Task UpdateMultifileToLatestManifestVersion() { TestUtils.InitializeMockDownloads(TestConstants.TestMsixInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("Multifile.MsixTest", null, this.tempPath, null, true); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("Multifile.Yaml.MsixTest", null, this.tempPath, null, true); var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); string initialManifestVersion = initialManifests.VersionManifest.ManifestVersion; var updatedManifests = await RunUpdateCommand(command, initialManifestContent); @@ -681,53 +683,105 @@ public void UpdateDeserializesAliasDefinedFields() } /// - /// Ensures that all fields from the Singleton v1.1 manifest can be deserialized and updated correctly. + /// Ensures that all fields from the YAML Singleton v1.1 manifest can be deserialized and updated correctly. /// /// A representing the result of the asynchronous operation. [Test] - public async Task UpdateFullSingletonVersion1_1() + public async Task UpdateFullYamlSingletonVersion1_1() { TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullSingleton1_1", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_1", null, this.tempPath, null); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); Assert.IsNotNull(updatedManifests, "Command should have succeeded"); } /// - /// Ensures that all fields from the Singleton v1.2 manifest can be deserialized and updated correctly. + /// Ensures that all fields from the YAML Singleton v1.2 manifest can be deserialized and updated correctly. /// /// A representing the result of the asynchronous operation. [Test] - public async Task UpdateFullSingletonVersion1_2() + public async Task UpdateFullYamlSingletonVersion1_2() { TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullSingleton1_2", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_2", null, this.tempPath, null); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); Assert.IsNotNull(updatedManifests, "Command should have succeeded"); } /// - /// Ensures that all fields from the Singleton v1.4 manifest can be deserialized and updated correctly. + /// Ensures that all fields from the YAML Singleton v1.4 manifest can be deserialized and updated correctly. /// /// A representing the result of the asynchronous operation. [Test] - public async Task UpdateFullSingletonVersion1_4() + public async Task UpdateFullYamlSingletonVersion1_4() { TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullSingleton1_4", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_4", null, this.tempPath, null); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); Assert.IsNotNull(updatedManifests, "Command should have succeeded"); } /// - /// Ensures that all fields from the Singleton v1.5 manifest can be deserialized and updated correctly. + /// Ensures that all fields from the YAML Singleton v1.5 manifest can be deserialized and updated correctly. /// /// A representing the result of the asynchronous operation. [Test] - public async Task UpdateFullSingletonVersion1_5() + public async Task UpdateFullYamlSingletonVersion1_5() { TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullSingleton1_5", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_5", null, this.tempPath, null); + var updatedManifests = await RunUpdateCommand(command, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded"); + } + + /// + /// Ensures that all fields from the JSON Singleton v1.1 manifest can be deserialized and updated correctly. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateFullJsonSingletonVersion1_1() + { + TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullJsonSingleton1_1", null, this.tempPath, null, fileExtension: ".json"); + var updatedManifests = await RunUpdateCommand(command, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded"); + } + + /// + /// Ensures that all fields from the JSON Singleton v1.2 manifest can be deserialized and updated correctly. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateFullJsonSingletonVersion1_2() + { + TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullJsonSingleton1_2", null, this.tempPath, null, fileExtension: ".json"); + var updatedManifests = await RunUpdateCommand(command, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded"); + } + + /// + /// Ensures that all fields from the JSON Singleton v1.4 manifest can be deserialized and updated correctly. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateFullJsonSingletonVersion1_4() + { + TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullJsonSingleton1_4", null, this.tempPath, null, fileExtension: ".json"); + var updatedManifests = await RunUpdateCommand(command, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded"); + } + + /// + /// Ensures that all fields from the Singleton JSON v1.5 manifest can be deserialized and updated correctly. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateFullJsonSingletonVersion1_5() + { + TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullJsonSingleton1_5", null, this.tempPath, null, fileExtension: ".json"); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); Assert.IsNotNull(updatedManifests, "Command should have succeeded"); } @@ -740,7 +794,7 @@ public async Task UpdateFullSingletonVersion1_5() public async Task UpdateResetsVersionSpecificFields() { TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullSingleton1_1", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_1", null, this.tempPath, null); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); Assert.IsNotNull(updatedManifests, "Command should have succeeded"); @@ -1313,7 +1367,7 @@ public async Task UpdateZipInvalidRelativeFilePath() Assert.That(result, Does.Contain(string.Format(Resources.NestedInstallerFileNotFound_Error, "fakeRelativeFilePath.exe")), "Failed to show warning for invalid relative file path."); } - private static (UpdateCommand UpdateCommand, List InitialManifestContent) GetUpdateCommandAndManifestData(string id, string version, string outputDir, IEnumerable installerUrls, bool isMultifile = false) + private static (UpdateCommand UpdateCommand, List InitialManifestContent) GetUpdateCommandAndManifestData(string id, string version, string outputDir, IEnumerable installerUrls, bool isMultifile = false, string fileExtension = ".yaml") { var updateCommand = new UpdateCommand { @@ -1327,7 +1381,7 @@ private static (UpdateCommand UpdateCommand, List InitialManifestContent updateCommand.InstallerUrls = installerUrls; } - var initialManifestContent = isMultifile ? TestUtils.GetInitialMultifileManifestContent(id) : TestUtils.GetInitialManifestContent($"{id}.yaml"); + var initialManifestContent = isMultifile ? TestUtils.GetInitialMultifileManifestContent(id) : TestUtils.GetInitialManifestContent($"{id}{fileExtension}"); return (updateCommand, initialManifestContent); } diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 66da4bb8..a84a2de8 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -27,13 +27,34 @@ - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest @@ -54,7 +75,7 @@ PreserveNewest - + PreserveNewest @@ -84,19 +105,19 @@ PreserveNewest - + PreserveNewest PreserveNewest - + PreserveNewest PreserveNewest - + PreserveNewest @@ -129,13 +150,28 @@ PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -147,10 +183,10 @@ PreserveNewest - + PreserveNewest - + PreserveNewest