diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index d50d997e..e2bd101a 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -864,6 +864,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer) string url = Prompt.Input(Resources.NewInstallerUrl_Message, null, null, new[] { FieldValidation.ValidateProperty(newInstaller, nameof(Installer.InstallerUrl)) }); string packageFile = await DownloadPackageFile(url); + string archivePath = null; if (string.IsNullOrEmpty(packageFile)) { @@ -872,6 +873,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer) if (packageFile.IsZipFile()) { + archivePath = packageFile; string extractDirectory = ExtractArchiveAndRetrieveDirectoryPath(packageFile); bool isRelativePathNull = false; @@ -894,7 +896,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer) packageFile = Path.Combine(extractDirectory, installer.NestedInstallerFiles.First().RelativeFilePath); } - if (!PackageParser.ParsePackageAndUpdateInstallerNode(installer, packageFile, url)) + if (!PackageParser.ParsePackageAndUpdateInstallerNode(installer, packageFile, url, archivePath)) { Logger.ErrorLocalized(nameof(Resources.PackageParsing_Error), url); Console.WriteLine(); diff --git a/src/WingetCreateCore/Common/PackageParser.cs b/src/WingetCreateCore/Common/PackageParser.cs index 699ba692..fa90c3b6 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -277,15 +277,22 @@ public static void UpdateInstallerNodesAsync(List installerMe /// Parses the package for relevant metadata and and updates the metadata of the provided installer node. /// /// Installer node. - /// Path to package file. + /// Path to package file. /// Installer url. + /// Path to archive file containing the installer. Required if the installer type is Zip. /// Boolean indicating whether the package parse was successful. - public static bool ParsePackageAndUpdateInstallerNode(Installer installer, string path, string url) + public static bool ParsePackageAndUpdateInstallerNode(Installer installer, string filePath, string url, string archivePath = null) { + // Guard clause to ensure that the archivePath is provided if the installer type is Zip. + if (installer.InstallerType == InstallerType.Zip && string.IsNullOrEmpty(archivePath)) + { + return false; + } + List newInstallers = new List(); - bool parseResult = ParseExeInstallerType(path, installer, newInstallers) || - ParseMsix(path, installer, null, newInstallers) || - ParseMsi(path, installer, null, newInstallers); + bool parseResult = ParseExeInstallerType(filePath, installer, newInstallers) || + ParseMsix(filePath, installer, null, newInstallers) || + ParseMsi(filePath, installer, null, newInstallers); if (!parseResult || !newInstallers.Any()) { @@ -302,11 +309,11 @@ public static bool ParsePackageAndUpdateInstallerNode(Installer installer, strin 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.Architecture = GetArchFromUrl(url) ?? GetMachineType(filePath)?.ToString().ToEnumOrDefault() ?? installer.Architecture; } newInstaller.InstallerUrl = url; - newInstaller.InstallerSha256 = GetFileHash(path); + newInstaller.InstallerSha256 = string.IsNullOrEmpty(archivePath) ? GetFileHash(filePath) : GetFileHash(archivePath); UpdateInstallerMetadata(installer, newInstallers.First()); return true; } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/PackageParserTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/PackageParserTests.cs index 7483b471..798dd00f 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/PackageParserTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/PackageParserTests.cs @@ -3,8 +3,10 @@ namespace Microsoft.WingetCreateUnitTests { + using System; using System.Collections.Generic; using System.IO; + using System.IO.Compression; using System.Linq; using AutoMapper; using Microsoft.WingetCreateCore; @@ -157,13 +159,47 @@ public void ParseAndUpdateInstaller() WingetCreateCore.Models.Singleton.Installer initialInstaller = initialManifests.SingletonManifest.Installers.First(); Installer installer = ConvertSingletonInstaller(initialInstaller); - PackageParser.ParsePackageAndUpdateInstallerNode(installer, testMsiInstallerPath, installer.InstallerUrl); + bool result = PackageParser.ParsePackageAndUpdateInstallerNode(installer, testMsiInstallerPath, installer.InstallerUrl); + ClassicAssert.IsTrue(result, "ParsePackageAndUpdateInstallerNode should return true."); ClassicAssert.AreEqual(InstallerType.Msi, installer.InstallerType, "InstallerType should be updated."); ClassicAssert.AreEqual(initialInstaller.Architecture.ToEnumAttributeValue(), installer.Architecture.ToEnumAttributeValue(), "Architecture should not change."); ClassicAssert.AreNotEqual(initialInstaller.InstallerSha256, installer.InstallerSha256, "InstallerSha256 should be updated."); ClassicAssert.AreEqual("{E2650EFC-DCD3-4FAA-BBAC-FD1812B03A61}", installer.ProductCode, "ProductCode should be updated"); } + /// + /// Validates that the ParsePackageAndUpdateInstallerNode function works as expected for a zip installer. + /// + [Test] + public void ParseAndUpdateZipInstaller() + { + var testZipInstaller = TestUtils.MockDownloadFile(TestConstants.TestZipInstaller); + Assert.That(testZipInstaller, Is.Not.Null.And.Not.Empty); + string extractDirectory = Path.Combine(PackageParser.InstallerDownloadPath, Path.GetFileNameWithoutExtension(testZipInstaller)); + + try + { + ZipFile.ExtractToDirectory(testZipInstaller, extractDirectory, true); + } + catch (Exception e) + { + ClassicAssert.Fail($"Failed to extract the zip file: {e.Message}"); + } + + List initialManifestContent = TestUtils.GetInitialManifestContent($"TestPublisher.ZipWithExe.yaml"); + Manifests initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); + WingetCreateCore.Models.Singleton.Installer initialInstaller = initialManifests.SingletonManifest.Installers.First(); + Installer installer = ConvertSingletonInstaller(initialInstaller); + string nestedInstallerPath = Path.Combine(extractDirectory, installer.NestedInstallerFiles.First().RelativeFilePath); + + bool result = PackageParser.ParsePackageAndUpdateInstallerNode(installer, nestedInstallerPath, installer.InstallerUrl, testZipInstaller); + ClassicAssert.IsTrue(result, "ParsePackageAndUpdateInstallerNode should return true."); + ClassicAssert.AreEqual(InstallerType.Zip, installer.InstallerType, "InstallerType should not change"); + ClassicAssert.AreEqual(initialInstaller.Architecture.ToEnumAttributeValue(), installer.Architecture.ToEnumAttributeValue(), "Architecture should not change."); + ClassicAssert.AreNotEqual(initialInstaller.InstallerSha256, installer.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.AreEqual(installer.InstallerSha256, PackageParser.GetFileHash(testZipInstaller), "InstallSha256 should match the hash of the zip file"); + } + /// /// Converts the SingletonManifest Installer object model to the InstallerManifest Installer object model. /// @@ -175,6 +211,7 @@ private static Installer ConvertSingletonInstaller(WingetCreateCore.Models.Singl { cfg.AllowNullCollections = true; cfg.CreateMap(); + cfg.CreateMap(); cfg.CreateMap(); cfg.CreateMap(); });