From e9a15dc112629c7a23996bc2fbd062f41539351c Mon Sep 17 00:00:00 2001 From: Michael Redig Date: Tue, 6 Feb 2024 01:08:32 -0600 Subject: [PATCH] (feat) can use json to augment/amend embedded data --- README.md | 8 +- .../PKGAppcastGeneratorCore/AppcastItem.swift | 96 +++++++++----- .../JSONAppcastItem.swift | 24 +++- .../PKGAppcastGeneratorCore.swift | 118 +++++++++++++----- .../GeneratorTests.swift | 30 +++++ .../ZipsAppend2/MyApp-2.16.4.json | 4 + .../ZipsAppend2/MyApp-2.16.4.zip | Bin 0 -> 1477 bytes .../ZipsAppend2/MyApp-3.1.3.json | 4 + .../TestResources/ZipsAppend2/MyApp-3.1.3.zip | Bin 0 -> 1477 bytes .../expectedZipsAppend2Result.xml | 1 + .../TestResources/generate_zip_test_data.sh | 2 +- 11 files changed, 221 insertions(+), 66 deletions(-) create mode 100644 Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.json create mode 100644 Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.zip create mode 100644 Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-3.1.3.json create mode 100644 Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-3.1.3.zip create mode 100644 Tests/PKGAppcastGeneratorCoreTests/TestResources/expectedZipsAppend2Result.xml diff --git a/README.md b/README.md index 9d94e9f..c33d073 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,10 @@ public struct JSONAppcastItem: Codable { public let minimumAutoUpdateVersion: String? public let ignoreSkippedUpgradesBelowVersion: String? + /// Simply indicates if an update is critical or not. + public let criticalUpdate: Bool? /// This value would be `1.2.4` in `` - public let criticalUpdate: String? + public let criticalUpdateVersion: String? public let phasedRolloutInterval: Int? } ``` @@ -26,14 +28,14 @@ public struct JSONAppcastItem: Codable { * While it used to just append duplicate entries if you ran it with the same data, outputting to the same appcast several times, it now forgoes adding duplicates with the same download link! * It now works with both companion JSON files, but also supports .zip archives with embedded .app bundles (and pulls data from Info.plist! - so no JSON companion is required) -* While the models support a more comprehensive solution for all the properties that can be added to the xml output, much of it has been omitted for now. Support for providing input for these features in the output xml would be great contributions, if anyone feels up to it. (My intention, in regards to this, is to refactor the logic to instead glob all the archive types, simply requiring json for formats that it can't automatically pull data from, but supporting json as supplementary data for the remaining.) +* You can augment the embedded data with an accompanied JSON file for zipped .app files +* While the models support a more comprehensive solution for all the properties that can be added to the xml output, much of it has been omitted for now. Support for providing input for these features in the output xml would be great contributions, if anyone feels up to it. * releaseNotesLink * fullReleaseNotesLink * description * maximumSystemVersion * minimumAutoUpdateVersion * ignoreSkippedUpgradesBelowVersion - * criticalUpdate * phasedRolloutInterval ## Usage diff --git a/Sources/PKGAppcastGeneratorCore/AppcastItem.swift b/Sources/PKGAppcastGeneratorCore/AppcastItem.swift index f1073b1..93a633a 100644 --- a/Sources/PKGAppcastGeneratorCore/AppcastItem.swift +++ b/Sources/PKGAppcastGeneratorCore/AppcastItem.swift @@ -2,23 +2,27 @@ import Foundation import XMLCoder public struct AppcastItem: Codable, Hashable { - public let title: String - public let link: URL - public let releaseNotesLink: URL? - public let fullReleaseNotesLink: URL? - public let version: String - public let shortVersionString: String? - public let description: String? - public let publishedDate: Date - public let enclosure: Enclosure - public let minimumSystemVersion: String? - public let maximumSystemVersion: String? - public let minimumAutoUpdateVersion: String? - public let ignoreSkippedUpgradesBelowVersion: String? + public var title: String + public var link: URL + public var releaseNotesLink: URL? + public var fullReleaseNotesLink: URL? + public var version: String + public var shortVersionString: String? + public var description: String? + public var publishedDate: Date + public var enclosure: Enclosure + public var minimumSystemVersion: String? + public var maximumSystemVersion: String? + public var minimumAutoUpdateVersion: String? + public var ignoreSkippedUpgradesBelowVersion: String? /// This value would be `1.2.4` in `` - public let criticalUpdate: String? - public let phasedRolloutInterval: Int? + public var criticalUpdateVersion: String? { + get { criticalUpdate?.version } + set { criticalUpdate?.version = newValue } + } + public var criticalUpdate: CriticalUpdate? + public var phasedRolloutInterval: Int? public init( title: String, @@ -34,7 +38,8 @@ public struct AppcastItem: Codable, Hashable { maximumSystemVersion: String? = nil, minimumAutoUpdateVersion: String? = nil, ignoreSkippedUpgradesBelowVersion: String? = nil, - criticalUpdate: String? = nil, + criticalUpdateVersion: String? = nil, + criticalUpdate: Bool = false, phasedRolloutInterval: Int? = nil ) { self.title = title @@ -50,7 +55,7 @@ public struct AppcastItem: Codable, Hashable { self.maximumSystemVersion = maximumSystemVersion self.minimumAutoUpdateVersion = minimumAutoUpdateVersion self.ignoreSkippedUpgradesBelowVersion = ignoreSkippedUpgradesBelowVersion - self.criticalUpdate = criticalUpdate + self.criticalUpdate = criticalUpdate ? CriticalUpdate(version: criticalUpdateVersion) : nil self.phasedRolloutInterval = phasedRolloutInterval } @@ -73,10 +78,10 @@ public struct AppcastItem: Codable, Hashable { } public struct Enclosure: Codable, DynamicNodeDecoding, DynamicNodeEncoding, Hashable { - public let url: URL - public let length: Int - public let mimeType: String - public let edSignature: String? + public var url: URL + public var length: Int + public var mimeType: String + public var edSignature: String? public var installationType: String? static public func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .attribute } @@ -104,20 +109,32 @@ public struct AppcastItem: Codable, Hashable { case installationType = "sparkle:installationType" } } + + public struct CriticalUpdate: Codable, DynamicNodeDecoding, DynamicNodeEncoding, Hashable { + var version: String? + + enum CodingKeys: String, CodingKey { + case version = "sparkle:version" + } + + static public func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { .attribute } + static public func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding { .attribute } + } } public extension AppcastItem { - init(from appCast: JSONAppcastItem, enclosure: Enclosure) { + init(from appCast: JSONAppcastItem, enclosure: Enclosure, isPackage: Bool? = nil) throws { var enclosure = enclosure - if appCast.isPackage == true { + if appCast.isPackage == true || isPackage == true { enclosure.installationType = "package" + try appCast.validateForPKG() } - self.init( - title: appCast.title, - link: appCast.link, + try self.init( + title: appCast.title.unwrap(), + link: appCast.link.unwrap(), releaseNotesLink: appCast.releaseNotesLink, fullReleaseNotesLink: appCast.fullReleaseNotesLink, - version: appCast.version, + version: appCast.version.unwrap(), shortVersionString: appCast.shortVersionString, description: appCast.description, publishedDate: Date(), @@ -126,7 +143,30 @@ public extension AppcastItem { maximumSystemVersion: appCast.maximumSystemVersion, minimumAutoUpdateVersion: appCast.minimumAutoUpdateVersion, ignoreSkippedUpgradesBelowVersion: appCast.ignoreSkippedUpgradesBelowVersion, - criticalUpdate: appCast.criticalUpdate, + criticalUpdateVersion: appCast.criticalUpdateVersion, + criticalUpdate: appCast.criticalUpdate ?? false, phasedRolloutInterval: appCast.phasedRolloutInterval) } + + mutating func update(from jsonItem: JSONAppcastItem) { + self.title = jsonItem.title ?? title + self.link = jsonItem.link ?? link + self.releaseNotesLink = jsonItem.releaseNotesLink ?? releaseNotesLink + self.fullReleaseNotesLink = jsonItem.fullReleaseNotesLink ?? fullReleaseNotesLink + self.version = jsonItem.version ?? version + self.shortVersionString = jsonItem.shortVersionString ?? shortVersionString + self.description = jsonItem.description ?? description + self.minimumSystemVersion = jsonItem.minimumSystemVersion ?? minimumSystemVersion + self.maximumSystemVersion = jsonItem.maximumSystemVersion ?? maximumSystemVersion + self.minimumAutoUpdateVersion = jsonItem.minimumAutoUpdateVersion ?? minimumAutoUpdateVersion + self.ignoreSkippedUpgradesBelowVersion = jsonItem.ignoreSkippedUpgradesBelowVersion ?? ignoreSkippedUpgradesBelowVersion + if let criticalUpdate = jsonItem.criticalUpdate { + if criticalUpdate { + self.criticalUpdate = CriticalUpdate(version: jsonItem.criticalUpdateVersion) + } else if let criticalUpdateVersion = jsonItem.criticalUpdateVersion { + self.criticalUpdate = nil + } + } + self.phasedRolloutInterval = jsonItem.phasedRolloutInterval ?? phasedRolloutInterval + } } diff --git a/Sources/PKGAppcastGeneratorCore/JSONAppcastItem.swift b/Sources/PKGAppcastGeneratorCore/JSONAppcastItem.swift index 18dbf4a..cab59fb 100644 --- a/Sources/PKGAppcastGeneratorCore/JSONAppcastItem.swift +++ b/Sources/PKGAppcastGeneratorCore/JSONAppcastItem.swift @@ -1,11 +1,11 @@ import Foundation public struct JSONAppcastItem: Codable { - public let title: String - public let link: URL + public let title: String? + public let link: URL? public let releaseNotesLink: URL? public let fullReleaseNotesLink: URL? - public let version: String + public let version: String? public let shortVersionString: String? public let description: String? public let minimumSystemVersion: String? @@ -13,9 +13,23 @@ public struct JSONAppcastItem: Codable { public let minimumAutoUpdateVersion: String? public let ignoreSkippedUpgradesBelowVersion: String? + /// Simply indicates if an update is critical or not. + public let criticalUpdate: Bool? /// This value would be `1.2.4` in `` - public let criticalUpdate: String? + public let criticalUpdateVersion: String? public let phasedRolloutInterval: Int? - public let isPackage: Bool? + public var isPackage: Bool? + + public func validateForPKG() throws { + guard title != nil else { throw JSONAppcastError.missingPKGTitle } + guard link != nil else { throw JSONAppcastError.missingPKGLink } + guard version != nil else { throw JSONAppcastError.missingPKGVersion } + } + + public enum JSONAppcastError: Error { + case missingPKGTitle + case missingPKGLink + case missingPKGVersion + } } diff --git a/Sources/PKGAppcastGeneratorCore/PKGAppcastGeneratorCore.swift b/Sources/PKGAppcastGeneratorCore/PKGAppcastGeneratorCore.swift index ea3e00c..9aa17bb 100644 --- a/Sources/PKGAppcastGeneratorCore/PKGAppcastGeneratorCore.swift +++ b/Sources/PKGAppcastGeneratorCore/PKGAppcastGeneratorCore.swift @@ -4,6 +4,19 @@ import SwiftPizzaSnips import ZIPFoundation public enum PKGAppcastGeneratorCore { + static let jsonDecoder = JSONDecoder() + + private static let allowedUpdaterExtensions = Set( + [ + "pkg", + "mpkg", + "zip", + "dmg" + ]) + private static let requirePairedJSONExtensions = allowedUpdaterExtensions.with { + $0.remove("zip") + } + package static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") @@ -63,44 +76,49 @@ public enum PKGAppcastGeneratorCore { let directoryContents = try FileManager.default.contentsOfDirectory(at: contentsOfDirectory, includingPropertiesForKeys: nil) let jsonFiles = directoryContents.filter { $0.pathExtension.lowercased() == "json" } - let allowedUpdaterExtensions = Set( - [ - "pkg", - "mpkg", - "zip", - "dmg" - ]) let updaterFiles = directoryContents.filter { url in allowedUpdaterExtensions.contains(url.pathExtension.lowercased()) } - let jsonBasenameSet = Set(jsonFiles.map { $0.deletingPathExtension().lastPathComponent }) - let (jsonUpdaterFiles, embeddedUpdaterFiles) = updaterFiles.reduce(into: ([URL](), [URL]())) { + let jsonDict = jsonFiles.reduce(into: [String: URL]()) { jsonMap, jsonFile in + jsonMap[jsonFile.deletingPathExtension().lastPathComponent] = jsonFile + } + let fileGroups = try updaterFiles.reduce(into: FileGroups()) { let basename = $1.deletingPathExtension().lastPathComponent - if jsonBasenameSet.contains(basename) { - $0.0.append($1) + if let jsonFile = jsonDict[basename] { + $0.pairedItems.append(.init(json: jsonFile, updateFile: $1)) + if requirePairedJSONExtensions.contains($1.pathExtension.lowercased()) == false { + $0.embeddedDataUpdateFiles.append($1) + } } else { - $0.1.append($1) + guard + requirePairedJSONExtensions.contains($1.pathExtension.lowercased()) == false + else { throw CustomError(message: "pkg, mpkg, and dmg files all require json companion files.") } + $0.embeddedDataUpdateFiles.append($1) } } guard - case let embeddedFiletypes = embeddedUpdaterFiles.reduce(into: Set([String]()), { - $0.insert($1.pathExtension.lowercased()) - }), - embeddedFiletypes.contains("pkg") == false, - embeddedFiletypes.contains("mpkg") == false, - embeddedFiletypes.contains("dmg") == false - else { throw CustomError(message: "pkg, mpkg, and dmg files all require json companion files.")} - - let jsonItems = try handleJSONFilePairs( - jsonFiles: jsonFiles, - jsonPKGFiles: jsonUpdaterFiles, + jsonDict.count == fileGroups.pairedItems.count + else { throw CustomError(message: "There are json files unpaired with an update file.") } + + let decodedJSONObjects = try fileGroups.pairedItems.reduce(into: [URL: JSONAppcastItem]()) { verifiedAppcastItems, pairedUpdateFile in + let data = try Data(contentsOf: pairedUpdateFile.json) + let jsonAppcast = try Self.jsonDecoder.decode(JSONAppcastItem.self, from: data) + if requirePairedJSONExtensions.contains(pairedUpdateFile.updateFile.pathExtension.lowercased()) { + try jsonAppcast.validateForPKG() + } + verifiedAppcastItems[pairedUpdateFile.updateFile] = jsonAppcast + } + + let appcastsFromJSON = try getAppcastFromJSONOnlyPairs( + jsonDict: decodedJSONObjects, downloadURLPrefix: downloadURLPrefix, signatureGenerator: signatureGenerator) let embeddedInfoItems = try handleEmbeddedInfoItems( - embeddedUpdaterFiles: embeddedUpdaterFiles, + embeddedUpdaterFiles: fileGroups.embeddedDataUpdateFiles, + decodedJSONObjects: decodedJSONObjects, downloadsLink: downloadsLink, downloadURLPrefix: downloadURLPrefix, signatureGenerator: signatureGenerator) @@ -116,7 +134,7 @@ public enum PKGAppcastGeneratorCore { } appCast.channel.title = channelTitle - appCast.channel.appendItems(jsonItems) + appCast.channel.appendItems(appcastsFromJSON) appCast.channel.appendItems(embeddedInfoItems) appCast.channel.sortItems(by: AppcastChannel.defaultSortItems) @@ -136,6 +154,30 @@ public enum PKGAppcastGeneratorCore { doctype: nil) } + private static func getAppcastFromJSONOnlyPairs( + jsonDict: [URL: JSONAppcastItem], + downloadURLPrefix: URL, + signatureGenerator: (URL) throws -> String? + ) throws -> [AppcastItem] { + try jsonDict.compactMap { (updateFile, jsonAppcast) in + guard + requirePairedJSONExtensions.contains(updateFile.pathExtension.lowercased()) + else { return nil } + guard + let fileSize = try updateFile.resourceValues(forKeys: [.fileSizeKey]).fileSize + else { throw CustomError(message: "Cannot retrieve file size for \(updateFile.lastPathComponent).") } + + let isPackage = updateFile.pathExtension.contains("pkg") + let enclosure = AppcastItem.Enclosure( + url: downloadURLPrefix.appending(component: updateFile.lastPathComponent), + length: fileSize, + mimeType: "application/octet-stream", + edSignature: try signatureGenerator(updateFile), + installationType: isPackage ? "package" : nil) + return try AppcastItem(from: jsonAppcast, enclosure: enclosure) + } + } + private static func handleJSONFilePairs( jsonFiles: [URL], jsonPKGFiles: [URL], @@ -162,19 +204,21 @@ public enum PKGAppcastGeneratorCore { let fileSize = try pkgFile.resourceValues(forKeys: [.fileSizeKey]).fileSize else { throw CustomError(message: "Cannot retrieve file size for \(pkgFile.lastPathComponent).") } + let isPackage = pkgFile.pathExtension.contains("pkg") let enclosure = AppcastItem.Enclosure( url: downloadURLPrefix.appending(component: pkgFile.lastPathComponent), length: fileSize, mimeType: "application/octet-stream", edSignature: try signatureGenerator(pkgFile), - installationType: pkgFile.pathExtension.contains("pkg") ? "package" : nil) - return AppcastItem(from: jsonItem, enclosure: enclosure) + installationType: isPackage ? "package" : nil) + return try AppcastItem(from: jsonItem, enclosure: enclosure, isPackage: isPackage) } return items } private static func handleEmbeddedInfoItems( embeddedUpdaterFiles: [URL], + decodedJSONObjects: [URL: JSONAppcastItem], downloadsLink: URL?, downloadURLPrefix: URL, signatureGenerator: (URL) throws -> String? @@ -187,11 +231,16 @@ public enum PKGAppcastGeneratorCore { return try embeddedUpdaterFiles.map { switch $0.pathExtension.lowercased() { case "zip": - try handleZipEmbeddedInfoItem( + var appcastItem = try handleZipEmbeddedInfoItem( zipFile: $0, downloadsLink: downloadsLink, downloadURLPrefix: downloadURLPrefix, signatureGenerator: signatureGenerator) + if let jsonAppcastItem = decodedJSONObjects[$0] { + appcastItem.update(from: jsonAppcastItem) + } + + return appcastItem default: throw CustomError(message: "Unexpected file format: \($0)") } @@ -285,7 +334,18 @@ public enum PKGAppcastGeneratorCore { maximumSystemVersion: nil, minimumAutoUpdateVersion: nil, ignoreSkippedUpgradesBelowVersion: nil, - criticalUpdate: nil, + criticalUpdateVersion: nil, + criticalUpdate: false, phasedRolloutInterval: nil) } + + struct FileGroups { + var pairedItems: [PairedItem] = [] + var embeddedDataUpdateFiles: [URL] = [] + + struct PairedItem { + let json: URL + let updateFile: URL + } + } } diff --git a/Tests/PKGAppcastGeneratorCoreTests/GeneratorTests.swift b/Tests/PKGAppcastGeneratorCoreTests/GeneratorTests.swift index bb57099..6c1952f 100644 --- a/Tests/PKGAppcastGeneratorCoreTests/GeneratorTests.swift +++ b/Tests/PKGAppcastGeneratorCoreTests/GeneratorTests.swift @@ -84,6 +84,36 @@ class GeneratorTests: XCTestCase { XCTAssertEqual(xmlDoc, zipsExpectation) } + func testGenerateAppcastZipWithJSONAugmentationViaAppend() throws { + let setupDirectory = Bundle.module.url(forResource: "ZipsAppend", withExtension: nil, subdirectory: "TestResources")! + let jsonPairedDirectory = Bundle.module.url(forResource: "ZipsAppend2", withExtension: nil, subdirectory: "TestResources")! + let zipsStarterURL = Bundle.module.url(forResource: "expectedZipsResult", withExtension: "xml", subdirectory: "TestResources")! + let zipsStarterData = try Data(contentsOf: zipsStarterURL) + let zipsExpectationURL = Bundle.module.url(forResource: "expectedZipsAppend2Result", withExtension: "xml", subdirectory: "TestResources")! + let zipsExpectation = try XMLDocument(contentsOf: zipsExpectationURL) + + let starterData = try PKGAppcastGeneratorCore.generateAppcast( + fromContentsOfDirectory: setupDirectory, + previousAppcastData: zipsStarterData, + channelTitle: "Appcast", + downloadsLink: URL(string: "https://he.ho.hum/myapps/downloads"), + signatureGenerator: { _ in "Secured! jk"}, + downloadURLPrefix: #URL("https://he.ho.hum/updates/")) + + let jsonPairedData = try PKGAppcastGeneratorCore.generateAppcast( + fromContentsOfDirectory: jsonPairedDirectory, + previousAppcastData: starterData, + channelTitle: "Appcast", + downloadsLink: URL(string: "https://he.ho.hum/myapps/downloads"), + signatureGenerator: { _ in "Secured! jk"}, + downloadURLPrefix: #URL("https://he.ho.hum/updates/")) + + let xmlDoc = try XMLDocument(data: jsonPairedData) + Self.cleanXMLDates(in: xmlDoc) + + XCTAssertEqual(xmlDoc, zipsExpectation) + } + func testSortWithMatchingBuilds() throws { let enclosure = AppcastItem.Enclosure( url: #URL("https://he.ho.hum/updates/myapp.pkg"), diff --git a/Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.json b/Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.json new file mode 100644 index 0000000..efdfba2 --- /dev/null +++ b/Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.json @@ -0,0 +1,4 @@ +{ + "title": "JSON Overriden Title", + "criticalUpdate": true, +} diff --git a/Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.zip b/Tests/PKGAppcastGeneratorCoreTests/TestResources/ZipsAppend2/MyApp-2.16.4.zip new file mode 100644 index 0000000000000000000000000000000000000000..7b2c6733c1e5ca21fd9488bdad1e61bc7f935385 GIT binary patch literal 1477 zcmWIWW@h1H0D%j8TqD2?D8a=b!{A%#SWuvsSWuuJ8p6rIeEsObR1hw$;AUWC`O3(^ zz#;-v9e~peVH7i*^Ycnl^Gb@zG)xk^VZMpU{=p;|nZ2Xtuo}?SAS{Q^$lOYx$MN}F zSy8{VxJW-KGfzJ?uS_8+u{eV(H8~?+!6P*%Cto2vzbGd~kqZ<$pitrfhj69W<^Xjj z28OS!3=Fc^ZTHMe%hxN&$t*6xXL;?Ez{#@=1dh*tz1*YHT+n9yT)yYMUEBOzXEwi% znQygIz_eH~MPWtFlDgiyO>AG@$WGWNSXRIL|6Si}d+k+aWHP304!IL)CKJ)XHR+ho zn|EuDXI%WBbGN_xoqN7T}5UyLV{odt6#lbS7}Z(n)EnVqPZH@*apdI?b=R@1I}aKWXDd$M*8Snz?Zmd8G~a;jg4ZOA)j0mt=WX=0 zy=xb%e(sd~^J8X@?>Z%Kzh;s7^4sZ7%fyD0-1jc7nDVx&IU)7Xq1st1yZ3DPmf|C&YtZahUK~gxxmr{-oW%nI~NS|Ci)Vob&IwIjy&QpCq-*v}l&no{VW~LH;|6 zgRZZRuYNM&vojOt@g(-Q@oSG&U(9BmTYme^J=2velTGYrN;5sJ@ z8!8r9tef{wXKL!Pvg)fT>)Ad$H!om2C-6h{g1#!dx8p6x+A~LH*_P|Joq7@+xMiV( zJm=&Y=Pk~CR@=_EI`s@k*qgUeg%>Lucddw9|M*^ffHxzP95b#QE&)uJARxf-))7P_ z=XX{}e#gk`xXlx0KsL_>#XO|EPrAX9CRU<^se1qy@zaCecv` zZfQ-ecg@&;-#NzJanjP#1ulsbpKb}b>ZJZUn?X%$W68|mZ{3DTC~~=EvqQGRkFAALl>s@%RHa`mdB>V?EHf-c|lz*!Sml~oc}b1>#KwLE+66L zhg#0@O_{=FUwkX&l9zeLp=T<>UPRp>@YdoH^9(Qeg-CzI7Fw)gO-+1Bm8ReF|ho$N=yFNV5oc8QjgEjxF zqR;(;LPh($6(2_AEcmkg$ncX<~#f5!Y_u1|H{``J-{aIh^ z_f^LC)~wupWb!?0-2-xbInEi&<*F^@`0UI(T}2EIUNY*{TDf9lF?*c($(4K{{q>6P;sM@_OmfV)a<~LAU4noB!&^rXjhx?E zA^9C6uj4jPm;u>57Zmf5@;>PXOQIM&5xc>#a)3DFQA+`cyMg5y!`sFu_>6~^3Ha>5 mmDdq=ENM&vT7?K}c-SE4d|XzrvH{)4zzT%tfrdO_2Jrx#t=n?| literal 0 HcmV?d00001 diff --git a/Tests/PKGAppcastGeneratorCoreTests/TestResources/expectedZipsAppend2Result.xml b/Tests/PKGAppcastGeneratorCoreTests/TestResources/expectedZipsAppend2Result.xml new file mode 100644 index 0000000..72306bf --- /dev/null +++ b/Tests/PKGAppcastGeneratorCoreTests/TestResources/expectedZipsAppend2Result.xml @@ -0,0 +1 @@ + Appcast 3.1.3 https://he.ho.hum/myapps/downloads 59 3.1.3 Fri, 31 Dec 0001 18:09:24 -055036 12.8 JSON Overriden Title https://he.ho.hum/myapps/downloads 52 2.16.4 Fri, 31 Dec 0001 18:09:24 -055036 12.7 2.11.5 https://he.ho.hum/myapps/downloads 45 2.11.5 Fri, 31 Dec 0001 18:09:24 -055036 12.6 2.6.6 https://he.ho.hum/myapps/downloads 38 2.6.6 Fri, 31 Dec 0001 18:09:24 -055036 12.5 2.1.7 https://he.ho.hum/myapps/downloads 31 2.1.7 Fri, 31 Dec 0001 18:09:24 -055036 12.4 1.16.8 https://he.ho.hum/myapps/downloads 24 1.16.8 Fri, 31 Dec 0001 18:09:24 -055036 12.3 1.11.9 https://he.ho.hum/myapps/downloads 17 1.11.9 Fri, 31 Dec 0001 18:09:24 -055036 12.2 1.7.0 https://he.ho.hum/myapps/downloads 10 1.7.0 Fri, 31 Dec 0001 18:09:24 -055036 12.1 1.2.1 https://he.ho.hum/myapps/downloads 3 1.2.1 Fri, 31 Dec 0001 18:09:24 -055036 12.0 \ No newline at end of file diff --git a/Tests/PKGAppcastGeneratorCoreTests/TestResources/generate_zip_test_data.sh b/Tests/PKGAppcastGeneratorCoreTests/TestResources/generate_zip_test_data.sh index be7610e..8c2e13d 100755 --- a/Tests/PKGAppcastGeneratorCoreTests/TestResources/generate_zip_test_data.sh +++ b/Tests/PKGAppcastGeneratorCoreTests/TestResources/generate_zip_test_data.sh @@ -11,7 +11,7 @@ function cleanup() { rm -rf "${output}/MyApp.app" } -for i in {0..6}; do +for i in {0..8}; do build=$(( ($i * 7) + 3 )) minor=$(( ($build * 7) / 10 ))