diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index b3e9f437..badbe943 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -172,15 +172,18 @@ public override async Task Execute() selectedInstallers = Prompt.MultiSelect(Resources.SelectInstallersFromZip_Message, extractedFiles, minimum: 1).ToList(); } - installerUpdateList.Add( + foreach (var installer in selectedInstallers) + { + installerUpdateList.Add( new InstallerMetadata { InstallerUrl = installerUrl, PackageFile = packageFile, - RelativeFilePaths = selectedInstallers, + RelativeFilePaths = new List { installer }, IsZipFile = true, ExtractedDirectory = extractDirectory, }); + } } else { @@ -270,6 +273,7 @@ private static void PromptPropertiesAndDisplayManifests(Manifests manifests) PromptRequiredProperties(manifests.VersionManifest); PromptRequiredProperties(manifests.InstallerManifest, manifests.VersionManifest); PromptRequiredProperties(manifests.DefaultLocaleManifest, manifests.VersionManifest); + MergeNestedInstallerFilesIfApplicable(manifests.InstallerManifest); Console.WriteLine(); if (Prompt.Confirm(Resources.ModifyOptionalDefaultLocaleFields_Message)) @@ -359,6 +363,12 @@ private static void PromptInstallerProperties(T manifest, PropertyInfo proper List installers = new List((ICollection)property.GetValue(manifest)); foreach (var installer in installers) { + Console.WriteLine(); + if (installer.InstallerType == InstallerType.Zip) + { + Logger.InfoLocalized(nameof(Resources.NestedInstallerParsing_HelpText), installer.NestedInstallerFiles.First().RelativeFilePath, installer.InstallerUrl); + } + var installerProperties = installer.GetType().GetProperties().ToList(); var requiredInstallerProperties = installerProperties @@ -369,8 +379,6 @@ private static void PromptInstallerProperties(T manifest, PropertyInfo proper // If the installerType is EXE, prompt the user for whether the package is a portable if (installer.InstallerType == InstallerType.Exe || installer.NestedInstallerType == NestedInstallerType.Exe) { - Console.WriteLine(); - if (!PromptForPortableExe(installer)) { // If we know the installertype is EXE, prompt the user for installer switches (silent and silentwithprogress) @@ -468,6 +476,69 @@ private static void PromptForPortableAliasIfApplicable(Installer installer) } } + /// + /// Retrieve nested portable installers that are compatible to have their NestedInstallerFiles merged. + /// + /// A list of lists where each list element contains installers that are applicable for merge. + private static List> GetNestedPortableInstallersForMerge(InstallerManifest installerManifest) + { + List> nestedPortablesCompatibleForMerge = new List>(); + + foreach (Installer installer in installerManifest.Installers) + { + if (installer.NestedInstallerType == NestedInstallerType.Portable) + { + // If the current installer has the same hash and architecture as another installer, add it to an existing list + // else create a new list + var matchingInstallers = nestedPortablesCompatibleForMerge.SingleOrDefault(i => i.Any(x => + x.Architecture.Equals(installer.Architecture) && + x.InstallerSha256.Equals(installer.InstallerSha256))); + + if (matchingInstallers != null) + { + matchingInstallers.Add(installer); + } + else + { + nestedPortablesCompatibleForMerge.Add(new List { installer }); + } + } + } + + return nestedPortablesCompatibleForMerge; + } + + /// + /// Merge nested installer files into a single installer if: + /// 1. Matching installers have NestedInstallerType: portable. + /// 2. Matching installers have the same architecture. + /// 3. Matching installers have the same hash. + /// + private static void MergeNestedInstallerFilesIfApplicable(InstallerManifest installerManifest) + { + var mergeableInstallersList = GetNestedPortableInstallersForMerge(installerManifest); + foreach (List installers in mergeableInstallersList) + { + // First installer in each list is used to merge into + var installerToMergeInto = installers.First(); + + // Remove the first installer from the manifest, will be added back once the merge is complete + installerManifest.Installers.Remove(installerToMergeInto); + + var installersToMerge = installers.Skip(1).ToList(); + + // Append NestedInstallerFiles of other matching installers and remove them from the manifest + foreach (var installer in installersToMerge) + { + installerToMergeInto.NestedInstallerFiles.AddRange(installer.NestedInstallerFiles); + installerManifest.Installers.Remove(installer); + } + + // Add the installer with the merged nested installer files back to the manifest + installerManifest.Installers.Add(installerToMergeInto); + } + } + /// /// Prompts for the package identifier and applies the value to all manifests. /// diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 13c42e75..5a45cd10 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1472,6 +1472,17 @@ public static string Name_KeywordDescription { return ResourceManager.GetString("Name_KeywordDescription", resourceCulture); } } + + /// + /// Looks up a localized string similar to Parsing nested installer '{0}' from {1}. + /// + public static string NestedInstallerParsing_HelpText + { + get + { + return ResourceManager.GetString("NestedInstallerParsing_HelpText", resourceCulture); + } + } /// /// Looks up a localized string similar to Nested installer not found in zip archive: {0}. diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index c75e4ac4..85cc85d5 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -958,6 +958,11 @@ The optional parameter for invocable files + + Parsing nested installer '{0}' from {1} + {0} - will be replaced with RelativeFilePath of the nested installer + {1} - will be replaced by the InstallerUrl + Nested installer not found in zip archive: {0} {0} - represents the relative file path of the nested installer file.