diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index b3e9f437..d8ed5578 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) @@ -449,22 +457,51 @@ private static void PromptForPortableAliasIfApplicable(Installer installer) if (!string.IsNullOrEmpty(portableCommandAlias)) { - List portableCommands = new List { portableCommandAlias }; + List portableCommands = new List { portableCommandAlias.Trim() }; installer.Commands = portableCommands; } } if (installer.NestedInstallerType == NestedInstallerType.Portable) { - foreach (NestedInstallerFile nestedInstallerFile in installer.NestedInstallerFiles) + string portableCommandAlias = Prompt.Input(Resources.PortableCommandAlias_Message); + + if (!string.IsNullOrEmpty(portableCommandAlias)) { - string portableCommandAlias = Prompt.Input(Resources.PortableCommandAlias_Message); + installer.NestedInstallerFiles.First().PortableCommandAlias = portableCommandAlias.Trim(); + } + } + } - if (!string.IsNullOrEmpty(portableCommandAlias)) - { - nestedInstallerFile.PortableCommandAlias = portableCommandAlias; - } + /// + /// 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 nestedPortableInstallers = installerManifest.Installers.Where(i => i.NestedInstallerType == NestedInstallerType.Portable).ToList(); + var mergeableInstallersList = nestedPortableInstallers.GroupBy(i => i.Architecture + i.InstallerSha256).ToList(); + foreach (var 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); } } 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.