From dfd2e3af4222cdc7acaa890a3496634896bfb9dd Mon Sep 17 00:00:00 2001 From: Vitalii Budnik Date: Fri, 27 Jun 2025 13:55:17 +0300 Subject: [PATCH] chore: bump min target --- Package.swift | 2 +- .../DepeneciesResolverRunner.swift | 26 ++++----- .../Utils/FileManagerProtocol.swift | 8 +-- .../CommandLine/Commands/CleanCommand.swift | 4 +- .../CommandLine/Commands/ResolveCommand.swift | 4 +- Sources/Utils/CLI/CLI.swift | 8 +-- Sources/Utils/String+fileURL.swift | 7 +-- Sources/Utils/URL+filePath.swift | 42 +-------------- ...DependenciesConfigurationReaderTests.swift | 6 +-- ...endenciesResolverRunnerDownloadTests.swift | 12 ++--- ...pendenciesResolverRunnerFileURLTests.swift | 18 +++---- ...enciesResolverRunnerLocalChecksTests.swift | 14 ++--- ...DependenciesResolverRunnerUnzipTests.swift | 49 +++++++++-------- .../Mocks/FileManagerProtocolMock.swift | 4 +- Tests/UtilsTests/URLFilePathTests.swift | 54 +------------------ 15 files changed, 86 insertions(+), 172 deletions(-) diff --git a/Package.swift b/Package.swift index 8f42d73..31f2def 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "binary-dependencies-manager", platforms: [ - .macOS(.v11), + .macOS(.v14), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. diff --git a/Sources/BinaryDependencyManager/DepeneciesResolverRunner.swift b/Sources/BinaryDependencyManager/DepeneciesResolverRunner.swift index e055880..6bb8836 100644 --- a/Sources/BinaryDependencyManager/DepeneciesResolverRunner.swift +++ b/Sources/BinaryDependencyManager/DepeneciesResolverRunner.swift @@ -85,7 +85,7 @@ public struct DependenciesResolverRunner { repo: dependency.repo, tag: dependency.tag, pattern: asset.pattern, - outputFilePath: downloadFileURL.filePath + outputFilePath: downloadFileURL.path(percentEncoded: false) ) let checksum = try runThrowable("Calculating checksum") { try calculateChecksum(fileURL: downloadFileURL) } @@ -123,7 +123,7 @@ public struct DependenciesResolverRunner { guard try shouldResolve(dependency, asset: asset) else { return } let tempRootDirURL = fileManager.privateDownloadsDirectoryURL - let tempDir = tempRootDirURL.appending(pathComponents: uuidString, isDirectory: true) + let tempDir = tempRootDirURL.appending(path: uuidString, directoryHint: .isDirectory) try createDirectoryIfNeeded(at: tempDir) defer { @@ -132,10 +132,10 @@ public struct DependenciesResolverRunner { // Unpack downloaded file to the temporary dir, so we can traverse through the contents and copy only specified `asset.contents`. let downloadedFileURL = downloadURL(for: dependency, asset: asset) - try unarchiver.unzip(archivePath: downloadedFileURL.filePath, outputFilePath: tempDir.filePath) + try unarchiver.unzip(archivePath: downloadedFileURL.path(percentEncoded: false), outputFilePath: tempDir.path(percentEncoded: false)) // if we have additional contents directory from dependency, we should use it - let contentsDirectoryURL = tempDir.appending(pathComponents: asset.contents ?? "", isDirectory: true) + let contentsDirectoryURL = tempDir.appending(path: asset.contents ?? "", directoryHint: .isDirectory) let contents = try fileManager.contentsOfDirectory(at: contentsDirectoryURL) @@ -144,8 +144,8 @@ public struct DependenciesResolverRunner { try createDirectoryIfNeeded(at: outputDirectory) for item in contents { - let downloadedFileURL = contentsDirectoryURL.appending(pathComponents: item, isDirectory: false) - let destinationFileURL = outputDirectory.appending(pathComponents: item, isDirectory: false) + let downloadedFileURL = contentsDirectoryURL.appending(path: item, directoryHint: .notDirectory) + let destinationFileURL = outputDirectory.appending(path: item, directoryHint: .notDirectory) if fileManager.fileExists(at: destinationFileURL) { Logger.log("[Unzip] Removing \(destinationFileURL.relativeFilePath).") try fileManager.removeItem(at: destinationFileURL) @@ -182,17 +182,18 @@ public struct DependenciesResolverRunner { /// Location of directory where the downloaded dependency will be placed func downloadDirectoryURL(for dependency: Dependency, asset: Dependency.Asset) -> URL { downloadsDirectoryURL - .appending(pathComponents: dependency.repo, dependency.tag, asset.outputDirectory ?? "", isDirectory: true) + .appending(components: dependency.repo, dependency.tag, asset.outputDirectory ?? "", directoryHint: .isDirectory) } /// Location of the file where the downloaded dependency will be placed func downloadURL(for dependency: Dependency, asset: Dependency.Asset) -> URL { downloadDirectoryURL(for: dependency, asset: asset) - .appending(pathComponents: asset.checksum + ".zip", isDirectory: false) + .appending(path: asset.checksum + ".zip", directoryHint: .notDirectory) } func outputDirectoryURL(for dependency: Dependency, asset: Dependency.Asset) -> URL { - outputDirectoryURL.appending(pathComponents: dependency.repo, asset.outputDirectory ?? "", isDirectory: true) + outputDirectoryURL + .appending(components: dependency.repo, asset.outputDirectory ?? "", directoryHint: .isDirectory) } func outputDirectoryHashFile(for dependency: Dependency, asset: Dependency.Asset) throws -> URL { @@ -209,11 +210,12 @@ public struct DependenciesResolverRunner { filename = ".\(filename).hash" - return outputDirectoryURL(for: dependency, asset: asset).appending(pathComponents: filename, isDirectory: false) + return outputDirectoryURL(for: dependency, asset: asset) + .appending(path: filename, directoryHint: .notDirectory) } var downloadsDirectoryURL: URL { - cacheDirectoryURL.appending(pathComponents: ".downloads", isDirectory: true) + cacheDirectoryURL.appending(path: ".downloads", directoryHint: .isDirectory) } func createDirectoryIfNeeded(at url: URL) throws { @@ -241,6 +243,6 @@ public struct DependenciesResolverRunner { extension FileManagerProtocol { var privateDownloadsDirectoryURL: URL { - temporaryDirectory.appending(pathComponents: "PrivateDownloads", isDirectory: true) + temporaryDirectory.appending(path: "PrivateDownloads", directoryHint: .isDirectory) } } diff --git a/Sources/BinaryDependencyManager/Utils/FileManagerProtocol.swift b/Sources/BinaryDependencyManager/Utils/FileManagerProtocol.swift index 65362c7..8623e85 100644 --- a/Sources/BinaryDependencyManager/Utils/FileManagerProtocol.swift +++ b/Sources/BinaryDependencyManager/Utils/FileManagerProtocol.swift @@ -37,19 +37,19 @@ extension FileManagerProtocol { } public func fileExists(at url: URL) -> Bool { - fileExists(atPath: url.filePath) + fileExists(atPath: url.path(percentEncoded: false)) } public func contents(at url: URL) -> Data? { - contents(atPath: url.filePath) + contents(atPath: url.path(percentEncoded: false)) } public func contentsOfDirectory(at url: URL) throws -> [String] { - try contentsOfDirectory(atPath: url.filePath) + try contentsOfDirectory(atPath: url.path(percentEncoded: false)) } func createFile(at url: URL, contents data: Data?) -> Bool { - createFile(atPath: url.filePath, contents: data, attributes: .none) + createFile(atPath: url.path(percentEncoded: false), contents: data, attributes: .none) } } diff --git a/Sources/CommandLine/Commands/CleanCommand.swift b/Sources/CommandLine/Commands/CleanCommand.swift index b1d08b0..0c79491 100644 --- a/Sources/CommandLine/Commands/CleanCommand.swift +++ b/Sources/CommandLine/Commands/CleanCommand.swift @@ -32,10 +32,10 @@ struct CleanCommand: ParsableCommand { outputDirectoryPath = configurationReader .resolveOutputDirectoryURL(outputDirectoryPath) - .filePath + .path(percentEncoded: false) cacheDirectoryPath = configurationReader .resolveCacheDirectoryURL(cacheDirectoryPath) - .filePath + .path(percentEncoded: false) } func run() throws { diff --git a/Sources/CommandLine/Commands/ResolveCommand.swift b/Sources/CommandLine/Commands/ResolveCommand.swift index ae23dc9..7d280ad 100644 --- a/Sources/CommandLine/Commands/ResolveCommand.swift +++ b/Sources/CommandLine/Commands/ResolveCommand.swift @@ -54,10 +54,10 @@ struct ResolveCommand: ParsableCommand { // Paths from CLI arguments take precedence over those from the configuration file. outputDirectoryPath = configurationReader .resolveOutputDirectoryURL(outputDirectoryPath ?? configuration.outputDirectory) - .filePath + .path(percentEncoded: false) cacheDirectoryPath = configurationReader .resolveCacheDirectoryURL(cacheDirectoryPath ?? configuration.cacheDirectory) - .filePath + .path(percentEncoded: false) } diff --git a/Sources/Utils/CLI/CLI.swift b/Sources/Utils/CLI/CLI.swift index 1173fab..793cc37 100644 --- a/Sources/Utils/CLI/CLI.swift +++ b/Sources/Utils/CLI/CLI.swift @@ -22,7 +22,7 @@ public enum CLI { /// - currentDirectoryURL: A working directory URL where executable will be launched. @discardableResult public static func run(executableURL: URL, arguments: [String], currentDirectoryURL: URL? = .none) throws -> String { - Logger.log("[Run] \(executableURL.filePath) \(arguments.joined(separator: " "))") + Logger.log("[Run] \(executableURL.path(percentEncoded: false)) \(arguments.joined(separator: " "))") let stdoutPipe = Pipe() let stderrPipe = Pipe() @@ -39,16 +39,16 @@ public enum CLI { let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() guard let stdout = String(data: stdoutData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) else { - throw GenericError("Can't parse stdout output from the \(executableURL.filePath)") + throw GenericError("Can't parse stdout output from the \(executableURL.path(percentEncoded: false))") } let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() guard let stderr = String(data: stderrData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) else { - throw GenericError("Can't parse stderr output from the \(executableURL.filePath)") + throw GenericError("Can't parse stderr output from the \(executableURL.path(percentEncoded: false))") } guard process.terminationStatus == 0 else { - throw GenericError("Error running \(executableURL.filePath) with arguments \(arguments.joined(separator: " ")). Output:\n\(stdout).\nError:\n\(stderr).") + throw GenericError("Error running \(executableURL.path(percentEncoded: false)) with arguments \(arguments.joined(separator: " ")). Output:\n\(stdout).\nError:\n\(stderr).") } return stdout diff --git a/Sources/Utils/String+fileURL.swift b/Sources/Utils/String+fileURL.swift index 9339996..ad51d08 100644 --- a/Sources/Utils/String+fileURL.swift +++ b/Sources/Utils/String+fileURL.swift @@ -5,11 +5,6 @@ extension String { /// /// - Note: Used to avoid deprecation warnings. public var asFileURL: URL { - let url = if #available(macOS 13.0, *) { - URL(filePath: self) - } else { - URL(fileURLWithPath: self) - } - return url.standardizedFileURL.absoluteURL + URL(filePath: self).standardizedFileURL.absoluteURL } } diff --git a/Sources/Utils/URL+filePath.swift b/Sources/Utils/URL+filePath.swift index ff7c987..e4af8a0 100644 --- a/Sources/Utils/URL+filePath.swift +++ b/Sources/Utils/URL+filePath.swift @@ -1,51 +1,11 @@ import Foundation extension URL { - /// Returns a URL constructed by appending the given variadic list of path components to self. - /// - /// - Note: Used to avoid deprecation warning for newer macOS. - /// - /// - Parameters: - /// - pathComponents: The list of the path components to add. - /// - isDirectory: A `Bool` flag to whether this URL will point to a directory. - /// - /// - Returns: The URL with the appended path components. - public func appending(pathComponents: String..., isDirectory: Bool) -> URL { - var result = self - - // Remove empty path components to avoid double slashes. - let pathComponents = pathComponents.filter { !$0.isEmpty } - - if #available(macOS 13.0, *) { - for path in pathComponents[0.. DependenciesResolverRunner { DependenciesResolverRunner.mock( dependencies: dependencies ?? [sampleDependency], - outputDirectoryURL: tempDir.appending(pathComponents: "output", isDirectory: true), - cacheDirectoryURL: tempDir.appending(pathComponents: "cache", isDirectory: true), + outputDirectoryURL: tempDir.appending(path: "output", directoryHint: .isDirectory), + cacheDirectoryURL: tempDir.appending(path: "cache", directoryHint: .isDirectory), fileManager: fileManager, uuidString: "mock-uuid", dependenciesDownloader: downloaderMock, @@ -71,7 +71,7 @@ struct DependenciesResolverRunnerDownloadTests { #expect(downloadCall.repo == sampleDependency.repo) #expect(downloadCall.tag == sampleDependency.tag) #expect(downloadCall.pattern == sampleAsset.pattern) - #expect(downloadCall.outputFilePath == downloadURL.filePath) + #expect(downloadCall.outputFilePath == downloadURL.path(percentEncoded: false)) // Verify checksum was calculated #expect(checksumCalculatorMock.checksumCalls.contains(downloadURL)) @@ -92,7 +92,7 @@ struct DependenciesResolverRunnerDownloadTests { let downloadURL = runner.downloadURL(for: sampleDependency, asset: sampleAsset) // File already exists - fileManager.existingFiles.insert(downloadURL.filePath) + fileManager.existingFiles.insert(downloadURL.path(percentEncoded: false)) // Checksum matches checksumCalculatorMock.checksums[downloadURL] = sampleAsset.checksum @@ -145,7 +145,7 @@ struct DependenciesResolverRunnerDownloadTests { let downloadURL = runner.downloadURL(for: sampleDependency, asset: sampleAsset) // File already exists - fileManager.existingFiles.insert(downloadURL.filePath) + fileManager.existingFiles.insert(downloadURL.path(percentEncoded: false)) // First checksum call returns wrong checksum, second returns correct checksumCalculatorMock.checksums[downloadURL] = "wrong_checksum" diff --git a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerFileURLTests.swift b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerFileURLTests.swift index fea5dec..0d0b7fc 100644 --- a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerFileURLTests.swift +++ b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerFileURLTests.swift @@ -21,11 +21,11 @@ struct DependenciesResolverRunnerFileURLTests { cacheDirectoryURL: cacheRoot ) let downloadsDir = runner.downloadsDirectoryURL - #expect(downloadsDir.filePath.hasSuffix("/cache/.downloads/")) - #expect(downloadsDir.filePath.hasPrefix(FileManager.default.currentDirectoryPath)) + #expect(downloadsDir.path(percentEncoded: false).hasSuffix("/cache/.downloads/")) + #expect(downloadsDir.path(percentEncoded: false).hasPrefix(FileManager.default.currentDirectoryPath)) #expect( downloadsDir == FileManager.default.currentDirectoryPath.asFileURL - .appending(pathComponents: "cache", ".downloads", isDirectory: true) + .appending(components: "cache", ".downloads", directoryHint: .isDirectory) ) } @@ -37,8 +37,8 @@ struct DependenciesResolverRunnerFileURLTests { cacheDirectoryURL: "/cache".asFileURL ) let downloadsDir = runner.downloadsDirectoryURL - #expect(downloadsDir.filePath.hasSuffix("/cache/.downloads/")) - #expect(!downloadsDir.filePath.hasPrefix(FileManager.default.currentDirectoryPath)) + #expect(downloadsDir.path(percentEncoded: false).hasSuffix("/cache/.downloads/")) + #expect(!downloadsDir.path(percentEncoded: false).hasPrefix(FileManager.default.currentDirectoryPath)) #expect(downloadsDir == "/cache/.downloads/".asFileURL) #expect(downloadsDir.path == "/cache/.downloads") } @@ -51,7 +51,7 @@ struct DependenciesResolverRunnerFileURLTests { cacheDirectoryURL: cacheRoot ) let url = runner.downloadDirectoryURL(for: dependency, asset: asset) - #expect(url.filePath.hasSuffix("/cache/.downloads/owner/repo/1.0.0/Lib/")) + #expect(url.path(percentEncoded: false).hasSuffix("/cache/.downloads/owner/repo/1.0.0/Lib/")) let downloadURL = runner.downloadURL(for: dependency, asset: asset) #expect(downloadURL.lastPathComponent == "abc123.zip") } @@ -64,7 +64,7 @@ struct DependenciesResolverRunnerFileURLTests { cacheDirectoryURL: cacheRoot ) let out = runner.outputDirectoryURL(for: dependency, asset: asset) - #expect(out.filePath.hasSuffix("/output/owner/repo/Lib/")) + #expect(out.path(percentEncoded: false).hasSuffix("/output/owner/repo/Lib/")) } @Test @@ -75,7 +75,7 @@ struct DependenciesResolverRunnerFileURLTests { cacheDirectoryURL: cacheRoot ) let hashURL = try runner.outputDirectoryHashFile(for: dependency, asset: asset) - #expect(hashURL.filePath.hasSuffix("/output/owner/repo/Lib/.lib_zip_Frameworks_Lib_xcframework.hash")) + #expect(hashURL.path(percentEncoded: false).hasSuffix("/output/owner/repo/Lib/.lib_zip_Frameworks_Lib_xcframework.hash")) } @Test @@ -97,7 +97,7 @@ struct DependenciesResolverRunnerFileURLTests { func test_createDirectoryIfNeeded_skips_if_exists() async throws { let mock = FileManagerProtocolMock() let url = URL(fileURLWithPath: "/exists/dir") - mock.existingFiles.insert(url.filePath) + mock.existingFiles.insert(url.path(percentEncoded: false)) let runner = DependenciesResolverRunner.mock( dependencies: [dependency], outputDirectoryURL: outputRoot, diff --git a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerLocalChecksTests.swift b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerLocalChecksTests.swift index 35a0ecb..2ed7b1b 100644 --- a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerLocalChecksTests.swift +++ b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerLocalChecksTests.swift @@ -21,7 +21,7 @@ struct DependenciesResolverRunnerLocalChecksTests { let tempDir: URL = { FileManager.default.temporaryDirectory - .appending(pathComponents: "binary-dependency-manager-tests", UUID().uuidString, isDirectory: true) + .appending(components: "binary-dependency-manager-tests", UUID().uuidString, directoryHint: .isDirectory) }() let checksumCalculatorMock: ChecksumCalculatorProtocolMock = .init() @@ -29,8 +29,8 @@ struct DependenciesResolverRunnerLocalChecksTests { func makeRunner(fileManager: FileManagerProtocolMock, dependencies: [Dependency]? = .none) throws -> DependenciesResolverRunner { DependenciesResolverRunner.mock( dependencies: dependencies ?? [sampleDependency], - outputDirectoryURL: tempDir.appending(pathComponents: "output", isDirectory: true), - cacheDirectoryURL: tempDir.appending(pathComponents: "cache", isDirectory: true), + outputDirectoryURL: tempDir.appending(path: "output", directoryHint: .isDirectory), + cacheDirectoryURL: tempDir.appending(path: "cache", directoryHint: .isDirectory), fileManager: fileManager, uuidString: "mock-uuid", checksumCalculator: checksumCalculatorMock @@ -58,7 +58,7 @@ struct DependenciesResolverRunnerLocalChecksTests { let runner = try makeRunner(fileManager: fileManager) let sampleAsset: Dependency.Asset = sampleDependency.assets[0] let hashURL = try runner.outputDirectoryHashFile(for: sampleDependency, asset: sampleAsset) - fileManager.contents[hashURL.filePath] = Data("abc123".utf8) + fileManager.contents[hashURL.path(percentEncoded: false)] = Data("abc123".utf8) // WHEN let shouldResolve = try runner.shouldResolve(sampleDependency, asset: sampleAsset) @@ -74,7 +74,7 @@ struct DependenciesResolverRunnerLocalChecksTests { let runner = try makeRunner(fileManager: fileManager) let sampleAsset: Dependency.Asset = sampleDependency.assets[0] let hashURL = try runner.outputDirectoryHashFile(for: sampleDependency, asset: sampleAsset) - fileManager.contents[hashURL.filePath] = Data("different".utf8) + fileManager.contents[hashURL.path(percentEncoded: false)] = Data("different".utf8) // WHEN let shouldResolve = try runner.shouldResolve(sampleDependency, asset: sampleAsset) @@ -97,7 +97,7 @@ struct DependenciesResolverRunnerLocalChecksTests { // THEN // check if the hash file was created - let hashData = fileManager.contents[hashURL.filePath] + let hashData = fileManager.contents[hashURL.path(percentEncoded: false)] #expect(hashData == Data("abc123".utf8)) } @@ -123,7 +123,7 @@ struct DependenciesResolverRunnerLocalChecksTests { let sampleAsset: Dependency.Asset = sampleDependency.assets[0] let zipFileURL = runner.downloadURL(for: sampleDependency, asset: sampleAsset) // .zip file exists - fileManager.existingFiles.insert(zipFileURL.filePath) + fileManager.existingFiles.insert(zipFileURL.path(percentEncoded: false)) // checksum is correct checksumCalculatorMock.checksums[zipFileURL] = sampleAsset.checksum diff --git a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerUnzipTests.swift b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerUnzipTests.swift index df4ea2b..8cd54d2 100644 --- a/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerUnzipTests.swift +++ b/Tests/BinaryDependencyManagerTests/DependenciesResolverRunner/DependenciesResolverRunnerUnzipTests.swift @@ -20,7 +20,7 @@ final class DependenciesResolverRunnerUnzipTests { let tempDir: URL = { FileManager.default.temporaryDirectory - .appending(pathComponents: "binary-dependency-manager-tests", UUID().uuidString, isDirectory: true) + .appending(components: "binary-dependency-manager-tests", UUID().uuidString, directoryHint: .isDirectory) }() lazy var sampleAsset = sampleDependency.assets[0] @@ -36,8 +36,8 @@ final class DependenciesResolverRunnerUnzipTests { ) -> DependenciesResolverRunner { DependenciesResolverRunner.mock( dependencies: dependencies ?? [sampleDependency], - outputDirectoryURL: tempDir.appending(pathComponents: "output", isDirectory: true), - cacheDirectoryURL: tempDir.appending(pathComponents: "cache", isDirectory: true), + outputDirectoryURL: tempDir.appending(path: "output", directoryHint: .isDirectory), + cacheDirectoryURL: tempDir.appending(path: "cache", directoryHint: .isDirectory), fileManager: fileManager, uuidString: "mock-uuid", unarchiver: unarchiverMock @@ -50,14 +50,14 @@ final class DependenciesResolverRunnerUnzipTests { tempContents: [String] = ["Library.framework", "Info.plist"] ) -> URL { let privateURLs = fileManager.privateDownloadsDirectoryURL - let unzipingDirectory: URL = privateURLs.appending(pathComponents: "mock-uuid", isDirectory: true) + let unzipingDirectory: URL = privateURLs.appending(path: "mock-uuid", directoryHint: .isDirectory) // Make shouldResolve return true (no hash file exists) // This is the default behavior when hash file doesn't exist // Set up temp directory contents after unzipping fileManager.directoryContents = [ - unzipingDirectory.appending(pathComponents: asset.contents ?? "", isDirectory: true).filePath: tempContents + unzipingDirectory.appending(path: asset.contents ?? "", directoryHint: .isDirectory).path(percentEncoded: false): tempContents ] return unzipingDirectory } @@ -68,7 +68,7 @@ final class DependenciesResolverRunnerUnzipTests { // Set up hash file to make shouldResolve return false let hashURL = try runner.outputDirectoryHashFile(for: sampleDependency, asset: sampleAsset) - fileManager.contents[hashURL.filePath] = Data(sampleAsset.checksum.utf8) + fileManager.contents[hashURL.path(percentEncoded: false)] = Data(sampleAsset.checksum.utf8) // WHEN try runner.unzip(sampleDependency, asset: sampleAsset) @@ -94,10 +94,11 @@ final class DependenciesResolverRunnerUnzipTests { #expect(unarchiverMock.unzipCalls.count == 1) let unzipCall = unarchiverMock.unzipCalls[0] let downloadURL = runner.downloadURL(for: sampleDependency, asset: sampleAsset) - #expect(unzipCall.archivePath == downloadURL.filePath) + #expect(unzipCall.archivePath == downloadURL.path(percentEncoded: false)) #expect( unzipCall.outputFilePath == fileManager.privateDownloadsDirectoryURL - .appending(pathComponents: runner.uuidString, isDirectory: true).filePath + .appending(path: runner.uuidString, directoryHint: .isDirectory) + .path(percentEncoded: false) ) } @@ -118,10 +119,10 @@ final class DependenciesResolverRunnerUnzipTests { #expect(fileManager.createdDirectories.contains(outputDir)) // Verify files were copied - let contentsDir = unzippingDirectory.appending(pathComponents: sampleAsset.contents ?? "", isDirectory: true) + let contentsDir = unzippingDirectory.appending(path: sampleAsset.contents ?? "", directoryHint: .isDirectory) for item in tempContents { - let sourceURL = contentsDir.appending(pathComponents: item, isDirectory: false) - let destinationURL = outputDir.appending(pathComponents: item, isDirectory: false) + let sourceURL = contentsDir.appending(path: item, directoryHint: .notDirectory) + let destinationURL = outputDir.appending(path: item, directoryHint: .notDirectory) #expect(fileManager.copiedFiles[sourceURL] == destinationURL) } } @@ -136,8 +137,8 @@ final class DependenciesResolverRunnerUnzipTests { // Set up existing file in output directory let outputDir = runner.outputDirectoryURL(for: sampleDependency, asset: sampleAsset) - let existingFileURL = outputDir.appending(pathComponents: "Library.framework", isDirectory: false) - fileManager.existingFiles.insert(existingFileURL.filePath) + let existingFileURL = outputDir.appending(path: "Library.framework", directoryHint: .notDirectory) + fileManager.existingFiles.insert(existingFileURL.path(percentEncoded: false)) // WHEN try runner.unzip(sampleDependency, asset: sampleAsset) @@ -183,9 +184,12 @@ final class DependenciesResolverRunnerUnzipTests { ) // Set up temp directory contents (no subdirectory) - let tempRootDir = tempDir.appending(pathComponents: "PrivateDownloads", "mock-uuid", isDirectory: true) + let tempRootDir = tempDir.appending( + components: "PrivateDownloads", "mock-uuid", assetWithoutContents.contents ?? "", + directoryHint: .isDirectory + ) fileManager.directoryContents = [ - tempRootDir.filePath: ["file1.txt", "file2.txt"] + tempRootDir.path(percentEncoded: false): ["file1.txt", "file2.txt"] ] // WHEN @@ -195,11 +199,14 @@ final class DependenciesResolverRunnerUnzipTests { // Verify unarchiver was called #expect(unarchiverMock.unzipCalls.count == 1) - // Verify files were copied from temp root (no contents subdirectory) + // Verify both files are copied from temp root (no contents subdirectory) let outputDir = runner.outputDirectoryURL(for: dependencyWithoutContents, asset: assetWithoutContents) - let sourceURL1 = tempRootDir.appending(pathComponents: "file1.txt", isDirectory: false) - let destURL1 = outputDir.appending(pathComponents: "file1.txt", isDirectory: false) + let sourceURL1 = tempRootDir.appending(path: "file1.txt", directoryHint: .notDirectory) + let destURL1 = outputDir.appending(path: "file1.txt", directoryHint: .notDirectory) + let sourceURL2 = tempRootDir.appending(path: "file2.txt", directoryHint: .notDirectory) + let destURL2 = outputDir.appending(path: "file2.txt", directoryHint: .notDirectory) #expect(fileManager.copiedFiles[sourceURL1] == destURL1) + #expect(fileManager.copiedFiles[sourceURL2] == destURL2) } func stubUnarchiverError(_ error: Error) { @@ -220,7 +227,7 @@ final class DependenciesResolverRunnerUnzipTests { #expect(unarchiverMock.unzipCalls.count == 1) // Verify temp directory cleanup still happens even on error - let tempRootDir = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", isDirectory: true) + let tempRootDir = fileManager.temporaryDirectory.appending(path: "PrivateDownloads", directoryHint: .isDirectory) #expect(fileManager.removedItems.contains(tempRootDir)) } @@ -237,8 +244,8 @@ final class DependenciesResolverRunnerUnzipTests { // THEN // Verify temporary files were removed after copying - let contentsDir = unzpipingDirectory.appending(pathComponents: sampleAsset.contents ?? "", isDirectory: true) - let tempFileURL = contentsDir.appending(pathComponents: "Library.framework", isDirectory: false) + let contentsDir = unzpipingDirectory.appending(path: sampleAsset.contents ?? "", directoryHint: .isDirectory) + let tempFileURL = contentsDir.appending(path: "Library.framework", directoryHint: .notDirectory) #expect(fileManager.removedItems.contains(tempFileURL)) } } diff --git a/Tests/BinaryDependencyManagerTests/Mocks/FileManagerProtocolMock.swift b/Tests/BinaryDependencyManagerTests/Mocks/FileManagerProtocolMock.swift index db6d0a5..44bc7ce 100644 --- a/Tests/BinaryDependencyManagerTests/Mocks/FileManagerProtocolMock.swift +++ b/Tests/BinaryDependencyManagerTests/Mocks/FileManagerProtocolMock.swift @@ -5,7 +5,7 @@ class FileManagerProtocolMock: FileManagerProtocol { var copiedFiles: [URL: URL] = [:] func copyItem(at srcURL: URL, to dstURL: URL) throws { copiedFiles[srcURL] = dstURL - existingFiles.insert(dstURL.filePath) + existingFiles.insert(dstURL.path(percentEncoded: false)) contentsMap[dstURL] = contentsMap[srcURL] } @@ -41,7 +41,7 @@ class FileManagerProtocolMock: FileManagerProtocol { func contents(at url: URL) -> Data? { contentsMap[url] } func removeItem(at url: URL) throws { removedItems.append(url) - existingFiles.remove(url.filePath) + existingFiles.remove(url.path(percentEncoded: false)) contentsMap[url] = .none } diff --git a/Tests/UtilsTests/URLFilePathTests.swift b/Tests/UtilsTests/URLFilePathTests.swift index df92d31..0fc209e 100644 --- a/Tests/UtilsTests/URLFilePathTests.swift +++ b/Tests/UtilsTests/URLFilePathTests.swift @@ -3,61 +3,11 @@ import Testing @Suite("Tests for URL+filePath extensions") struct URLFilePathTests { - @Test("appending(pathComponents:isDirectory:) appends multiple path components correctly directories") - func test_appendingPathComponents_dir() { - // GIVEN - let base = URL(fileURLWithPath: "/tmp") - - // WHEN - let dirURL = base.appending(pathComponents: "foo", "bar", isDirectory: true) - - // THEN - #expect(dirURL.path.hasSuffix("/foo/bar"), "Directory URL should end with /foo/bar") - #expect(dirURL.hasDirectoryPath, "Should flag as directory") - } - - @Test("appending(pathComponents:isDirectory:) appends multiple path components correctly for files") - func test_appendingPathComponents_file() { - // GIVEN - let base = URL(fileURLWithPath: "/tmp") - - // WHEN - let fileURL = base.appending(pathComponents: "foo", "bar.txt", isDirectory: false) - - // THEN - #expect(fileURL.path.hasSuffix("/foo/bar.txt"), "File URL should end with /foo/bar.txt") - #expect(!fileURL.hasDirectoryPath, "Should not flag as directory") - } - - @Test("appending(pathComponents:isDirectory:) appends multiple path components correctly directories") - func test_appendingPathComponents_empty_components() { - // GIVEN - let base = URL(fileURLWithPath: "/tmp") - - // WHEN - let fileURL = base - .appending(pathComponents: "", "", "foo", "", "", "bar", "", "", isDirectory: true) - .appending(pathComponents: "", isDirectory: true) - .appending(pathComponents: "some file.txt", isDirectory: false) - - // THEN - #expect(fileURL.filePath.hasSuffix("/foo/bar/some file.txt"), "File URL should end with /foo/bar/some file.txt") - } - - @Test("filePath returns correct path with or without percent encoding on different platforms") - func test_filePath() throws { - let original = "/tmp/some file.txt" - let encoded = original.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" - let url = try #require(URL(string: "file://" + encoded)) - // filePath should match the normal path - #expect(url.filePath.hasSuffix("some file.txt"), "filePath should remove percent encoding if present") - } - @Test("relativeFilePath returns './' prefixed path when under current directory") func test_relativeFilePath() { let cwd = FileManager.default.currentDirectoryPath let fileName = "test.txt" - let url = URL(fileURLWithPath: cwd).appending(pathComponents: fileName, isDirectory: false) + let url = URL(fileURLWithPath: cwd).appending(path: fileName, directoryHint: .notDirectory) #expect(url.relativeFilePath.hasPrefix("./"), "Should prefix with ./") #expect(url.relativeFilePath.hasSuffix(fileName), "Should end with the file name") } @@ -65,6 +15,6 @@ struct URLFilePathTests { @Test("relativeFilePath returns absolute path for files outside current directory") func test_relativeFilePath_outsideCWD() { let url = URL(fileURLWithPath: "/tmp/outside.txt") - #expect(url.relativeFilePath == url.filePath, "Should match filePath for files not under CWD") + #expect(url.relativeFilePath == url.path(percentEncoded: false), "Should match filePath for files not under CWD") } }