diff --git a/README.md b/README.md index 57fc0562..631e73c5 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ choco install wingetcreate | ------- | ----------- | | [New](doc/new.md) | Command for creating a new manifest from scratch | | [Update](doc/update.md) | Command for updating an existing manifest | +| [New-Locale](doc/new-locale.md) | Command for creating a new locale for an existing manifest | +| [Update-Locale](doc/update-locale.md) | Command for updating a locale for an existing manifest | | [Submit](doc/submit.md) | Command for submitting an existing PR | | [Show](doc/show.md) | Command for displaying existing manifests | | [Token](doc/token.md) | Command for managing cached GitHub personal access tokens | diff --git a/doc/new-locale.md b/doc/new-locale.md new file mode 100644 index 00000000..9d50b5a1 --- /dev/null +++ b/doc/new-locale.md @@ -0,0 +1,33 @@ +# new-locale command (Winget-Create) + +The **new-locale** command of the [Winget-Create](../README.md) tool is designed to create a new locale for an existing manifest from the [Windows Package Manager repo](https://docs.microsoft.com/windows/package-manager/). This command offers an interactive flow, prompting user for a set of locale fields and then generating a new locale manifest. + +## Usage + +Add a new locale for the latest version of a package: + +`wingetcreate.exe new-locale --token ` + +Add a new locale for a specific version of a package: + +`wingetcreate.exe new-locale --token --version ` + +Create a new locale and save the generated manifests to a specified directory: + +`wingetcreate.exe new-locale --out --token --version ` + +## Arguments + +The following arguments are available: + +| Argument | Description | +|--------------|-------------| +| **id** | Required. Package identifier used to lookup the existing manifest on the Windows Package Manager repo. +| **-v, --version** | The version of the package to add a new locale for. Default is the latest version. +| **-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. +| **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | +| **-?, --help** | Gets additional help on this command | + +Instructions on setting up GitHub Token for Winget-Create can be found [here](../README.md#github-personal-access-token-classic-permissions). diff --git a/doc/update-locale.md b/doc/update-locale.md new file mode 100644 index 00000000..5822f02f --- /dev/null +++ b/doc/update-locale.md @@ -0,0 +1,32 @@ +# update-locale command (Winget-Create) + +The **update-locale** command of the [Winget-Create](../README.md) tool is designed to update existing locales for a manifest from the [Windows Package Manager repo](https://docs.microsoft.com/windows/package-manager/). This command offers an interactive flow, prompting user for a set of locale fields and then generating a new manifest for submission. + +## Usage + +Update existing locales for the latest version of a package: + +`wingetcreate.exe update-locale --token ` + +Update existing locales for a specific version of a package: + +`wingetcreate.exe update-locale --token --version ` + +Update existing locale and save the generated manifests to a specified directory: + +`wingetcreate.exe update-locale --out --token --version ` + +## Arguments + +The following arguments are available: + +| Argument | Description | +|--------------|-------------| +| **id** | Required. Package identifier used to lookup the existing manifest on the Windows Package Manager repo. +| **-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. +| **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | +| **-?, --help** | Gets additional help on this command | + +Instructions on setting up GitHub Token for Winget-Create can be found [here](../README.md#github-personal-access-token-classic-permissions). diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 737b59bc..96182a7c 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -6,10 +6,12 @@ namespace Microsoft.WingetCreateCLI.Commands using System; using System.Collections; using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net; using System.Net.Http; + using System.Reflection; using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Logging; using Microsoft.WingetCreateCLI.Properties; @@ -23,8 +25,10 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Locale; using Microsoft.WingetCreateCore.Models.Version; + using Newtonsoft.Json; using Octokit; using RestSharp; + using Sharprompt; /// /// Abstract base command class that all commands inherit from. @@ -219,6 +223,12 @@ protected static bool ValidateManifestsInTempDir(Manifests manifests) File.WriteAllText(Path.Combine(randomDirPath, installerManifestFileName), manifests.InstallerManifest.ToYaml()); File.WriteAllText(Path.Combine(randomDirPath, defaultLocaleManifestFileName), manifests.DefaultLocaleManifest.ToYaml()); + foreach (LocaleManifest localeManifest in manifests.LocaleManifests) + { + string localeManifestFileName = Manifests.GetFileName(localeManifest); + File.WriteAllText(Path.Combine(randomDirPath, localeManifestFileName), localeManifest.ToYaml()); + } + bool result = ValidateManifest(randomDirPath); Directory.Delete(randomDirPath, true); return result; @@ -515,6 +525,83 @@ protected static void ShiftInstallerFieldsToRootLevel(InstallerManifest installe } } + /// + /// Prompts user to enter values for the optional properties of the manifest. + /// + /// Type of the manifest. + /// Object model of the manifest. + /// Optional parameter to specify the list of property names to be prompted for. + protected static void PromptOptionalProperties(T manifest, List optionalPropertiesNames = null) + { + List optionalProperties; + if (optionalPropertiesNames == null) + { + optionalProperties = manifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null).ToList(); + } + else + { + optionalProperties = manifest.GetType().GetProperties().Where(p => optionalPropertiesNames.Contains(p.Name)).ToList(); + } + + foreach (var property in optionalProperties) + { + Type type = property.PropertyType; + if (type.IsEnumerable()) + { + Type elementType = type.GetGenericArguments().SingleOrDefault(); + if (elementType.IsNonStringClassType() && !Prompt.Confirm(string.Format(Resources.EditObjectTypeField_Message, property.Name))) + { + continue; + } + } + + PromptHelper.PromptPropertyAndSetValue(manifest, property.Name, property.GetValue(manifest)); + Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]"); + } + } + + /// + /// Displays the preview of the default locale manifest to the console. + /// + /// Object model of the default locale manifest. + protected static void DisplayDefaultLocaleManifest(DefaultLocaleManifest defaultLocaleManifest) + { + Logger.InfoLocalized(nameof(Resources.DefaultLocaleManifest_Message), defaultLocaleManifest.PackageLocale); + Console.WriteLine(defaultLocaleManifest.ToYaml(true)); + } + + /// + /// Displays the preview of all the locale manifests to the console. + /// + /// List of locale manifest object models. + protected static void DisplayLocaleManifests(List localeManifests) + { + foreach (var localeManifest in localeManifests) + { + Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); + Console.WriteLine(localeManifest.ToYaml(true)); + } + } + + /// + /// Ensures that the manifestVersion is consistent across all manifest object models. + /// + /// Manifests object model. + protected static void EnsureManifestVersionConsistency(Manifests manifests) + { + string latestManifestVersion = new VersionManifest().ManifestVersion; + manifests.VersionManifest.ManifestVersion = latestManifestVersion; + manifests.DefaultLocaleManifest.ManifestVersion = latestManifestVersion; + manifests.InstallerManifest.ManifestVersion = latestManifestVersion; + + foreach (var localeManifest in manifests.LocaleManifests) + { + localeManifest.ManifestVersion = latestManifestVersion; + } + } + /// /// Launches the GitHub OAuth flow and obtains a GitHub token. /// diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index 91926b80..8393607d 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -33,11 +33,6 @@ namespace Microsoft.WingetCreateCLI.Commands [Verb("new", HelpText = "NewCommand_HelpText", ResourceType = typeof(Resources))] public class NewCommand : BaseCommand { - /// - /// The url path to the manifest documentation site. - /// - private const string ManifestDocumentationUrl = "https://aka.ms/winget-manifest-schema"; - /// /// Installer types for which we can trust that the detected architecture is correct, so don't need to prompt the user to confirm. /// @@ -210,9 +205,9 @@ public override async Task Execute() Console.WriteLine(); Console.WriteLine(Resources.NewCommand_Header); Console.WriteLine(); - Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), ManifestDocumentationUrl); + Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); Console.WriteLine(); - Console.WriteLine(Resources.NewCommand_Description); + Console.WriteLine(Resources.NewCommand_PrePrompt_Header); Console.WriteLine(); Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); @@ -338,30 +333,6 @@ private static void PromptRequiredProperties(T manifest, VersionManifest vers } } - private static void PromptOptionalProperties(T manifest) - { - var properties = manifest.GetType().GetProperties().ToList(); - var optionalProperties = properties.Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null).ToList(); - - foreach (var property in optionalProperties) - { - Type type = property.PropertyType; - if (type.IsEnumerable()) - { - Type elementType = type.GetGenericArguments().SingleOrDefault(); - if (elementType.IsNonStringClassType() && !Prompt.Confirm(string.Format(Resources.EditObjectTypeField_Message, property.Name))) - { - continue; - } - } - - PromptHelper.PromptPropertyAndSetValue(manifest, property.Name, property.GetValue(manifest)); - Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]"); - } - } - private static void PromptInstallerProperties(T manifest, PropertyInfo property) { List installers = new List((ICollection)property.GetValue(manifest)); diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs new file mode 100644 index 00000000..98667ab0 --- /dev/null +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI.Commands +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using CommandLine; + using CommandLine.Text; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCLI.Telemetry; + using Microsoft.WingetCreateCLI.Telemetry.Events; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Common; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; + using Sharprompt; + + /// + /// Command to create a new locale manifest for an existing manifest in the Windows Package Manager repository. + /// + [Verb("new-locale", HelpText = "NewLocaleCommand_HelpText", ResourceType = typeof(Resources))] + public class NewLocaleCommand : BaseCommand + { + private readonly List defaultPromptPropertiesForNewLocale = new() + { + nameof(LocaleManifest.PackageLocale), + nameof(LocaleManifest.PackageName), + nameof(LocaleManifest.Publisher), + nameof(LocaleManifest.License), + nameof(LocaleManifest.ShortDescription), + }; + + private bool localeArgumentUsed = false; + + /// + /// Gets the usage examples for the new-locale command. + /// + [Usage(ApplicationAlias = ProgramApplicationAlias)] + public static IEnumerable Examples + { + get + { + yield return new Example(Resources.Example_NewLocaleCommand_AddForLatestVersion, new NewLocaleCommand { Id = "", GitHubToken = "" }); + yield return new Example(Resources.Example_NewLocaleCommand_AddForSpecificVersion, new NewLocaleCommand { Id = "", Version = "", GitHubToken = "" }); + yield return new Example(Resources.Example_NewLocaleCommand_SaveToDirectory, new NewLocaleCommand { Id = "", Locale = "", Version = "", OutputDir = "", GitHubToken = "" }); + } + } + + /// + /// Gets or sets the id used for looking up an existing manifest in the Windows Package Manager repository. + /// + [Value(0, MetaName = "PackageIdentifier", Required = true, HelpText = "PackageIdentifier_HelpText", ResourceType = typeof(Resources))] + public string Id { get; set; } + + /// + /// Gets or sets the version of the package from the Windows Package Manager repository to create the new locales for. + /// + [Option('v', "version", Required = false, Default = null, HelpText = "NewLocaleCommand_Version_HelpText", ResourceType = typeof(Resources))] + public string Version { get; set; } + + /// + /// Gets or sets the locale to create a new manifest for. + /// + [Option('l', "locale", Required = false, HelpText = "NewLocaleCommand_Locale_HelpText", ResourceType = typeof(Resources))] + public string Locale { get; set; } + + /// + /// Gets or sets the reference locale to use for generating the new locale manifest. + /// + [Option('r', "reference-locale", Required = false, HelpText = "ReferenceLocale_HelpText", ResourceType = typeof(Resources))] + public string ReferenceLocale { get; set; } + + /// + /// Gets or sets the outputPath where the generated manifest file should be saved to. + /// + [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] + public string OutputDir { get; set; } + + /// + /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. + /// + [Option('t', "token", Required = false, HelpText = "GitHubToken_HelpText", ResourceType = typeof(Resources))] + public override string GitHubToken { get => base.GitHubToken; set => base.GitHubToken = value; } + + /// + /// Executes the new-locale command flow. + /// + /// Boolean representing success or fail of the command. + public override async Task Execute() + { + CommandExecutedEvent commandEvent = new CommandExecutedEvent + { + Command = nameof(NewLocaleCommand), + Id = this.Id, + Version = this.Version, + HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), + }; + + try + { + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); + + // Validate format of input locale and reference locale arguments + try + { + if (!string.IsNullOrEmpty(this.Locale)) + { + _ = new RegionInfo(this.Locale); + } + + if (!string.IsNullOrEmpty(this.ReferenceLocale)) + { + _ = new RegionInfo(this.ReferenceLocale); + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } + + Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); + + if (!string.IsNullOrEmpty(this.Locale)) + { + try + { + if (LocaleHelper.DoesLocaleManifestExist(this.Locale, originalManifests)) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), this.Locale); + return false; + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + } + + // Validate input reference locale argument and set the reference locale + LocaleManifest referenceLocaleManifest; + + if (!string.IsNullOrEmpty(this.ReferenceLocale)) + { + try + { + referenceLocaleManifest = (LocaleManifest)LocaleHelper.GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + + if (referenceLocaleManifest == null) + { + Logger.ErrorLocalized(nameof(Resources.ReferenceLocaleNotFound_Error)); + return false; + } + } + else + { + referenceLocaleManifest = originalManifests.DefaultLocaleManifest.ToLocaleManifest(); + } + + Console.WriteLine(Resources.NewLocaleCommand_Header); + Console.WriteLine(); + Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); + Console.WriteLine(); + Console.WriteLine(Resources.NewLocaleCommand_PrePrompt_Header); + Console.WriteLine(); + + Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); + + List newLocales = new(); + do + { + LocaleManifest newlocale = this.GenerateLocaleManifest(originalManifests, referenceLocaleManifest); + Console.WriteLine(); + ValidateManifestsInTempDir(originalManifests); + originalManifests.LocaleManifests.Add(newlocale); + newLocales.Add(newlocale); + } + while (Prompt.Confirm(Resources.CreateAnotherLocale_Message)); + + Console.WriteLine(); + EnsureManifestVersionConsistency(originalManifests); + this.DisplayGeneratedLocales(newLocales); + + if (string.IsNullOrEmpty(this.OutputDir)) + { + this.OutputDir = Directory.GetCurrentDirectory(); + } + + string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir); + + if (ValidateManifest(manifestDirectoryPath)) + { + if (Prompt.Confirm(Resources.ConfirmGitHubSubmitManifest_Message)) + { + return await this.LoadGitHubClient(true) ? + (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( + originalManifests, + $"Add locale: {originalManifests.VersionManifest.PackageIdentifier} version {originalManifests.VersionManifest.PackageVersion}")) + : false; + } + else + { + Logger.WarnLocalized(nameof(Resources.SkippingPullRequest_Message)); + } + } + else + { + return false; + } + + return await Task.FromResult(commandEvent.IsSuccessful = true); + } + finally + { + TelemetryManager.Log.WriteEvent(commandEvent); + } + } + + private void PopulateLocalePropertiesWithDefaultValues(LocaleManifest reference, LocaleManifest target, List properties) + { + foreach (string propertyName in properties) + { + if (propertyName == nameof(LocaleManifest.PackageLocale)) + { + continue; + } + + var value = reference.GetType().GetProperty(propertyName).GetValue(reference); + if (value != null) + { + target.GetType().GetProperty(propertyName).SetValue(target, value); + } + } + } + + private LocaleManifest GenerateLocaleManifest(Manifests originalManifests, LocaleManifest referenceLocaleManifest) + { + LocaleManifest newLocaleManifest = new LocaleManifest + { + PackageIdentifier = originalManifests.VersionManifest.PackageIdentifier, + PackageVersion = originalManifests.VersionManifest.PackageVersion, + }; + + // Fill in properties from the reference locale. This is to help user see previous values to quickly fill out the new manifest. + this.PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, this.defaultPromptPropertiesForNewLocale); + + if (!string.IsNullOrEmpty(this.Locale) && !this.localeArgumentUsed) + { + newLocaleManifest.PackageLocale = this.Locale; + + // Don't prompt for PackageLocale if it is provided as an argument. + List properties = new(this.defaultPromptPropertiesForNewLocale); + properties.Remove(nameof(LocaleManifest.PackageLocale)); + + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), this.Locale); + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); + this.localeArgumentUsed = true; + } + else + { + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, this.defaultPromptPropertiesForNewLocale, originalManifests); + } + + Console.WriteLine(); + if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) + { + // Get optional properties that have not been prompted before. + List optionalProperties = newLocaleManifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null && + !this.defaultPromptPropertiesForNewLocale.Any(d => d == p.Name)) + .Select(p => p.Name).ToList(); + this.PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, optionalProperties); + PromptOptionalProperties(newLocaleManifest, optionalProperties); + } + + return newLocaleManifest; + } + + private void DisplayGeneratedLocales(List newLocales) + { + Logger.DebugLocalized(nameof(Resources.GenerateNewLocalePreview_Message)); + foreach (var localeManifest in newLocales) + { + Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); + Console.WriteLine(localeManifest.ToYaml(true)); + } + } + } +} diff --git a/src/WingetCreateCLI/Commands/ShowCommand.cs b/src/WingetCreateCLI/Commands/ShowCommand.cs index 74101e12..610003c9 100644 --- a/src/WingetCreateCLI/Commands/ShowCommand.cs +++ b/src/WingetCreateCLI/Commands/ShowCommand.cs @@ -95,7 +95,6 @@ public override async Task Execute() HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), }; - Console.OutputEncoding = System.Text.Encoding.UTF8; string exactId; try { @@ -153,21 +152,6 @@ private static void DisplayVersionManifest(VersionManifest versionManifest) Console.WriteLine(versionManifest.ToYaml(true)); } - private static void DisplayDefaultLocaleManifest(DefaultLocaleManifest defaultLocaleManifest) - { - Logger.InfoLocalized(nameof(Resources.DefaultLocaleManifest_Message), defaultLocaleManifest.PackageLocale); - Console.WriteLine(defaultLocaleManifest.ToYaml(true)); - } - - private static void DisplayLocaleManifests(List localeManifests) - { - foreach (var localeManifest in localeManifests) - { - Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); - Console.WriteLine(localeManifest.ToYaml(true)); - } - } - private static void DisplaySingletonManifest(SingletonManifest singletonManifest) { Logger.InfoLocalized(nameof(Resources.SingletonManifest_Message)); diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index afd6b902..e15213a4 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -545,23 +545,6 @@ private static void UpdatePropertyForLocaleManifests(string propertyName, string } } - /// - /// Ensures that the manifestVersion is consistent across all manifest object models. - /// - /// Manifests object model. - private static void EnsureManifestVersionConsistency(Manifests manifests) - { - string latestManifestVersion = new VersionManifest().ManifestVersion; - manifests.VersionManifest.ManifestVersion = latestManifestVersion; - manifests.DefaultLocaleManifest.ManifestVersion = latestManifestVersion; - manifests.InstallerManifest.ManifestVersion = latestManifestVersion; - - foreach (var localeManifest in manifests.LocaleManifests) - { - localeManifest.ManifestVersion = latestManifestVersion; - } - } - /// /// Resets the value of version specific fields to null. /// diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs new file mode 100644 index 00000000..54bd1bcd --- /dev/null +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -0,0 +1,284 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI.Commands +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using CommandLine; + using CommandLine.Text; + using Microsoft.WingetCreateCLI.Logging; + 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.Locale; + using Sharprompt; + + /// + /// Command to update an existing locale manifest for a package in the Windows Package Manager repository. + /// + [Verb("update-locale", HelpText = "UpdateLocaleCommand_HelpText", ResourceType = typeof(Resources))] + public class UpdateLocaleCommand : BaseCommand + { + /// + /// Gets the usage examples for the update-locale command. + /// + [Usage(ApplicationAlias = ProgramApplicationAlias)] + public static IEnumerable Examples + { + get + { + yield return new Example(Resources.Example_UpdateLocaleCommand_UpdateForLatestVersion, new UpdateLocaleCommand { Id = "", GitHubToken = "" }); + yield return new Example(Resources.Example_UpdateLocaleCommand_UpdateForSpecificVersion, new UpdateLocaleCommand { Id = "", Version = "", GitHubToken = "" }); + yield return new Example(Resources.Example_UpdateLocaleCommand_SaveToDirectory, new UpdateLocaleCommand { Id = "", Locale = "", Version = "", OutputDir = "", GitHubToken = "" }); + } + } + + /// + /// Gets or sets the id used for looking up an existing manifest in the Windows Package Manager repository. + /// + [Value(0, MetaName = "PackageIdentifier", Required = true, HelpText = "PackageIdentifier_HelpText", ResourceType = typeof(Resources))] + public string Id { get; set; } + + /// + /// Gets or sets the version of the package from the Windows Package Manager repository to update the locales for. + /// + [Option('v', "version", Required = false, Default = null, HelpText = "UpdateLocaleCommand_Version_HelpText", ResourceType = typeof(Resources))] + public string Version { get; set; } + + /// + /// Gets or sets the locale to update the manifest for. + /// + [Option('l', "locale", Required = false, HelpText = "UpdateLocaleCommand_Locale_HelpText", ResourceType = typeof(Resources))] + public string Locale { get; set; } + + /// + /// Gets or sets the outputPath where the generated manifest file should be saved to. + /// + [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] + public string OutputDir { get; set; } + + /// + /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. + /// + [Option('t', "token", Required = false, HelpText = "GitHubToken_HelpText", ResourceType = typeof(Resources))] + public override string GitHubToken { get => base.GitHubToken; set => base.GitHubToken = value; } + + /// + /// Executes the update-locale command flow. + /// + /// Boolean representing success or fail of the command. + public override async Task Execute() + { + CommandExecutedEvent commandEvent = new CommandExecutedEvent + { + Command = nameof(UpdateLocaleCommand), + Id = this.Id, + Version = this.Version, + HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), + }; + + try + { + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); + + // Validate format of input locale argument + try + { + if (!string.IsNullOrEmpty(this.Locale)) + { + _ = new RegionInfo(this.Locale); + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } + + Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); + + // Validate input locale argument + if (!string.IsNullOrEmpty(this.Locale)) + { + try + { + if (!LocaleHelper.DoesLocaleManifestExist(this.Locale, originalManifests)) + { + Logger.ErrorLocalized(nameof(Resources.LocaleDoesNotExist_Message), this.Locale); + return false; + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + } + + var updatedLocalesTuple = this.PromptAndUpdateExistingLocales(originalManifests); + Console.WriteLine(); + EnsureManifestVersionConsistency(originalManifests); + this.DisplayUpdatedLocales(updatedLocalesTuple); + + if (string.IsNullOrEmpty(this.OutputDir)) + { + this.OutputDir = Directory.GetCurrentDirectory(); + } + + string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir); + + if (ValidateManifest(manifestDirectoryPath)) + { + if (Prompt.Confirm(Resources.ConfirmGitHubSubmitManifest_Message)) + { + return await this.LoadGitHubClient(true) ? + (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( + originalManifests, + $"Update locale: {originalManifests.VersionManifest.PackageIdentifier} version {originalManifests.VersionManifest.PackageVersion}")) + : false; + } + else + { + Logger.WarnLocalized(nameof(Resources.SkippingPullRequest_Message)); + } + } + else + { + return false; + } + + return await Task.FromResult(commandEvent.IsSuccessful = true); + } + finally + { + TelemetryManager.Log.WriteEvent(commandEvent); + } + } + + private object PromptAllLocalesAsMenuSelection(DefaultLocaleManifest defaultLocale, List localeManifests) + { + Dictionary localeMap = new Dictionary + { + { string.Format("{0} ({1})", defaultLocale.PackageLocale, Resources.DefaultLocale_MenuItem), defaultLocale }, + }; + + foreach (var locale in localeManifests) + { + localeMap.Add(locale.PackageLocale, locale); + } + + string selectedLocale = Prompt.Select(Resources.SelectExistingLocale_Message, localeMap.Keys.ToList(), defaultValue: localeMap.Keys.First()); + return localeMap[selectedLocale]; + } + + private void DisplayUpdatedLocales(Tuple> updatedLocales) + { + Logger.DebugLocalized(nameof(Resources.GenerateUpdatedLocalePreview_Message)); + if (updatedLocales.Item1 != null) + { + DisplayDefaultLocaleManifest(updatedLocales.Item1); + } + + if (updatedLocales.Item2 != null) + { + DisplayLocaleManifests(updatedLocales.Item2); + } + } + + /// + /// Displays a list of existing locale manifests and prompts user to update properties for the selected locale. + /// + /// Object model containing existing locale manifests. + /// A tuple containing the updated locale manifests. + private Tuple> PromptAndUpdateExistingLocales(Manifests manifests) + { + // Object containing only the locales that are updated by the user. + Manifests updatedLocales = new Manifests(); + + bool localeArgumentUsed = false; + object selectedLocale; + + // Properties that should not be prompted for. + List excludedProperties = new List() + { + nameof(LocaleManifest.PackageIdentifier), + nameof(LocaleManifest.PackageVersion), + nameof(LocaleManifest.PackageLocale), + nameof(LocaleManifest.ManifestType), + nameof(LocaleManifest.ManifestVersion), + }; + + do + { + if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) + { + selectedLocale = LocaleHelper.GetMatchingLocaleManifest(this.Locale, manifests); + localeArgumentUsed = true; + } + else + { + selectedLocale = this.PromptAllLocalesAsMenuSelection(manifests.DefaultLocaleManifest, manifests.LocaleManifests); + } + + if (selectedLocale is DefaultLocaleManifest defaultLocaleManifest) + { + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), defaultLocaleManifest.PackageLocale); + var properties = LocaleHelper.GetLocalePropertyNames(defaultLocaleManifest).Except(excludedProperties).ToList(); + LocaleHelper.PromptAndSetLocaleProperties(defaultLocaleManifest, properties); + updatedLocales.DefaultLocaleManifest = defaultLocaleManifest; + } + else if (selectedLocale is LocaleManifest localeManifest) + { + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), localeManifest.PackageLocale); + var properties = LocaleHelper.GetLocalePropertyNames(localeManifest).Except(excludedProperties).ToList(); + LocaleHelper.PromptAndSetLocaleProperties(localeManifest, properties); + updatedLocales.LocaleManifests.Add(localeManifest); + } + + Console.WriteLine(); + ValidateManifestsInTempDir(manifests); + } + while (Prompt.Confirm(Resources.UpdateAnotherLocale_Message)); + + return new Tuple>(updatedLocales.DefaultLocaleManifest, updatedLocales.LocaleManifests); + } + } +} diff --git a/src/WingetCreateCLI/LocaleHelper.cs b/src/WingetCreateCLI/LocaleHelper.cs new file mode 100644 index 00000000..a7c11b9e --- /dev/null +++ b/src/WingetCreateCLI/LocaleHelper.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore.Common; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; + using Sharprompt; + + /// + /// Provides helper functions for dealing with prompting and validating locale properties. + /// + public static class LocaleHelper + { + /// + /// Prompts user to enter values for the input locale or default locale manifest properties. + /// + /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. + /// Object model of the locale/defaultLocale manifest. + /// List of property names to be prompted for. + /// Optional parameter to be used when validating the user-inputted locale. Check whether the locale already exists in the original manifests. + public static void PromptAndSetLocaleProperties(T localeManifest, List properties, Manifests originalManifests = null) + { + foreach (string propertyName in properties) + { + PropertyInfo property = typeof(T).GetProperty(propertyName); + + Type type = property.PropertyType; + if (type.IsEnumerable()) + { + Type elementType = type.GetGenericArguments().SingleOrDefault(); + if (elementType.IsNonStringClassType() && !Prompt.Confirm(string.Format(Resources.EditObjectTypeField_Message, property.Name))) + { + continue; + } + } + + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + + if (propertyName == nameof(LocaleManifest.PackageLocale) && originalManifests != null) + { + while (!ValidatePromptedLocale(property.GetValue(localeManifest).ToString(), originalManifests)) + { + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + } + + continue; + } + + Logger.Trace($"Property [{propertyName}] set to the value [{property.GetValue(localeManifest)}]"); + } + } + + /// + /// Returns the locale manifest that matches the provided locale string. Returns null if the locale does not exist. + /// This function throws an exception if the locale string is in an invalid format. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// An object representing the matching locale manifest. + public static object GetMatchingLocaleManifest(string locale, Manifests manifests) + { + RegionInfo localeInfo = new RegionInfo(locale); + + if (localeInfo.Equals(new RegionInfo(manifests.DefaultLocaleManifest.PackageLocale))) + { + return manifests.DefaultLocaleManifest; + } + + foreach (var localeManifest in manifests.LocaleManifests) + { + if (localeInfo.Equals(new RegionInfo(localeManifest.PackageLocale))) + { + return localeManifest; + } + } + + return null; + } + + /// + /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests. + /// This function throws an exception if the locale string is in an invalid format. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale exists. + public static bool DoesLocaleManifestExist(string locale, Manifests manifests) + { + RegionInfo targetLocaleInfo = new RegionInfo(locale); + + if (targetLocaleInfo.Equals(new RegionInfo(manifests.DefaultLocaleManifest.PackageLocale))) + { + return true; + } + + return manifests.LocaleManifests.Any(localeManifest => targetLocaleInfo.Equals(new RegionInfo(localeManifest.PackageLocale))); + } + + /// + /// Gets the list of all locale properties. + /// + /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. + /// Object model of the locale/defaultLocale manifest. + /// List of locale property names. + public static List GetLocalePropertyNames(T manifest) + { + return manifest.GetType().GetProperties().ToList().Where(p => p.GetCustomAttribute() != null) + .Select(property => property.Name).ToList(); + } + + /// + /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. + /// This function handles the exception gracefully to be used in a prompt. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale is valid. + private static bool ValidatePromptedLocale(string locale, Manifests manifests) + { + try + { + if (DoesLocaleManifestExist(locale, manifests)) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); + Console.WriteLine(); + return false; + } + + return true; + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + Console.WriteLine(); + return false; + } + } + } +} diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 8a132b59..2dae8b96 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -28,6 +28,7 @@ private static async Task Main(string[] args) UserSettings.FirstRunTelemetryConsent(); TelemetryEventListener.EventListener.IsTelemetryEnabled(); SentenceBuilder.Factory = () => new LocalizableSentenceBuilder(); + Console.OutputEncoding = System.Text.Encoding.UTF8; string arguments = string.Join(' ', Environment.GetCommandLineArgs()); Logger.Trace($"Command line args: {arguments}"); @@ -42,6 +43,8 @@ private static async Task Main(string[] args) { typeof(NewCommand), typeof(UpdateCommand), + typeof(NewLocaleCommand), + typeof(UpdateLocaleCommand), typeof(SubmitCommand), typeof(SettingsCommand), typeof(TokenCommand), diff --git a/src/WingetCreateCLI/PromptHelper.cs b/src/WingetCreateCLI/PromptHelper.cs index 549454ec..aa94595b 100644 --- a/src/WingetCreateCLI/PromptHelper.cs +++ b/src/WingetCreateCLI/PromptHelper.cs @@ -15,7 +15,6 @@ namespace Microsoft.WingetCreateCLI using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; - using Newtonsoft.Json; using Sharprompt; /// @@ -409,7 +408,9 @@ public static void PromptList(string message, object model, string memberName if (!value.Any()) { - value = null; + // In update scenarios, if an older value exists then use that value instead of setting the property to null. + var existingValue = model.GetType().GetProperty(property.Name).GetValue(model); + value = existingValue != null ? (IEnumerable)existingValue : null; } else { diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index a3495ede..dc9c783d 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -69,6 +69,15 @@ public static string Add_MenuItem { } } + /// + /// Looks up a localized string similar to Would you like to add additional locale properties?. + /// + public static string AddAdditionalLocaleProperties_Message { + get { + return ResourceManager.GetString("AddAdditionalLocaleProperties_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Additional metadata needed for installer from {0}. /// @@ -384,6 +393,15 @@ public static string CopyrightUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to create another locale?. + /// + public static string CreateAnotherLocale_Message { + get { + return ResourceManager.GetString("CreateAnotherLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to The custom installer switches. /// @@ -411,6 +429,15 @@ public static string DefaultLocale_KeywordDescription { } } + /// + /// Looks up a localized string similar to Default locale. + /// + public static string DefaultLocale_MenuItem { + get { + return ResourceManager.GetString("DefaultLocale_MenuItem", resourceCulture); + } + } + /// /// Looks up a localized string similar to Display the default locale manifest of the package.. /// @@ -744,6 +771,33 @@ public static string Example_NewCommand_StartFromScratch { } } + /// + /// Looks up a localized string similar to Add a new locale for the latest version of a package. + /// + public static string Example_NewLocaleCommand_AddForLatestVersion { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_AddForLatestVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a new locale for a specific version of a package. + /// + public static string Example_NewLocaleCommand_AddForSpecificVersion { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_AddForSpecificVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create a new locale and save the generated manifests to a specified directory. + /// + public static string Example_NewLocaleCommand_SaveToDirectory { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_SaveToDirectory", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show the latest manifest of an existing package from the Windows Package Manager repo. /// @@ -825,6 +879,33 @@ public static string Example_UpdateCommand_SubmitToGitHub { } } + /// + /// Looks up a localized string similar to Update existing locale and save the generated manifests to a specified directory. + /// + public static string Example_UpdateLocaleCommand_SaveToDirectory { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_SaveToDirectory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update existing locale for the latest version of a package. + /// + public static string Example_UpdateLocaleCommand_UpdateForLatestVersion { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_UpdateForLatestVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update existing locale for a specific version of a package. + /// + public static string Example_UpdateLocaleCommand_UpdateForSpecificVersion { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_UpdateForSpecificVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to The excluded installer target markets. /// @@ -861,6 +942,15 @@ public static string ExternalDependencies_KeywordDescription { } } + /// + /// Looks up a localized string similar to [{0}] value is: {1}. + /// + public static string FieldSetToValue_Message { + get { + return ResourceManager.GetString("FieldSetToValue_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to [{0}] value is. /// @@ -924,6 +1014,15 @@ public static string GenerateManifestPreview_Message { } } + /// + /// Looks up a localized string similar to Generating a preview of the created locale(s)..... + /// + public static string GenerateNewLocalePreview_Message { + get { + return ResourceManager.GetString("GenerateNewLocalePreview_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to No settings file found, generating new settings file from loaded settings.... /// @@ -933,6 +1032,15 @@ public static string GenerateNewSettingsFile_Message { } } + /// + /// Looks up a localized string similar to Generating a preview of the updated locale(s)..... + /// + public static string GenerateUpdatedLocalePreview_Message { + get { + return ResourceManager.GetString("GenerateUpdatedLocalePreview_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to For the latest version ({0}), go to {1}. /// @@ -1356,6 +1464,15 @@ public static string InvalidGitHubToken_Message { } } + /// + /// Looks up a localized string similar to Invalid locale. Enter a valid language tag.. + /// + public static string InvalidLocale_ErrorMessage { + get { + return ResourceManager.GetString("InvalidLocale_ErrorMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid token provided, please generate a new GitHub token and try again.. /// @@ -1473,6 +1590,24 @@ public static string LoadSettingsFromDefault_Message { } } + /// + /// Looks up a localized string similar to Locale manifest matching with input locale "{0}" already exists. + /// + public static string LocaleAlreadyExists_ErrorMessage { + get { + return ResourceManager.GetString("LocaleAlreadyExists_ErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Manifest for input locale "{0}" does not exist. . + /// + public static string LocaleDoesNotExist_Message { + get { + return ResourceManager.GetString("LocaleDoesNotExist_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Locale ({0}) Manifest:. /// @@ -1771,29 +1906,29 @@ public static string NetworkConnectionFailure_Message { } /// - /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default) value.. + /// Looks up a localized string similar to This command will walk you through a series of questions to help you create your package manifest.. /// - public static string NewCommand_Description { + public static string NewCommand_Header { get { - return ResourceManager.GetString("NewCommand_Description", resourceCulture); + return ResourceManager.GetString("NewCommand_Header", resourceCulture); } } /// - /// Looks up a localized string similar to This tool will walk you through a series of questions to help you create your package manifest.. + /// Looks up a localized string similar to Launches a series of questions to help generate a new manifest. /// - public static string NewCommand_Header { + public static string NewCommand_HelpText { get { - return ResourceManager.GetString("NewCommand_Header", resourceCulture); + return ResourceManager.GetString("NewCommand_HelpText", resourceCulture); } } /// - /// Looks up a localized string similar to Launches a series of questions to help generate a new manifest. + /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default) value.. /// - public static string NewCommand_HelpText { + public static string NewCommand_PrePrompt_Header { get { - return ResourceManager.GetString("NewCommand_HelpText", resourceCulture); + return ResourceManager.GetString("NewCommand_PrePrompt_Header", resourceCulture); } } @@ -1815,6 +1950,51 @@ public static string NewInstallerUrlMustMatchExisting_Message { } } + /// + /// Looks up a localized string similar to This command will walk you through a series of questions to help generate a new locale manifest.. + /// + public static string NewLocaleCommand_Header { + get { + return ResourceManager.GetString("NewLocaleCommand_Header", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launches a series of questions to help generate a new locale manifest. + /// + public static string NewLocaleCommand_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The package locale to create a new manifest for. If not provided, the tool will prompt for this value.. + /// + public static string NewLocaleCommand_Locale_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_Locale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (reference) value from reference locale manifest.. + /// + public static string NewLocaleCommand_PrePrompt_Header { + get { + return ResourceManager.GetString("NewLocaleCommand_PrePrompt_Header", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The version of the package to add a new locale for.. + /// + public static string NewLocaleCommand_Version_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_Version_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Submitting a manifest without any updated changes is not allowed. . /// @@ -1897,7 +2077,7 @@ public static string OutdatedVersionNotice_Message { } /// - /// Looks up a localized string similar to Output directory where the newly created manifests will be saved locally. + /// Looks up a localized string similar to The output directory to store the generated manifests locally.. /// public static string OutputDirectory_HelpText { get { @@ -1978,7 +2158,7 @@ public static string PackageIdentifier_KeywordDescription { } /// - /// Looks up a localized string similar to The package meta-data locale. + /// Looks up a localized string similar to The package meta-data locale |e.g. en-US|. /// public static string PackageLocale_KeywordDescription { get { @@ -2193,6 +2373,24 @@ public static string RateLimitExceeded_Message { } } + /// + /// Looks up a localized string similar to Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used.. + /// + public static string ReferenceLocale_HelpText { + get { + return ResourceManager.GetString("ReferenceLocale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The reference locale does not exist in the manifest.. + /// + public static string ReferenceLocaleNotFound_Error { + get { + return ResourceManager.GetString("ReferenceLocaleNotFound_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Input does not match the valid format pattern for this field.. /// @@ -2409,6 +2607,15 @@ public static string SelectAction_Message { } } + /// + /// Looks up a localized string similar to Select an existing locale. + /// + public static string SelectExistingLocale_Message { + get { + return ResourceManager.GetString("SelectExistingLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select the installer(s) from the zip archive:. /// @@ -2823,6 +3030,24 @@ public static string Switches_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to create the locale manifest instead?. + /// + public static string SwitchToNewLocaleFlow_Message { + get { + return ResourceManager.GetString("SwitchToNewLocaleFlow_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Would you like to update the locale manifest instead?. + /// + public static string SwitchToUpdateLocaleFlow_Message { + get { + return ResourceManager.GetString("SwitchToUpdateLocaleFlow_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to create a reference to the forked repository. This can be caused when the forked repository is behind by too many commits. Sync your fork and try again.. /// @@ -2985,6 +3210,24 @@ public static string Update_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to modify additional locale properties?. + /// + public static string UpdateAdditionalLocaleProperties_Message { + get { + return ResourceManager.GetString("UpdateAdditionalLocaleProperties_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Would you like to update another locale?. + /// + public static string UpdateAnotherLocale_Message { + get { + return ResourceManager.GetString("UpdateAnotherLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to The upgrade method. /// @@ -3003,6 +3246,33 @@ public static string UpdateCommand_HelpText { } } + /// + /// Looks up a localized string similar to Launches a series of questions to help update an existing locale manifest. + /// + public static string UpdateLocaleCommand_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from.. + /// + public static string UpdateLocaleCommand_Locale_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_Locale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The version of the package to update the locale for.. + /// + public static string UpdateLocaleCommand_Version_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_Version_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Updating {0} of {1} installers:. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 67a26139..2f21bc7a 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -312,11 +312,11 @@ The package name |e.g. Visual Studio| - + Press ENTER to submit the value for each question including accepting the (default) value. - This tool will walk you through a series of questions to help you create your package manifest. + This command will walk you through a series of questions to help you create your package manifest. Launches a series of questions to help generate a new manifest @@ -325,13 +325,13 @@ Please check if the provided package arguments are correct. - Output directory where the newly created manifests will be saved locally + The output directory to store the generated manifests locally. The MSIX installer package family name - The package meta-data locale + The package meta-data locale |e.g. en-US| The package home page @@ -1234,9 +1234,103 @@ The following commands are available: + + Generating a preview of the created locale(s).... + + + Would you like to add additional locale properties? + + + Would you like to create another locale? + + + Default locale + + + Add a new locale for the latest version of a package + + + Add a new locale for a specific version of a package + + + Create a new locale and save the generated manifests to a specified directory + + + Update existing locale and save the generated manifests to a specified directory + + + Update existing locale for the latest version of a package + + + Update existing locale for a specific version of a package + + + Generating a preview of the updated locale(s).... + + + Invalid locale. Enter a valid language tag. + + + Locale manifest matching with input locale "{0}" already exists. Use the update-locale command to update an existing locale manifest. + {0} - represents the input package locale string + + + The version of the package to add a new locale for. + + + Select an existing locale + + + Would you like to modify additional locale properties? + + + Would you like to update another locale? + + + The version of the package to update the locale for. + + + Launches a series of questions to help generate a new locale manifest + + + Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used. + + + Launches a series of questions to help update an existing locale manifest + + + The reference locale does not exist in the manifest. + + + [{0}] value is: {1} + {0} - represents the name of the manifest field +{1} - represents the value of the manifest field + + + The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from. + + + The package locale to create a new manifest for. If not provided, the tool will prompt for this value. + + + Manifest for input locale "{0}" does not exist. Use the new-locale command to generate a new locale manifest. + {0} - represents the package locale string + + + Would you like to create the locale manifest instead? + + + Would you like to update the locale manifest instead? + Indicates whether the installer is prohibited from being downloaded for offline installation + + This command will walk you through a series of questions to help generate a new locale manifest. + + + Press ENTER to submit the value for each question including accepting the (reference) value from reference locale manifest. + Submit arguments were provided. Did you forget to include the --submit, -s flag? '--submit, -s' refers to a command line switch argument diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 6ff6ce04..25ecae77 100644 --- a/src/WingetCreateCore/Common/Constants.cs +++ b/src/WingetCreateCore/Common/Constants.cs @@ -62,5 +62,10 @@ public static class Constants /// Represents the subdirectory name of the user's local app data folder where the tool stores its debug data. /// public const string DiagnosticOutputDirectoryFolderName = "DiagOutputDir"; + + /// + /// The url path to the manifest documentation site. + /// + public const string ManifestDocumentationUrl = "https://aka.ms/winget-manifest-schema"; } } diff --git a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs index 8b59d35c..3e446705 100644 --- a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs +++ b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs @@ -3,12 +3,86 @@ namespace Microsoft.WingetCreateCore.Models.DefaultLocale { + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; using YamlDotNet.Serialization; + /// + /// Partial class that extends the property definitions of DefaultLocaleManifest. + /// + public partial class DefaultLocaleManifest + { + /// + /// Method to convert a DefaultLocaleManifest to a LocaleManifest. + /// + /// Object model representing the locale manifest. + public LocaleManifest ToLocaleManifest() + { + LocaleManifest localeManifest = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); + localeManifest.ManifestType = "locale"; + return localeManifest; + } + } + + /// + /// Partial class that implements helper methods for the Icon class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Icon +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Icon objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Icon other = (Icon)obj; + return this.IconUrl == other.IconUrl + && this.IconResolution == other.IconResolution + && this.IconSha256 == other.IconSha256 + && this.IconFileType == other.IconFileType + && this.IconTheme == other.IconTheme; + } + } + + /// + /// Partial class that implements helper methods for the Documentation class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Documentation +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Documentation objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Documentation other = (Documentation)obj; + return this.DocumentUrl == other.DocumentUrl + && this.DocumentLabel == other.DocumentLabel; + } + } + /// /// Partial class that extends the property definitions of Agreement. /// +#pragma warning disable SA1402 // File may only contain a single type public partial class Agreement +#pragma warning restore SA1402 // File may only contain a single type { /// Gets or sets the agreement text content. [YamlMember(Alias = "Agreement")] diff --git a/src/WingetCreateCore/Models/Partials/LocalePartials.cs b/src/WingetCreateCore/Models/Partials/LocalePartials.cs index 7cade590..732db296 100644 --- a/src/WingetCreateCore/Models/Partials/LocalePartials.cs +++ b/src/WingetCreateCore/Models/Partials/LocalePartials.cs @@ -3,12 +3,84 @@ namespace Microsoft.WingetCreateCore.Models.Locale { + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Newtonsoft.Json; using YamlDotNet.Serialization; + /// + /// Partial class that extends the property definitions of LocaleManifest. + /// + public partial class LocaleManifest + { + /// + /// Explicit cast from DefaultLocaleManifest to a LocaleManifest. + /// + /// Represents the default locale manifest. + public static explicit operator LocaleManifest(DefaultLocaleManifest defaultLocaleManifest) + { + return defaultLocaleManifest.ToLocaleManifest(); + } + } + + /// + /// Partial class that implements helper methods for the Icon class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Icon +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Icon objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Icon other = (Icon)obj; + return this.IconUrl == other.IconUrl + && this.IconResolution == other.IconResolution + && this.IconSha256 == other.IconSha256 + && this.IconFileType == other.IconFileType + && this.IconTheme == other.IconTheme; + } + } + + /// + /// Partial class that implements helper methods for the Documentation class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Documentation +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Documentation objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Documentation other = (Documentation)obj; + return this.DocumentUrl == other.DocumentUrl + && this.DocumentLabel == other.DocumentLabel; + } + } + /// /// Partial class that extends the property definitions of Agreement. /// +#pragma warning disable SA1402 // File may only contain a single type public partial class Agreement +#pragma warning restore SA1402 // File may only contain a single type { /// Gets or sets the agreement text content. [YamlMember(Alias = "Agreement")] diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml new file mode 100644 index 00000000..26315741 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml @@ -0,0 +1,39 @@ +PackageIdentifier: TestPublisher.LocaleConversionTest.yaml +PackageVersion: 7.4.0.0 +PackageLocale: en-US +Publisher: fakePublisher +PublisherUrl: https://fakePublisherUrl.com/ +PublisherSupportUrl: https://fakePublisherSupportUrl.com/ +PrivacyUrl: https://fakePrivacyUrl.com/ +Author: fakeAuthor +PackageName: fakePackageName +PackageUrl: https://fakePackageUrl.com/ +License: fakeLicense +LicenseUrl: https://fakeLicenseUrl.com/ +Copyright: fakeCopyright +CopyrightUrl: fakeCopyrightUrl +Description: |- + fakeExtended description of a fake package. +Moniker: fakeMoniker +Tags: +- fakeTag1 +- fakeTag2 +- fakeTag3 +Icons: + - IconUrl: https://fakeIconUrl.com/ + IconFileType: png + - IconUrl: https://fakeIconUrl2.com/ + IconFileType: jpeg +ReleaseNotes: |- + fakeReleaseNotes of a fake package. +ReleaseNotesUrl: https://fakeReleaseNotesUrl.com/ +PurchaseUrl: https://fakePurchaseUrl.com/ +InstallationNotes: |- + fakeInstallationNotes of a fake package. +Documentations: +- DocumentLabel: fakeDocumentation1 + DocumentUrl: https://fakeDocumentationUrl1.com/ +- DocumentLabel: fakeDocumentation2 + DocumentUrl: https://fakeDocumentationUrl2.com/ +ManifestType: defaultLocale +ManifestVersion: 1.5.0 diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs new file mode 100644 index 00000000..00e62e91 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateUnitTests +{ + using System; + using Microsoft.WingetCreateCLI; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Microsoft.WingetCreateCore.Models.Locale; + using Microsoft.WingetCreateTests; + using NUnit.Framework; + + /// + /// Test cases for commands that deal with creating/updating locales. + /// + public class LocaleCommandsTests + { + /// + /// Checks whether locale validations are working as expected. + /// + [Test] + public void VerifyLocaleValidations() + { + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.MsixTest"); + Manifests manifests = Serialization.DeserializeManifestContents(initialManifestContent); + + // Verify with different casing. + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-us", manifests), manifests.DefaultLocaleManifest, "The locale manifest for en-US should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-us", manifests), "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-USA", manifests), manifests.DefaultLocaleManifest, "The locale manifest for en-US should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-USA", manifests), "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-GB", manifests), manifests.LocaleManifests[0], "The locale manifest for en-GB should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-GB", manifests), "Locale for en-GB already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-gb", manifests), manifests.LocaleManifests[0], "Locale for en-GB already exists in the manifest."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-gb", manifests), "Locale for en-GB already exists in the manifest."); + + // Check for non-existent locales. + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("fr-FR", manifests), null, "Null should be returned as the locale manifest for fr-FR does not exist."); + Assert.IsFalse(LocaleHelper.DoesLocaleManifestExist("fr-FR", manifests), "Locale for fr-FR does not exist in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("de-DE", manifests), null, "Null should be returned as the locale manifest for de-DE does not exist."); + Assert.IsFalse(LocaleHelper.DoesLocaleManifestExist("de-DE", manifests), "Locale for de-DE does not exist in the manifest."); + + // Check for invalid locale formats. + Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("en", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.DoesLocaleManifestExist("en", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("fr_FR", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.DoesLocaleManifestExist("fr_FR", manifests), "Locale is not in the correct format."); + } + + /// + /// Tests the ability to convert a DefaultLocaleManifest to a LocaleManifest. + /// + [Test] + public void VerifyDefaultLocaleToLocaleManifestTypeConversion() + { + var manifestContent = TestUtils.GetInitialManifestContent("TestPublisher.LocaleConversionTest.yaml"); + Manifests manifest = Serialization.DeserializeManifestContents(manifestContent); + + DefaultLocaleManifest defaultLocale = manifest.DefaultLocaleManifest; + LocaleManifest localeManifest = defaultLocale.ToLocaleManifest(); + + Assert.IsTrue(localeManifest.ManifestType == "locale", "The manifest type should be locale."); + + Assert.AreEqual(defaultLocale.PackageIdentifier, localeManifest.PackageIdentifier, "The package identifier should be the same."); + Assert.AreEqual(defaultLocale.PackageVersion, localeManifest.PackageVersion, "The package version should be the same."); + Assert.AreEqual(defaultLocale.PackageLocale, localeManifest.PackageLocale, "The package locale should be the same."); + Assert.AreEqual(defaultLocale.Publisher, localeManifest.Publisher, "The publisher should be the same."); + Assert.AreEqual(defaultLocale.PublisherUrl, localeManifest.PublisherUrl, "The publisher URL should be the same."); + Assert.AreEqual(defaultLocale.PublisherSupportUrl, localeManifest.PublisherSupportUrl, "The publisher support URL should be the same."); + Assert.AreEqual(defaultLocale.PrivacyUrl, localeManifest.PrivacyUrl, "The privacy URL should be the same."); + Assert.AreEqual(defaultLocale.Author, localeManifest.Author, "The author should be the same."); + Assert.AreEqual(defaultLocale.PackageName, localeManifest.PackageName, "The package name should be the same."); + Assert.AreEqual(defaultLocale.PackageUrl, localeManifest.PackageUrl, "The package URL should be the same."); + Assert.AreEqual(defaultLocale.License, localeManifest.License, "The license should be the same."); + Assert.AreEqual(defaultLocale.LicenseUrl, localeManifest.LicenseUrl, "The license URL should be the same."); + Assert.AreEqual(defaultLocale.Copyright, localeManifest.Copyright, "The copyright should be the same."); + Assert.AreEqual(defaultLocale.CopyrightUrl, localeManifest.CopyrightUrl, "The copyright URL should be the same."); + Assert.AreEqual(defaultLocale.ShortDescription, localeManifest.ShortDescription, "The short description should be the same."); + Assert.AreEqual(defaultLocale.Description, localeManifest.Description, "The description should be the same."); + Assert.AreEqual(defaultLocale.Tags, localeManifest.Tags, "The tags should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconUrl, localeManifest.Icons[0].IconUrl, "First icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconResolution, localeManifest.Icons[0].IconResolution, "First icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconSha256, localeManifest.Icons[0].IconSha256, "First icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconFileType.ToString(), localeManifest.Icons[0].IconFileType.ToString(), "First icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconTheme, localeManifest.Icons[0].IconTheme, "First icon theme should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconUrl, localeManifest.Icons[1].IconUrl, "Second icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconResolution, localeManifest.Icons[1].IconResolution, "Second icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconSha256, localeManifest.Icons[1].IconSha256, "Second icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconFileType.ToString(), localeManifest.Icons[1].IconFileType.ToString(), "Second icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconTheme, localeManifest.Icons[1].IconTheme, "Second icon theme should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotes, localeManifest.ReleaseNotes, "The release notes should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotesUrl, localeManifest.ReleaseNotesUrl, "The release notes URL should be the same."); + Assert.AreEqual(defaultLocale.InstallationNotes, localeManifest.InstallationNotes, "The installation notes should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentUrl, localeManifest.Documentations[0].DocumentUrl, "First document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentLabel, localeManifest.Documentations[0].DocumentLabel, "First document label should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentUrl, localeManifest.Documentations[1].DocumentUrl, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentLabel, localeManifest.Documentations[1].DocumentLabel, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.ManifestVersion, localeManifest.ManifestVersion, "The manifest version should be the same."); + + // Skipped due to bad conversion model from schema. + // Assert.AreEqual(defaultLocale.Agreements, localeManifest.Agreements, "The agreements should be the same."); + } + } +} diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index a21a583a..fd6434bd 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -39,6 +39,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest