From 853a8bf2477f13a52852e9941e190ff47fd7a210 Mon Sep 17 00:00:00 2001 From: Ryan Fu <69221034+ryfu-msft@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:40:36 -0700 Subject: [PATCH] Add modify feature to interactive update (#171) * initial commit of interactive modify * Finish implementation of interactive modify * remove unneeded using statement * addres PR comments * update architecture * use getfilename in basecommand.cs --- src/WingetCreateCLI/Commands/BaseCommand.cs | 17 +- src/WingetCreateCLI/Commands/NewCommand.cs | 124 +-------- src/WingetCreateCLI/Commands/UpdateCommand.cs | 58 ++++- src/WingetCreateCLI/PromptHelper.cs | 235 +++++++++++++++--- .../Properties/Resources.Designer.cs | 27 ++ src/WingetCreateCLI/Properties/Resources.resx | 9 + src/WingetCreateCore/Common/PackageParser.cs | 32 ++- src/WingetCreateCore/Common/Serialization.cs | 11 + src/WingetCreateCore/Models/Manifests.cs | 53 ++-- 9 files changed, 363 insertions(+), 203 deletions(-) diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 35a15618..906208d1 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -148,7 +148,6 @@ protected static string SaveManifestDirToLocalPath( DefaultLocaleManifest defaultLocaleManifest = manifests.DefaultLocaleManifest; List localeManifests = manifests.LocaleManifests; - string defaultPackageLocale = defaultLocaleManifest.PackageLocale; string version = versionManifest.PackageVersion; string packageId = versionManifest.PackageIdentifier; string manifestDir = Utils.GetAppManifestDirPath(packageId, version); @@ -167,9 +166,9 @@ protected static string SaveManifestDirToLocalPath( fullDirPath = Path.Combine(outputDir, manifestDir); } - string versionManifestFileName = $"{packageId}.yaml"; - string installerManifestFileName = $"{packageId}.installer.yaml"; - string defaultLocaleManifestFileName = $"{packageId}.locale.{defaultPackageLocale}.yaml"; + string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest); + string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest); + string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest); File.WriteAllText(Path.Combine(fullDirPath, versionManifestFileName), versionManifest.ToYaml()); File.WriteAllText(Path.Combine(fullDirPath, installerManifestFileName), installerManifest.ToYaml()); @@ -177,7 +176,7 @@ protected static string SaveManifestDirToLocalPath( foreach (LocaleManifest localeManifest in localeManifests) { - string localeManifestFileName = $"{packageId}.locale.{localeManifest.PackageLocale}.yaml"; + string localeManifestFileName = Manifests.GetFileName(localeManifest); File.WriteAllText(Path.Combine(fullDirPath, localeManifestFileName), localeManifest.ToYaml()); } @@ -195,11 +194,9 @@ protected static string SaveManifestDirToLocalPath( /// A boolean value indicating whether validation of the manifests was successful. protected static bool ValidateManifestsInTempDir(Manifests manifests) { - string packageId = manifests.VersionManifest.PackageIdentifier; - string defaultPackageLocale = manifests.DefaultLocaleManifest.PackageLocale; - string versionManifestFileName = $"{packageId}.yaml"; - string installerManifestFileName = $"{packageId}.installer.yaml"; - string defaultLocaleManifestFileName = $"{packageId}.locale.{defaultPackageLocale}.yaml"; + string versionManifestFileName = Manifests.GetFileName(manifests.VersionManifest); + string installerManifestFileName = Manifests.GetFileName(manifests.InstallerManifest); + string defaultLocaleManifestFileName = Manifests.GetFileName(manifests.DefaultLocaleManifest); string randomDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(randomDirPath); diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index da339044..39cc0e01 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -201,7 +201,7 @@ private static void PromptPropertiesAndDisplayManifests(Manifests manifests) if (Prompt.Confirm(Resources.ModifyOptionalInstallerFields_Message)) { - DisplayInstallersAsMenuSelection(manifests.InstallerManifest); + PromptHelper.DisplayInstallersAsMenuSelection(manifests.InstallerManifest); } Console.WriteLine(); @@ -252,128 +252,6 @@ private static void PromptRequiredProperties(T manifest, VersionManifest vers } } - /// - /// Displays all installers from an Installer manifest as a selection menu. - /// - private static void DisplayInstallersAsMenuSelection(InstallerManifest installerManifest) - { - Console.Clear(); - - while (true) - { - List selectionList = GenerateInstallerSelectionList(installerManifest.Installers, out Dictionary installerSelectionMap); - var selectedItem = Prompt.Select(Resources.SelectInstallerToEdit_Message, selectionList); - - if (selectedItem == Resources.None_MenuItem) - { - break; - } - else if (selectedItem == Resources.AllInstallers_MenuItem) - { - Installer installerCopy = new Installer(); - PromptHelper.PromptPropertiesWithMenu(installerCopy, Resources.None_MenuItem); - ApplyChangesToIndividualInstallers(installerCopy, installerManifest.Installers); - } - else if (selectedItem == Resources.DisplayPreview_MenuItem) - { - Console.Clear(); - Console.WriteLine(); - Logger.InfoLocalized(nameof(Resources.DisplayPreviewOfSelectedInstaller_Message)); - var serializer = Serialization.CreateSerializer(); - string installerString = serializer.Serialize(installerManifest); - Console.WriteLine(installerString); - Console.WriteLine(); - } - else - { - Installer selectedInstaller = installerSelectionMap[selectedItem]; - PromptHelper.PromptPropertiesWithMenu(selectedInstaller, Resources.None_MenuItem); - } - } - } - - private static List GenerateInstallerSelectionList(List installers, out Dictionary installerSelectionMap) - { - installerSelectionMap = new Dictionary(); - int index = 1; - foreach (Installer installer in installers) - { - var installerTuple = string.Join(" | ", new[] - { - installer.Architecture.ToEnumAttributeValue(), - installer.InstallerType.ToEnumAttributeValue(), - installer.Scope?.ToEnumAttributeValue(), - installer.InstallerLocale, - installer.InstallerUrl, - }.Where(s => !string.IsNullOrEmpty(s))); - - var installerMenuItem = string.Format(Resources.InstallerSelection_MenuItem, index, installerTuple); - installerSelectionMap.Add(installerMenuItem, installer); - index++; - } - - List selectionList = new List() { Resources.AllInstallers_MenuItem }; - selectionList.AddRange(installerSelectionMap.Keys); - selectionList.AddRange(new[] { Resources.DisplayPreview_MenuItem, Resources.None_MenuItem }); - return selectionList; - } - - private static void ApplyChangesToIndividualInstallers(Installer installerCopy, List installers) - { - // Skip architecture as the default value when instantiated is x86. - var modifiedFields = installerCopy.GetType().GetProperties() - .Select(prop => prop) - .Where(pi => - pi.GetValue(installerCopy) != null && - pi.Name != nameof(Installer.Architecture) && - pi.Name != nameof(Installer.AdditionalProperties)); - - foreach (var field in modifiedFields) - { - foreach (Installer installer in installers) - { - var fieldValue = field.GetValue(installerCopy); - var prop = installer.GetType().GetProperty(field.Name); - if (prop.PropertyType.IsValueType) - { - prop.SetValue(installer, fieldValue); - } - else if (fieldValue is IList list) - { - prop.SetValue(installer, list.DeepClone()); - } - else if (fieldValue is Dependencies dependencies) - { - ApplyDependencyChangesToInstaller(dependencies, installer); - } - } - } - } - - /// - /// Clones any non-null property values of the dependencies object and assigns them to the provided installer object. - /// - /// Dependencies object with new values. - /// Installer object to assign new changes to. - private static void ApplyDependencyChangesToInstaller(Dependencies dependencies, Installer installer) - { - var modifiedFields = dependencies.GetType().GetProperties() - .Select(prop => prop) - .Where(pi => pi.GetValue(dependencies) != null); - - foreach (var field in modifiedFields.Where(f => f.Name != nameof(Installer.AdditionalProperties))) - { - var fieldValue = field.GetValue(dependencies); - installer.Dependencies ??= new Dependencies(); - var prop = installer.Dependencies.GetType().GetProperty(field.Name); - - if (fieldValue is IList list) - { - prop.SetValue(installer.Dependencies, list.DeepClone()); - } - } - } - private static void PromptOptionalProperties(T manifest) { var properties = manifest.GetType().GetProperties().ToList(); diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index 25cb91b1..99309cfc 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -370,6 +370,54 @@ private static bool VerifyUpdatedInstallerHash(Manifests oldManifest, InstallerM return newHashes.Except(oldHashes).Any(); } + private static void DisplayManifestsAsMenuSelection(Manifests manifests) + { + Console.Clear(); + string versionFileName = Manifests.GetFileName(manifests.VersionManifest); + string installerFileName = Manifests.GetFileName(manifests.InstallerManifest); + 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); + 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); + localeManifestMap.Add(localeManifestFileName, localeManifest); + selectionList.Add(localeManifestFileName); + } + + selectionList.Add(Resources.Done_MenuItem); + var selectedItem = Prompt.Select(Resources.SelectManifestToEdit_Message, selectionList); + + if (selectedItem == versionManifestMenuItem) + { + PromptHelper.PromptPropertiesWithMenu(manifests.VersionManifest, Resources.SaveAndExit_MenuItem, versionFileName); + } + else if (selectedItem == installerManifestMenuItem) + { + PromptHelper.PromptPropertiesWithMenu(manifests.InstallerManifest, Resources.SaveAndExit_MenuItem, installerFileName); + } + else if (selectedItem == defaultLocaleMenuItem) + { + PromptHelper.PromptPropertiesWithMenu(manifests.DefaultLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(manifests.DefaultLocaleManifest)); + } + else if (selectedItem == Resources.Done_MenuItem) + { + break; + } + else + { + var selectedLocaleManifest = localeManifestMap[selectedItem]; + PromptHelper.PromptPropertiesWithMenu(selectedLocaleManifest, Resources.SaveAndExit_MenuItem, Manifests.GetFileName(selectedLocaleManifest)); + } + } + } + /// /// Update flow for interactively updating the manifest. /// s @@ -393,7 +441,12 @@ private async Task UpdateManifestsInteractively(Manifests manifests) ValidateManifestsInTempDir(manifests); } while (Prompt.Confirm(Resources.DiscardUpdateAndStartOver_Message)); - Console.Clear(); + + if (Prompt.Confirm(Resources.EditManifests_Message)) + { + DisplayManifestsAsMenuSelection(manifests); + } + return manifests; } @@ -404,14 +457,13 @@ private async Task UpdateManifestsInteractively(Manifests manifests) /// A representing the asynchronous operation. private async Task UpdateInstallersInteractively(List existingInstallers) { - var serializer = Serialization.CreateSerializer(); int numOfExistingInstallers = existingInstallers.Count; int index = 1; foreach (var installer in existingInstallers) { Logger.InfoLocalized(nameof(Resources.UpdatingInstallerOutOfTotal_Message), index, numOfExistingInstallers); - Console.WriteLine(serializer.Serialize(installer)); + Console.WriteLine(Serialization.Serialize(installer)); await this.UpdateSingleInstallerInteractively(installer); index++; } diff --git a/src/WingetCreateCLI/PromptHelper.cs b/src/WingetCreateCLI/PromptHelper.cs index 59eb0b08..7fb8cada 100644 --- a/src/WingetCreateCLI/PromptHelper.cs +++ b/src/WingetCreateCLI/PromptHelper.cs @@ -22,6 +22,14 @@ namespace Microsoft.WingetCreateCLI /// public static class PromptHelper { + private static readonly string[] NonEditableRequiredFields = new[] + { + nameof(InstallerManifest.PackageIdentifier), + nameof(InstallerManifest.PackageVersion), + nameof(InstallerManifest.ManifestType), + nameof(InstallerManifest.ManifestVersion), + }; + /// /// List of strings representing the optional fields that should not be editable. /// @@ -61,36 +69,15 @@ public static class PromptHelper /// Model type. /// Instance of object model. /// Exit keyword to be shown to the user to exit the navigational menu. - public static void PromptPropertiesWithMenu(T model, string exitMenuWord) + /// If a non-null string is provided, a menu item to display a preview of the model will be added to the selection list. + public static void PromptPropertiesWithMenu(T model, string exitMenuWord, string modelName = null) { Console.Clear(); - - var properties = model.GetType().GetProperties().ToList(); - var optionalProperties = properties.Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null).ToList(); - - var fieldList = properties - .Select(property => property.Name) - .Where(pName => !NonEditableOptionalFields.Any(field => field == pName)).ToList(); - - var installerTypeProperty = model.GetType().GetProperty(nameof(InstallerType)); - if (installerTypeProperty != null) + var properties = model.GetType().GetProperties(); + var fieldList = FilterPropertiesAndCreateSelectionList(model, properties); + if (!string.IsNullOrEmpty(modelName)) { - var installerTypeValue = installerTypeProperty.GetValue(model); - if (installerTypeValue != null) - { - var installerType = (InstallerType)installerTypeValue; - if (installerType == InstallerType.Msi || installerType == InstallerType.Exe) - { - fieldList.Add(nameof(Installer.ProductCode)); - } - else if (installerType == InstallerType.Msix || installerType == InstallerType.Appx) - { - fieldList = fieldList.Where(pName => !MsixExclusionList.Any(field => field == pName)).ToList(); - fieldList.AddRange(MsixInclusionList); - } - } + fieldList.Add(Resources.DisplayPreview_MenuItem); } fieldList.Add(exitMenuWord); @@ -105,9 +92,62 @@ public static void PromptPropertiesWithMenu(T model, string exitMenuWord) { break; } + else if (selectedField == Resources.DisplayPreview_MenuItem) + { + Console.Clear(); + Console.WriteLine(); + Logger.InfoLocalized(nameof(Resources.DisplayPreviewOfItems), modelName); + Console.WriteLine(Serialization.Serialize(model)); + Console.WriteLine(); + } + else if (selectedField == nameof(InstallerManifest.Installers)) + { + DisplayInstallersAsMenuSelection(model as InstallerManifest); + } + else + { + var selectedProperty = properties.First(p => p.Name == selectedField); + PromptPropertyAndSetValue(model, selectedField, selectedProperty.GetValue(model)); + } + } + } - var selectedProperty = properties.First(p => p.Name == selectedField); - PromptPropertyAndSetValue(model, selectedField, selectedProperty.GetValue(model)); + /// + /// Displays all installers from an Installer manifest as a selection menu. + /// + /// Installer manifest model. + public static void DisplayInstallersAsMenuSelection(InstallerManifest installerManifest) + { + Console.Clear(); + + while (true) + { + List selectionList = GenerateInstallerSelectionList(installerManifest.Installers, out Dictionary installerSelectionMap); + var selectedItem = Prompt.Select(Resources.SelectInstallerToEdit_Message, selectionList); + + if (selectedItem == Resources.SaveAndExit_MenuItem) + { + break; + } + else if (selectedItem == Resources.AllInstallers_MenuItem) + { + Installer installerCopy = new Installer(); + PromptPropertiesWithMenu(installerCopy, Resources.SaveAndExit_MenuItem); + ApplyChangesToIndividualInstallers(installerCopy, installerManifest.Installers); + } + else if (selectedItem == Resources.DisplayPreview_MenuItem) + { + Console.Clear(); + Console.WriteLine(); + Logger.InfoLocalized(nameof(Resources.DisplayPreviewOfSelectedInstaller_Message)); + Console.WriteLine(Serialization.Serialize(installerManifest)); + Console.WriteLine(); + } + else + { + Installer selectedInstaller = installerSelectionMap[selectedItem]; + PromptPropertiesWithMenu(selectedInstaller, Resources.SaveAndExit_MenuItem, selectedItem.Split(':')[0]); + } } } @@ -269,6 +309,138 @@ public static void PromptList(string message, object model, string memberName } } + /// + /// Creates a filtered list of strings representing the fields to be shown in a selection menu. Filters out + /// non-editable fields as well as fields that are not relevant based on the installer type if present. + /// + /// Object type. + /// Object model. + /// Array of model properties. + /// List of strings representing the properties to be shown in the selection menu. + private static List FilterPropertiesAndCreateSelectionList(T model, PropertyInfo[] properties) + { + var fieldList = properties + .Select(property => property.Name) + .Where(pName => + !NonEditableOptionalFields.Any(field => field == pName) && + !NonEditableRequiredFields.Any(field => field == pName)).ToList(); + + // Filter out fields if an installerType is present + var installerTypeProperty = model.GetType().GetProperty(nameof(InstallerType)); + if (installerTypeProperty != null) + { + var installerTypeValue = installerTypeProperty.GetValue(model); + if (installerTypeValue != null) + { + var installerType = (InstallerType)installerTypeValue; + if (installerType == InstallerType.Msi || installerType == InstallerType.Exe) + { + fieldList.Add(nameof(Installer.ProductCode)); + } + else if (installerType == InstallerType.Msix || installerType == InstallerType.Appx) + { + fieldList = fieldList.Where(pName => !MsixExclusionList.Any(field => field == pName)).ToList(); + fieldList.AddRange(MsixInclusionList); + } + } + } + + return fieldList; + } + + /// + /// Creates a list of strings representing the installer nodes to be shown in a selection menu. + /// + /// List of installers. + /// Installer dictionary that maps the installer menu item string to the installer object model. + /// List of strings representing the installers to be shown in the selection menu. + private static List GenerateInstallerSelectionList(List installers, out Dictionary installerSelectionMap) + { + installerSelectionMap = new Dictionary(); + int index = 1; + foreach (Installer installer in installers) + { + var installerTuple = string.Join(" | ", new[] + { + installer.Architecture.ToEnumAttributeValue(), + installer.InstallerType.ToEnumAttributeValue(), + installer.Scope?.ToEnumAttributeValue(), + installer.InstallerLocale, + installer.InstallerUrl, + }.Where(s => !string.IsNullOrEmpty(s))); + + var installerMenuItem = string.Format(Resources.InstallerSelection_MenuItem, index, installerTuple); + installerSelectionMap.Add(installerMenuItem, installer); + index++; + } + + List selectionList = new List() { Resources.AllInstallers_MenuItem }; + selectionList.AddRange(installerSelectionMap.Keys); + selectionList.AddRange(new[] { Resources.DisplayPreview_MenuItem, Resources.SaveAndExit_MenuItem }); + return selectionList; + } + + /// + /// Applies all installerCopy changes to each installer in the List of installers. + /// + /// Installer object model with new changes. + /// List of installers receiving the new changes. + private static void ApplyChangesToIndividualInstallers(Installer installerCopy, List installers) + { + // Skip architecture as the default value when instantiated is x86. + var modifiedFields = installerCopy.GetType().GetProperties() + .Select(prop => prop) + .Where(pi => + pi.GetValue(installerCopy) != null && + pi.Name != nameof(Installer.Architecture) && + pi.Name != nameof(Installer.AdditionalProperties)); + + foreach (var field in modifiedFields) + { + foreach (Installer installer in installers) + { + var fieldValue = field.GetValue(installerCopy); + var prop = installer.GetType().GetProperty(field.Name); + if (prop.PropertyType.IsValueType) + { + prop.SetValue(installer, fieldValue); + } + else if (fieldValue is IList list) + { + prop.SetValue(installer, list.DeepClone()); + } + else if (fieldValue is Dependencies dependencies) + { + ApplyDependencyChangesToInstaller(dependencies, installer); + } + } + } + } + + /// + /// Clones any non-null property values of the dependencies object and assigns them to the provided installer object. + /// + /// Dependencies object with new values. + /// Installer object to assign new changes to. + private static void ApplyDependencyChangesToInstaller(Dependencies dependencies, Installer installer) + { + var modifiedFields = dependencies.GetType().GetProperties() + .Select(prop => prop) + .Where(pi => pi.GetValue(dependencies) != null); + + foreach (var field in modifiedFields.Where(f => f.Name != nameof(Installer.AdditionalProperties))) + { + var fieldValue = field.GetValue(dependencies); + installer.Dependencies ??= new Dependencies(); + var prop = installer.Dependencies.GetType().GetProperty(field.Name); + + if (fieldValue is IList list) + { + prop.SetValue(installer.Dependencies, list.DeepClone()); + } + } + } + private static void PromptEnum(object model, PropertyInfo property, Type enumType, bool required, string message, string defaultValue = null) { var enumList = Enum.GetNames(enumType); @@ -288,7 +460,6 @@ private static void PromptEnum(object model, PropertyInfo property, Type enumTyp private static void PromptForItemList(List items) where T : new() { - var serializer = Serialization.CreateSerializer(); string selection = string.Empty; while (selection != Resources.Back_MenuItem) { @@ -296,7 +467,7 @@ private static void PromptForItemList(List items) if (items.Any()) { Logger.InfoLocalized(nameof(Resources.DisplayPreviewOfItems), typeof(T).Name); - string serializedString = serializer.Serialize(items); + string serializedString = Serialization.Serialize(items); Console.WriteLine(serializedString); Console.WriteLine(); } @@ -328,7 +499,7 @@ private static void PromptForItemList(List items) private static void PromptSubfieldProperties(T field, PropertyInfo property, object model) { - PromptPropertiesWithMenu(field, Resources.None_MenuItem); + PromptPropertiesWithMenu(field, Resources.SaveAndExit_MenuItem); if (field.IsEmptyObject()) { property.SetValue(model, null); diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index faaf03a9..9698f21a 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -438,6 +438,15 @@ public static string DownloadInstaller_Message { } } + /// + /// Looks up a localized string similar to Would you like to edit your manifests?. + /// + public static string EditManifests_Message { + get { + return ResourceManager.GetString("EditManifests_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please enter values for the following fields:. /// @@ -1491,6 +1500,15 @@ public static string RetrievingManifest_Message { } } + /// + /// Looks up a localized string similar to SAVE AND EXIT. + /// + public static string SaveAndExit_MenuItem { + get { + return ResourceManager.GetString("SaveAndExit_MenuItem", resourceCulture); + } + } + /// /// Looks up a localized string similar to This is an enumeration of install scope (“User”, “System”). /// @@ -1518,6 +1536,15 @@ public static string SelectInstallerToEdit_Message { } } + /// + /// Looks up a localized string similar to Which manifest do you want to edit?. + /// + public static string SelectManifestToEdit_Message { + get { + return ResourceManager.GetString("SelectManifestToEdit_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Which property would you like to edit?. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 5407faba..3cd873e4 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -774,4 +774,13 @@ {0} - represents the installer index number {1} - represents the associated installer metadata + + Would you like to edit your manifests? + + + SAVE AND EXIT + + + Which manifest do you want to edit? + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/PackageParser.cs b/src/WingetCreateCore/Common/PackageParser.cs index 0d9c5482..49f6f1c2 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -305,13 +305,35 @@ public static bool ParsePackageAndUpdateInstallerNode(Installer installer, strin } Installer newInstaller = newInstallers.First(); + + if (newInstallers.Count > 1) + { + // For multiple installers in an AppxBundle, use the existing architecture to avoid matching conflicts. + newInstaller.Architecture = installer.Architecture; + } + else + { + // For a single installer, detect the architecture. If no architecture is detected, default to architecture from existing manifest. + newInstaller.Architecture = GetArchFromUrl(url) ?? GetMachineType(path)?.ToString().ToEnumOrDefault() ?? installer.Architecture; + } + newInstaller.InstallerUrl = url; newInstaller.InstallerSha256 = GetFileHash(path); - UpdateInstallerMetadata(installer, newInstallers.First()); return true; } + /// + /// Creates a new Installer object that is a copy of the provided Installer. + /// + /// Installer object to be cloned. + /// A new cloned Installer object. + public static Installer CloneInstaller(Installer installer) + { + string json = JsonConvert.SerializeObject(installer); + return JsonConvert.DeserializeObject(json); + } + /// /// Updates the metadata from an existing installer node with the metadata from a new installer node. /// @@ -319,6 +341,7 @@ public static bool ParsePackageAndUpdateInstallerNode(Installer installer, strin /// New installer node. private static void UpdateInstallerMetadata(Installer existingInstaller, Installer newInstaller) { + existingInstaller.Architecture = newInstaller.Architecture; existingInstaller.InstallerUrl = newInstaller.InstallerUrl; existingInstaller.InstallerSha256 = newInstaller.InstallerSha256; existingInstaller.SignatureSha256 = newInstaller.SignatureSha256; @@ -453,7 +476,6 @@ private static bool ParsePackageAndGenerateInstallerNodes( return archMatches.Count == 1 ? archMatches.Single() : null; } - /// /// Computes the SHA256 hash value for the specified byte array. /// @@ -650,12 +672,6 @@ private static string GetApplicationProperty(AppxMetadata appxMetadata, string p return null; } - private static Installer CloneInstaller(Installer installer) - { - string json = JsonConvert.SerializeObject(installer); - return JsonConvert.DeserializeObject(json); - } - private static void SetInstallerPropertiesFromAppxMetadata(AppxMetadata appxMetadata, Installer installer, InstallerManifest installerManifest) { installer.Architecture = appxMetadata.Architecture.ToEnumOrDefault() ?? InstallerArchitecture.Neutral; diff --git a/src/WingetCreateCore/Common/Serialization.cs b/src/WingetCreateCore/Common/Serialization.cs index 18f5b308..63c69eab 100644 --- a/src/WingetCreateCore/Common/Serialization.cs +++ b/src/WingetCreateCore/Common/Serialization.cs @@ -137,6 +137,17 @@ public static string ConvertYamlToJson(string yaml) return JsonConvert.SerializeObject(yamlObject); } + /// + /// Serializes the provided object and returns the serialized string. + /// + /// Object to be serialized. + /// Serialized string. + public static string Serialize(object value) + { + var serializer = CreateSerializer(); + return serializer.Serialize(value); + } + /// /// Deserializes a list of manifest strings into their appropriate object models. /// diff --git a/src/WingetCreateCore/Models/Manifests.cs b/src/WingetCreateCore/Models/Manifests.cs index 9dc25ccb..23126bee 100644 --- a/src/WingetCreateCore/Models/Manifests.cs +++ b/src/WingetCreateCore/Models/Manifests.cs @@ -3,6 +3,7 @@ namespace Microsoft.WingetCreateCore.Models { + using System; using System.Collections.Generic; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; @@ -40,36 +41,34 @@ public class Manifests /// public List LocaleManifests { get; set; } = new List(); + /// + /// Generates the proper file name for the given manifest. + /// + /// Manifest type. + /// Manifest object model. + /// File name string of manifest. + public static string GetFileName(T manifest) + { + 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", + _ => throw new ArgumentException(nameof(manifest)), + }; + } + + /// + /// Creates a new cloned list of Installers that is a copy of the current list of installers. + /// + /// A new cloned list of Installers. public List CloneInstallers() { List deepCopy = new List(); - - this.InstallerManifest.Installers.ForEach( - i => deepCopy.Add( - new Installer.Installer { - InstallerLocale = i.InstallerLocale, - Platform = i.Platform, - MinimumOSVersion = i.MinimumOSVersion, - Architecture = i.Architecture, - InstallerType = i.InstallerType, - Scope = i.Scope, - InstallerUrl = i.InstallerUrl, - InstallerSha256 = i.InstallerSha256, - SignatureSha256 = i.SignatureSha256, - InstallModes = i.InstallModes, - InstallerSwitches = i.InstallerSwitches, - InstallerSuccessCodes = i.InstallerSuccessCodes, - UpgradeBehavior = i.UpgradeBehavior, - Commands = i.Commands, - Protocols = i.Protocols, - FileExtensions = i.FileExtensions, - PackageFamilyName = i.PackageFamilyName, - ProductCode = i.ProductCode, - Capabilities = i.Capabilities, - RestrictedCapabilities = i.RestrictedCapabilities, - })); - + this.InstallerManifest.Installers.ForEach(i => deepCopy.Add(PackageParser.CloneInstaller(i))); return deepCopy; - } + } } }