diff --git a/Swiftpkgr/State/ProjectEditorModel.swift b/Swiftpkgr/State/ProjectEditorModel.swift index dea68e7..ac135ac 100644 --- a/Swiftpkgr/State/ProjectEditorModel.swift +++ b/Swiftpkgr/State/ProjectEditorModel.swift @@ -283,7 +283,7 @@ final class ProjectEditorModel { private func saveDraft() throws { guard let projectURL, let document = buildInfoDocument else { - throw MunkiPkgError.message("No project is open.") + throw SwiftPkgError.message("No project is open.") } let configuration = try draft.validatedConfiguration() try BuildInfoStore.write(configuration, to: projectURL, format: document.format) diff --git a/swiftpkg.xcodeproj/project.pbxproj b/swiftpkg.xcodeproj/project.pbxproj index 0070c91..e69b23d 100644 --- a/swiftpkg.xcodeproj/project.pbxproj +++ b/swiftpkg.xcodeproj/project.pbxproj @@ -481,18 +481,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = DPXY7JLK67; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = Swiftpkgr; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 0.3.1; + MARKETING_VERSION = 0.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.codecarton.Swiftpkgr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; @@ -506,18 +507,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = DPXY7JLK67; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = Swiftpkgr; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 0.3.1; + MARKETING_VERSION = 0.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.codecarton.Swiftpkgr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; diff --git a/swiftpkg/BuildInfo.swift b/swiftpkg/BuildInfo.swift index 921be79..b8dfaab 100644 --- a/swiftpkg/BuildInfo.swift +++ b/swiftpkg/BuildInfo.swift @@ -198,38 +198,38 @@ public struct PackageConfiguration: Sendable { private static func stringValue(for key: String, in values: [String: Any]) throws -> String? { guard let value = values[key] else { return nil } - guard let string = scalarString(value) else { throw MunkiPkgError.invalidConfiguration("build-info key '\(key)' must be a string") } + guard let string = scalarString(value) else { throw SwiftPkgError.invalidConfiguration("build-info key '\(key)' must be a string") } return string } private static func boolValue(for key: String, in values: [String: Any]) throws -> Bool? { guard let value = values[key] else { return nil } - guard let bool = value as? Bool else { throw MunkiPkgError.invalidConfiguration("build-info key '\(key)' must be a Boolean") } + guard let bool = value as? Bool else { throw SwiftPkgError.invalidConfiguration("build-info key '\(key)' must be a Boolean") } return bool } private static func enumValue(_ type: T.Type, key: String, values: [String: Any]) throws -> T? where T.RawValue == String { guard let string = try stringValue(for: key, in: values) else { return nil } - guard let value = T(rawValue: string) else { throw MunkiPkgError.invalidConfiguration("build-info key '\(key)' has illegal value: \(string)") } + guard let value = T(rawValue: string) else { throw SwiftPkgError.invalidConfiguration("build-info key '\(key)' has illegal value: \(string)") } return value } private static func signingConfiguration(in values: [String: Any]) throws -> SigningConfiguration? { guard let value = values["signing_info"] else { return nil } guard let signing = value as? [String: Any], let identity = signing["identity"] as? String else { - throw MunkiPkgError.invalidConfiguration("signing_info must contain a string identity") + throw SwiftPkgError.invalidConfiguration("signing_info must contain a string identity") } let certificates: [String] if let certificate = signing["additional_cert_names"] as? String { certificates = [certificate] } else if let values = signing["additional_cert_names"] as? [String] { certificates = values } else if signing["additional_cert_names"] == nil { certificates = [] } - else { throw MunkiPkgError.invalidConfiguration("signing_info additional_cert_names must be a string or string array") } + else { throw SwiftPkgError.invalidConfiguration("signing_info additional_cert_names must be a string or string array") } return SigningConfiguration(identity: identity, keychain: signing["keychain"] as? String, additionalCertificateNames: certificates, usesTimestamp: signing["timestamp"] as? Bool) } private static func notarizationConfiguration(in values: [String: Any]) throws -> NotarizationConfiguration? { guard let value = values["notarization_info"] else { return nil } - guard let notary = value as? [String: Any] else { throw MunkiPkgError.invalidConfiguration("notarization_info must be a dictionary") } + guard let notary = value as? [String: Any] else { throw SwiftPkgError.invalidConfiguration("notarization_info must be a dictionary") } let authentication: NotarizationConfiguration.Authentication if let appleID = notary["apple_id"] as? String, let teamID = notary["team_id"] as? String { let password = notary["password"] as? String ?? "" @@ -295,8 +295,8 @@ public enum BuildInfoStore { case .json: object = try JSONSerialization.jsonObject(with: data) case .yaml, .yml: object = try Yams.load(yaml: String(decoding: data, as: UTF8.self)) as Any } - } catch { throw MunkiPkgError.invalidConfiguration("\(url.path) is not a valid \(format.rawValue) file: \(error.localizedDescription)") } - guard let values = object as? [String: Any] else { throw MunkiPkgError.invalidConfiguration("\(url.path) must contain a dictionary") } + } catch { throw SwiftPkgError.invalidConfiguration("\(url.path) is not a valid \(format.rawValue) file: \(error.localizedDescription)") } + guard let values = object as? [String: Any] else { throw SwiftPkgError.invalidConfiguration("\(url.path) must contain a dictionary") } return try PackageConfiguration(values: values, defaults: .defaults(for: project)) } @@ -318,19 +318,19 @@ public enum BuildInfoStore { public static func discover(in project: URL, requestedFormat: BuildInfoFormat? = nil, fileManager: FileManager = .default) throws -> BuildInfoDocument { if let requestedFormat { let url = project.appendingPathComponent("build-info").appendingPathExtension(requestedFormat.rawValue) - guard fileManager.itemExists(at: url) else { throw MunkiPkgError.invalidConfiguration("No build-info file found!") } + guard fileManager.itemExists(at: url) else { throw SwiftPkgError.invalidConfiguration("No build-info file found!") } return BuildInfoDocument(url: url, format: requestedFormat) } let baseURL = project.appendingPathComponent("build-info") let formats = BuildInfoFormat.allCases.filter { fileManager.itemExists(at: baseURL.appendingPathExtension($0.rawValue)) } - guard formats.count <= 1 else { throw MunkiPkgError.invalidConfiguration("Multiple build-info files found!") } - guard let format = formats.first else { throw MunkiPkgError.invalidConfiguration("No build-info file found!") } + guard formats.count <= 1 else { throw SwiftPkgError.invalidConfiguration("Multiple build-info files found!") } + guard let format = formats.first else { throw SwiftPkgError.invalidConfiguration("No build-info file found!") } return BuildInfoDocument(url: baseURL.appendingPathExtension(format.rawValue), format: format) } private static func format(for url: URL) throws -> BuildInfoFormat { guard let format = BuildInfoFormat(rawValue: url.pathExtension.lowercased()) else { - throw MunkiPkgError.invalidConfiguration("Unsupported build-info format: \(url.pathExtension)") + throw SwiftPkgError.invalidConfiguration("Unsupported build-info format: \(url.pathExtension)") } return format } diff --git a/swiftpkg/EnvLoader.swift b/swiftpkg/EnvLoader.swift index 86f5764..15bb626 100644 --- a/swiftpkg/EnvLoader.swift +++ b/swiftpkg/EnvLoader.swift @@ -26,14 +26,14 @@ public enum EnvLoader { let attributes = try fileManager.attributesOfItem(atPath: path) if let size = attributes[.size] as? Int, size > maxFileSize { - throw MunkiPkgError.invalidConfiguration("Environment file exceeds the \(maxFileSize)-byte limit: \(path)") + throw SwiftPkgError.invalidConfiguration("Environment file exceeds the \(maxFileSize)-byte limit: \(path)") } if let permissions = attributes[.posixPermissions] as? Int, permissions & 0o044 != 0 { console?.warning("environment file \(path) is group- or world-readable (mode 0\(String(permissions, radix: 8))). Recommend `chmod 600 \(path)`.") } guard let data = fileManager.contents(atPath: path), let content = String(data: data, encoding: .utf8) else { - throw MunkiPkgError.invalidConfiguration("Failed to read environment file: \(path)") + throw SwiftPkgError.invalidConfiguration("Failed to read environment file: \(path)") } var variables: [String: String] = [:] diff --git a/swiftpkg/Linter.swift b/swiftpkg/Linter.swift index 9fca25d..0c5e598 100644 --- a/swiftpkg/Linter.swift +++ b/swiftpkg/Linter.swift @@ -29,16 +29,16 @@ public struct Linter { /// (missing directory / undecodable build-info), which is itself a failure. public func lint(project: URL, requestedFormat: BuildInfoFormat?) throws -> [LintFinding] { guard fileManager.directoryExists(at: project) else { - throw MunkiPkgError.message("\(project.path): Project not found.") + throw SwiftPkgError.message("\(project.path): Project not found.") } var findings: [LintFinding] = [] - // Decoding failures throw MunkiPkgError; surface them as a lint error + // Decoding failures throw SwiftPkgError; surface them as a lint error // rather than a crash so `--lint` always produces a report. let configuration: PackageConfiguration do { configuration = try BuildInfoStore.load(from: project, requestedFormat: requestedFormat) - } catch let error as MunkiPkgError { + } catch let error as SwiftPkgError { return [LintFinding(.error, error.description)] } diff --git a/swiftpkg/PackageBuilder.swift b/swiftpkg/PackageBuilder.swift index bc88de5..b44b987 100644 --- a/swiftpkg/PackageBuilder.swift +++ b/swiftpkg/PackageBuilder.swift @@ -83,7 +83,7 @@ public struct PackageBuildCoordinator: @unchecked Sendable { let envPath: String if let explicit = configuration.envFile { guard fileManager.fileExists(atPath: explicit) else { - throw MunkiPkgError.invalidConfiguration("--env-file not found: \(explicit)") + throw SwiftPkgError.invalidConfiguration("--env-file not found: \(explicit)") } envPath = explicit } else { @@ -127,13 +127,13 @@ public struct PackageBuildCoordinator: @unchecked Sendable { let detail = unresolved.sorted { $0.key < $1.key } .map { "\($0.key): \($0.value.sorted().joined(separator: ", "))" } .joined(separator: "; ") - throw MunkiPkgError.invalidConfiguration("Unresolved script placeholders (--strict-env): \(detail)") + throw SwiftPkgError.invalidConfiguration("Unresolved script placeholders (--strict-env): \(detail)") } private func packageBOM(for package: URL) throws -> URL { let result = try runner.run(executable: ToolPaths.pkgutil, arguments: ["--bom", package.path]) guard result.status == 0, let path = result.stdoutString.split(whereSeparator: \.isNewline).first else { - throw MunkiPkgError.processFailed(tool: "pkgutil", message: "pkgutil returned no BOM path") + throw SwiftPkgError.processFailed(tool: "pkgutil", message: "pkgutil returned no BOM path") } return URL(fileURLWithPath: String(path)) } @@ -151,7 +151,7 @@ public struct PackageBuildCoordinator: @unchecked Sendable { !name.contains("\0"), URL(fileURLWithPath: name).lastPathComponent == name else { - throw MunkiPkgError.invalidConfiguration( + throw SwiftPkgError.invalidConfiguration( "Package name \"\(name)\" must be a single path component (no \"/\" or \"..\")." ) } @@ -184,7 +184,7 @@ private struct PackageProjectLayout { func createBuildDirectoryIfNeeded() throws { if !fileManager.itemExists(at: buildDirectory) { try fileManager.createDirectory(at: buildDirectory, withIntermediateDirectories: true) } - else if !fileManager.directoryExists(at: buildDirectory) { throw MunkiPkgError.message("\(buildDirectory.path) is not a directory.") } + else if !fileManager.directoryExists(at: buildDirectory) { throw SwiftPkgError.message("\(buildDirectory.path) is not a directory.") } } func withTemporaryDirectory(_ body: (URL) async throws -> Void) async throws { @@ -270,7 +270,7 @@ private struct ComponentPackageBuilder { try runner.runSuccessfully(executable: ToolPaths.pkgbuild, arguments: arguments, failureMessage: "pkgbuild failed while analyzing payload") let data = try Data(contentsOf: destination) guard var propertyList = try PropertyListSerialization.propertyList(from: data, format: nil) as? [[String: Any]] else { - throw MunkiPkgError.message("Couldn't read \(destination.path)") + throw SwiftPkgError.message("Couldn't read \(destination.path)") } for index in propertyList.indices where propertyList[index]["BundleIsRelocatable"] as? Bool == true { propertyList[index]["BundleIsRelocatable"] = false @@ -326,11 +326,11 @@ struct NotarizationService: Sendable { /// happened rather than what was merely requested. func notarize(package: URL, configuration: NotarizationConfiguration, skipsStapling: Bool) async throws -> Outcome { if case let .invalid(reason) = configuration.authentication { - throw MunkiPkgError.invalidConfiguration(reason) + throw SwiftPkgError.invalidConfiguration(reason) } console.display("Uploading package to Apple notary service") let submission = try plistOutput(for: ["notarytool", "submit", "--output-format", "plist", package.path] + authenticationArguments(for: configuration), failureMessage: "Notarization upload failed.") - guard let identifier = submission["id"] as? String else { throw MunkiPkgError.notarizationFailed("Unexpected output from notarytool") } + guard let identifier = submission["id"] as? String else { throw SwiftPkgError.notarizationFailed("Unexpected output from notarytool") } console.display("id \(identifier)", toolName: "notarytool") if let message = submission["message"] as? String { console.display(message, toolName: "notarytool") } let accepted = try await waitForAcceptance(identifier, configuration: configuration) @@ -351,10 +351,10 @@ struct NotarizationService: Sendable { let status = output["status"] as? String ?? "Unknown" let message = output["message"] as? String ?? "" if status == "Accepted" { console.display("Notarization successful. \(message)"); return true } - if status != "In Progress" && status != "Unknown" { throw MunkiPkgError.notarizationFailed("Notarization failed (\(status)): \(message)") } + if status != "In Progress" && status != "Unknown" { throw SwiftPkgError.notarizationFailed("Notarization failed (\(status)): \(message)") } console.display("Notarization state: \(status). Trying again in \(delay) seconds") } - throw MunkiPkgError.notarizationFailed("Timeout exceeded (\(configuration.staplingTimeout)s) waiting for notarization to complete. The package was uploaded but never confirmed Accepted, so it was not stapled. Check with 'xcrun notarytool info \(identifier)' and staple manually if it later succeeds.") + throw SwiftPkgError.notarizationFailed("Timeout exceeded (\(configuration.staplingTimeout)s) waiting for notarization to complete. The package was uploaded but never confirmed Accepted, so it was not stapled. Check with 'xcrun notarytool info \(identifier)' and staple manually if it later succeeds.") } /// Carries notarytool's own explanation through, the way every other @@ -366,10 +366,10 @@ struct NotarizationService: Sendable { do { result = try runner.run(executable: ToolPaths.xcrun, arguments: arguments) } catch { - throw MunkiPkgError.notarizationFailed("\(failureMessage) \(error.localizedDescription)") + throw SwiftPkgError.notarizationFailed("\(failureMessage) \(error.localizedDescription)") } guard result.status == 0 else { - throw MunkiPkgError.notarizationFailed("notarytool: \(result.failureDetail(fallback: failureMessage))") + throw SwiftPkgError.notarizationFailed("notarytool: \(result.failureDetail(fallback: failureMessage))") } let data: Data if result.stdoutString.hasPrefix("Generated JWT"), let newline = result.stdoutString.firstIndex(of: "\n") { @@ -381,9 +381,9 @@ struct NotarizationService: Sendable { do { object = try PropertyListSerialization.propertyList(from: data, format: nil) } catch { - throw MunkiPkgError.notarizationFailed("\(failureMessage) \(error.localizedDescription)") + throw SwiftPkgError.notarizationFailed("\(failureMessage) \(error.localizedDescription)") } - guard let plist = object as? [String: Any] else { throw MunkiPkgError.notarizationFailed(failureMessage) } + guard let plist = object as? [String: Any] else { throw SwiftPkgError.notarizationFailed(failureMessage) } return plist } diff --git a/swiftpkg/PackageImporter.swift b/swiftpkg/PackageImporter.swift index df54af5..e2c25c0 100644 --- a/swiftpkg/PackageImporter.swift +++ b/swiftpkg/PackageImporter.swift @@ -28,7 +28,7 @@ public struct PackageImporter { /// Imports a package into a new project using the requested configuration format. public func importPackage(at package: URL, to project: URL, format: BuildInfoFormat) throws { guard !fileManager.itemExists(at: project) else { - throw MunkiPkgError.projectExists("Directory \(project.path) already exists.") + throw SwiftPkgError.projectExists("Directory \(project.path) already exists.") } if fileManager.directoryExists(at: package) { try importBundlePackage(package, project: project, format: format) @@ -42,7 +42,7 @@ public struct PackageImporter { let contents = package.appendingPathComponent("Contents", isDirectory: true) let distributionFiles = try fileManager.contents(at: contents).filter { $0.hasSuffix(".dist") } guard distributionFiles.isEmpty else { - throw MunkiPkgError.importFailed("Bundle-style distribution packages are not supported for import. Consider importing the included sub-package(s).") + throw SwiftPkgError.importFailed("Bundle-style distribution packages are not supported for import. Consider importing the included sub-package(s).") } try fileManager.createDirectory(at: project, withIntermediateDirectories: false) do { @@ -104,7 +104,7 @@ public struct PackageImporter { name.hasSuffix(".pkg") && fileManager.directoryExists(at: project.appendingPathComponent(name)) } guard packages.count == 1 else { - throw MunkiPkgError.importFailed("Distribution packages to be imported must contain exactly one component package! Found: \(packages)") + throw SwiftPkgError.importFailed("Distribution packages to be imported must contain exactly one component package! Found: \(packages)") } let component = project.appendingPathComponent(packages[0]) for name in ["Bom", "PackageInfo", "Payload", "Scripts"] { @@ -129,10 +129,10 @@ public struct PackageImporter { private func convertPackageInfo(package: URL, project: URL, format: BuildInfoFormat) throws { let url = project.appendingPathComponent("PackageInfo") - guard let parser = XMLParser(contentsOf: url) else { throw MunkiPkgError.importFailed("Could not parse \(url.path)") } + guard let parser = XMLParser(contentsOf: url) else { throw SwiftPkgError.importFailed("Could not parse \(url.path)") } let delegate = PackageInfoParser() parser.delegate = delegate - guard parser.parse() else { throw MunkiPkgError.importFailed("Could not parse \(url.path): \(parser.parserError?.localizedDescription ?? "invalid XML")") } + guard parser.parse() else { throw SwiftPkgError.importFailed("Could not parse \(url.path): \(parser.parserError?.localizedDescription ?? "invalid XML")") } let attributes = delegate.attributes var values: [String: Any] = [ "identifier": attributes["identifier"] ?? "", @@ -154,9 +154,9 @@ public struct PackageImporter { do { object = try PropertyListSerialization.propertyList(from: Data(contentsOf: url), format: nil) } catch { - throw MunkiPkgError.importFailed("Could not read \(url.path): \(error.localizedDescription)") + throw SwiftPkgError.importFailed("Could not read \(url.path): \(error.localizedDescription)") } - guard let plist = object as? [String: Any] else { throw MunkiPkgError.importFailed("Could not read \(url.path)") } + guard let plist = object as? [String: Any] else { throw SwiftPkgError.importFailed("Could not read \(url.path)") } let restart = plist["IFPkgFlagRestartAction"] as? String let action: String if ["RequiredRestart", "RecommendedRestart"].contains(restart) { action = "restart" } diff --git a/swiftpkg/PackageSettingsDraft.swift b/swiftpkg/PackageSettingsDraft.swift index 6d143ac..bd7ddc8 100644 --- a/swiftpkg/PackageSettingsDraft.swift +++ b/swiftpkg/PackageSettingsDraft.swift @@ -83,14 +83,14 @@ public struct PackageSettingsDraft: Equatable, Sendable { let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedIdentifier = identifier.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedVersion = version.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmedName.isEmpty else { throw MunkiPkgError.invalidConfiguration("Package name is required.") } - guard !trimmedIdentifier.isEmpty else { throw MunkiPkgError.invalidConfiguration("Package identifier is required.") } - guard !trimmedVersion.isEmpty else { throw MunkiPkgError.invalidConfiguration("Package version is required.") } + guard !trimmedName.isEmpty else { throw SwiftPkgError.invalidConfiguration("Package name is required.") } + guard !trimmedIdentifier.isEmpty else { throw SwiftPkgError.invalidConfiguration("Package identifier is required.") } + guard !trimmedVersion.isEmpty else { throw SwiftPkgError.invalidConfiguration("Package version is required.") } let signing: SigningConfiguration? if signingEnabled { let identity = signingIdentity.trimmingCharacters(in: .whitespacesAndNewlines) - guard !identity.isEmpty else { throw MunkiPkgError.invalidConfiguration("A signing identity is required when signing is enabled.") } + guard !identity.isEmpty else { throw SwiftPkgError.invalidConfiguration("A signing identity is required when signing is enabled.") } let usesTimestamp: Bool? = switch signingTimestampMode { case .automatic: nil case .enabled: true @@ -115,16 +115,16 @@ public struct PackageSettingsDraft: Equatable, Sendable { notarization = nil case .keychainProfile: guard let profile = optional(notarizationKeychainProfile) else { - throw MunkiPkgError.invalidConfiguration("A keychain profile is required for notarization.") + throw SwiftPkgError.invalidConfiguration("A keychain profile is required for notarization.") } - guard staplingTimeout > 0 else { throw MunkiPkgError.invalidConfiguration("Stapling timeout must be greater than zero.") } + guard staplingTimeout > 0 else { throw SwiftPkgError.invalidConfiguration("Stapling timeout must be greater than zero.") } notarization = NotarizationConfiguration(authentication: .keychainProfile(profile), staplingTimeout: staplingTimeout) case .appleID: guard let appleID = optional(notarizationAppleID), let teamID = optional(notarizationTeamID) else { - throw MunkiPkgError.invalidConfiguration("Apple ID and team ID are required for Apple ID notarization.") + throw SwiftPkgError.invalidConfiguration("Apple ID and team ID are required for Apple ID notarization.") } - guard staplingTimeout > 0 else { throw MunkiPkgError.invalidConfiguration("Stapling timeout must be greater than zero.") } + guard staplingTimeout > 0 else { throw SwiftPkgError.invalidConfiguration("Stapling timeout must be greater than zero.") } let password = optional(notarizationPassword) ?? "" notarization = NotarizationConfiguration( authentication: .appleID(appleID: appleID, teamID: teamID, password: password), diff --git a/swiftpkg/PackageVerifier.swift b/swiftpkg/PackageVerifier.swift index 5a0a8c1..c8d7fff 100644 --- a/swiftpkg/PackageVerifier.swift +++ b/swiftpkg/PackageVerifier.swift @@ -17,14 +17,14 @@ struct PackageVerifier { if signed { let result = try runner.run(executable: ToolPaths.pkgutil, arguments: ["--check-signature", package.path]) guard result.status == 0 else { - throw MunkiPkgError.message("Verification failed: package is not validly signed. \(diagnostics(result))") + throw SwiftPkgError.message("Verification failed: package is not validly signed. \(diagnostics(result))") } console.display("Verified package signature") } if notarized { let result = try runner.run(executable: ToolPaths.spctl, arguments: ["-a", "-vvv", "-t", "install", package.path]) guard result.status == 0 else { - throw MunkiPkgError.message("Verification failed: package does not pass Gatekeeper assessment. \(diagnostics(result))") + throw SwiftPkgError.message("Verification failed: package does not pass Gatekeeper assessment. \(diagnostics(result))") } console.display("Verified Gatekeeper assessment") } @@ -41,7 +41,7 @@ struct PackageVerifier { defer { try? fileManager.removeItem(at: scratch) } let result = try runner.run(executable: ToolPaths.pkgutil, arguments: ["--expand", package.path, scratch.path]) guard result.status == 0 else { - throw MunkiPkgError.message("Verification failed: could not expand \(package.lastPathComponent) to inspect its metadata. \(diagnostics(result))") + throw SwiftPkgError.message("Verification failed: could not expand \(package.lastPathComponent) to inspect its metadata. \(diagnostics(result))") } // A component package carries a top-level PackageInfo. If it's absent // (e.g. a distribution-style package, whose metadata lives elsewhere) @@ -50,7 +50,7 @@ struct PackageVerifier { let xml = String(data: data, encoding: .utf8) else { return } if let mismatch = Self.metadataMismatch(expectedIdentifier: expectedIdentifier, expectedVersion: expectedVersion, packageInfoXML: xml) { - throw MunkiPkgError.message("Verification failed: \(mismatch)") + throw SwiftPkgError.message("Verification failed: \(mismatch)") } console.display("Verified package identifier and version") } diff --git a/swiftpkg/ProjectOperations.swift b/swiftpkg/ProjectOperations.swift index adfc3dc..4c4bf46 100644 --- a/swiftpkg/ProjectOperations.swift +++ b/swiftpkg/ProjectOperations.swift @@ -18,14 +18,14 @@ public struct ProjectCreator { configuration: PackageConfiguration? = nil ) throws { if fileManager.itemExists(at: project), !force { - throw MunkiPkgError.projectExists("\(project.path) already exists! Use --force to convert it to a project directory.") + throw SwiftPkgError.projectExists("\(project.path) already exists! Use --force to convert it to a project directory.") } if !fileManager.itemExists(at: project) { try fileManager.createDirectory(at: project, withIntermediateDirectories: false) } for directoryName in ["payload", "scripts", "build"] { let directory = project.appendingPathComponent(directoryName, isDirectory: true) - guard !fileManager.itemExists(at: directory) else { throw MunkiPkgError.projectExists("\(directory.path) already exists") } + guard !fileManager.itemExists(at: directory) else { throw SwiftPkgError.projectExists("\(directory.path) already exists") } try fileManager.createDirectory(at: directory, withIntermediateDirectories: false) } try BuildInfoStore.write(configuration ?? .defaults(for: project), to: project, format: format) @@ -49,7 +49,7 @@ public struct BOMMetadataService { public func exportMetadata(from bom: URL, to project: URL) throws { let result = try runner.run(executable: ToolPaths.lsbom, arguments: [bom.path]) guard result.status == 0 else { - throw MunkiPkgError.processFailed(tool: "lsbom", message: result.stderrString.trimmingCharacters(in: .whitespacesAndNewlines)) + throw SwiftPkgError.processFailed(tool: "lsbom", message: result.stderrString.trimmingCharacters(in: .whitespacesAndNewlines)) } try result.stdout.write(to: project.appendingPathComponent("Bom.txt"), options: .atomic) } @@ -66,7 +66,7 @@ public struct BOMMetadataService { public func synchronizeMetadataFromBOM(in project: URL, requestedFormat: BuildInfoFormat?) throws { let bom = project.appendingPathComponent("Bom.txt") let payload = project.appendingPathComponent("payload", isDirectory: true) - guard fileManager.itemExists(at: bom) else { throw MunkiPkgError.message("Can't sync with bom info: no Bom.txt found in project directory.") } + guard fileManager.itemExists(at: bom) else { throw SwiftPkgError.message("Can't sync with bom info: no Bom.txt found in project directory.") } let packageConfiguration = (try? BuildInfoStore.load(from: project, requestedFormat: requestedFormat)) ?? .defaults(for: project) let isRoot = geteuid() == 0 if packageConfiguration.ownership != .recommended, !isRoot { @@ -97,7 +97,7 @@ public struct BOMMetadataService { changes += 1 continue } else { - throw MunkiPkgError.message("File \(target.path) is missing in payload") + throw SwiftPkgError.message("File \(target.path) is missing in payload") } if isRoot, fileStatus.st_uid != metadata.owner || fileStatus.st_gid != metadata.group { console.display("Changing user/group of \(target.path) to \(metadata.owner)/\(metadata.group)") @@ -135,12 +135,12 @@ private struct BOMEntry { init(parsing line: String, lineNumber: Int) throws { let fields = line.split(separator: "\t", omittingEmptySubsequences: false).map(String.init) - guard fields.count >= 3 else { throw MunkiPkgError.message("Malformed Bom.txt row \(lineNumber): expected path, mode, and owner/group") } + guard fields.count >= 3 else { throw SwiftPkgError.message("Malformed Bom.txt row \(lineNumber): expected path, mode, and owner/group") } var path = fields[0] if path.hasPrefix("./") { path.removeFirst(2) } let ownerGroup = fields[2].split(separator: "/", omittingEmptySubsequences: false) guard ownerGroup.count == 2, let owner = uid_t(ownerGroup[0]), let group = gid_t(ownerGroup[1]), let mode = mode_t(String(fields[1].suffix(4)), radix: 8) else { - throw MunkiPkgError.message("Malformed Bom.txt metadata on row \(lineNumber)") + throw SwiftPkgError.message("Malformed Bom.txt metadata on row \(lineNumber)") } relativePath = path self.mode = mode @@ -150,6 +150,6 @@ private struct BOMEntry { } } -private func posixError(_ action: String, path: String) -> MunkiPkgError { +private func posixError(_ action: String, path: String) -> SwiftPkgError { .message("\(action) for \(path): \(String(cString: strerror(errno)))") } diff --git a/swiftpkg/Support.swift b/swiftpkg/Support.swift index 0ba0abb..a6c888d 100644 --- a/swiftpkg/Support.swift +++ b/swiftpkg/Support.swift @@ -1,7 +1,7 @@ import Darwin import Foundation -public enum MunkiPkgError: Error, CustomStringConvertible, LocalizedError { +public enum SwiftPkgError: Error, CustomStringConvertible, LocalizedError { case message(String) case invalidConfiguration(String) case processFailed(tool: String, message: String) @@ -43,7 +43,7 @@ public let usageErrorExitCode: Int32 = 64 /// Maps any thrown error to a process exit code, defaulting unknown errors to 1. public func exitCode(for error: any Error) -> Int32 { - (error as? MunkiPkgError)?.exitCode ?? 1 + (error as? SwiftPkgError)?.exitCode ?? 1 } public struct ProcessResult: Equatable, Sendable { @@ -92,7 +92,7 @@ public extension ProcessRunning { func runSuccessfully(executable: String, arguments: [String], failureMessage: String) throws { let result = try run(executable: executable, arguments: arguments) guard result.status == 0 else { - throw MunkiPkgError.processFailed(tool: URL(fileURLWithPath: executable).lastPathComponent, message: result.failureDetail(fallback: failureMessage)) + throw SwiftPkgError.processFailed(tool: URL(fileURLWithPath: executable).lastPathComponent, message: result.failureDetail(fallback: failureMessage)) } } } @@ -120,7 +120,7 @@ public final class SystemProcessRunner: ProcessRunning, ProcessControlling, @unc do { try process.run() } catch { - throw MunkiPkgError.message("\(URL(fileURLWithPath: executable).lastPathComponent) execution failed: \(error.localizedDescription)") + throw SwiftPkgError.message("\(URL(fileURLWithPath: executable).lastPathComponent) execution failed: \(error.localizedDescription)") } // Drain stdout and stderr concurrently on background queues *before* diff --git a/swiftpkgCLI/CLI.swift b/swiftpkgCLI/CLI.swift index 3caf09d..be4e375 100644 --- a/swiftpkgCLI/CLI.swift +++ b/swiftpkgCLI/CLI.swift @@ -136,7 +136,7 @@ public enum CLICommand { private static func requestedFormat(from options: CLIOptions) throws -> BuildInfoFormat? { guard !(options.json && options.yaml) else { - throw MunkiPkgError.invalidConfiguration("Only a single build-info file can be built at a time!") + throw SwiftPkgError.invalidConfiguration("Only a single build-info file can be built at a time!") } if options.json { return .json } if options.yaml { return .yaml } diff --git a/swiftpkgCLI/SwiftPkg.swift b/swiftpkgCLI/SwiftPkg.swift index 5c28271..c13f8c3 100644 --- a/swiftpkgCLI/SwiftPkg.swift +++ b/swiftpkgCLI/SwiftPkg.swift @@ -46,7 +46,7 @@ public enum SwiftPkg { .importPackage(at: package, to: project, format: format) case let .synchronize(project, requestedFormat): guard fileManager.directoryExists(at: project) else { - throw MunkiPkgError.message(fileManager.itemExists(at: project) + throw SwiftPkgError.message(fileManager.itemExists(at: project) ? "\(project.path) is not a directory." : "\(project.path): Project not found.") } @@ -66,7 +66,7 @@ public enum SwiftPkg { return 0 case let .build(project, configuration): guard fileManager.directoryExists(at: project) else { - throw MunkiPkgError.message(fileManager.itemExists(at: project) + throw SwiftPkgError.message(fileManager.itemExists(at: project) ? "\(project.path) is not a directory." : "\(project.path): Project not found.") } diff --git a/swiftpkgTests/CLITests.swift b/swiftpkgTests/CLITests.swift index 0593c2f..a7b69ab 100644 --- a/swiftpkgTests/CLITests.swift +++ b/swiftpkgTests/CLITests.swift @@ -109,7 +109,7 @@ struct CLITests { } private func parsedOptions(_ arguments: [String]) throws -> CLIOptions { - guard case let .options(options) = CLIParser.parse(arguments) else { throw MunkiPkgError.message("Expected CLI options") } + guard case let .options(options) = CLIParser.parse(arguments) else { throw SwiftPkgError.message("Expected CLI options") } return options } } diff --git a/swiftpkgTests/EnvLoaderTests.swift b/swiftpkgTests/EnvLoaderTests.swift index ba3f037..0acbd39 100644 --- a/swiftpkgTests/EnvLoaderTests.swift +++ b/swiftpkgTests/EnvLoaderTests.swift @@ -44,7 +44,7 @@ struct EnvLoaderTests { defer { temp.remove() } let path = temp.url.appendingPathComponent(".env").path try write(String(repeating: "A=B\n", count: EnvLoader.maxFileSize), to: URL(fileURLWithPath: path)) - #expect(throws: MunkiPkgError.self) { try EnvLoader.load(from: path) } + #expect(throws: SwiftPkgError.self) { try EnvLoader.load(from: path) } } @Test("missing file yields no variables") diff --git a/swiftpkgTests/ExitCodeTests.swift b/swiftpkgTests/ExitCodeTests.swift index fc106a1..5d1ba4f 100644 --- a/swiftpkgTests/ExitCodeTests.swift +++ b/swiftpkgTests/ExitCodeTests.swift @@ -7,19 +7,19 @@ struct ExitCodeTests { @Test("each error class maps to its documented exit code") func errorExitCodes() { - #expect(MunkiPkgError.message("x").exitCode == 1) - #expect(MunkiPkgError.projectExists("x").exitCode == 2) - #expect(MunkiPkgError.invalidConfiguration("x").exitCode == 3) - #expect(MunkiPkgError.importFailed("x").exitCode == 4) - #expect(MunkiPkgError.processFailed(tool: "t", message: "m").exitCode == 5) - #expect(MunkiPkgError.notarizationFailed("x").exitCode == 7) + #expect(SwiftPkgError.message("x").exitCode == 1) + #expect(SwiftPkgError.projectExists("x").exitCode == 2) + #expect(SwiftPkgError.invalidConfiguration("x").exitCode == 3) + #expect(SwiftPkgError.importFailed("x").exitCode == 4) + #expect(SwiftPkgError.processFailed(tool: "t", message: "m").exitCode == 5) + #expect(SwiftPkgError.notarizationFailed("x").exitCode == 7) } @Test("unknown errors default to exit 1") func unknownErrorDefault() { struct Other: Error {} #expect(exitCode(for: Other()) == 1) - #expect(exitCode(for: MunkiPkgError.notarizationFailed("x")) == 7) + #expect(exitCode(for: SwiftPkgError.notarizationFailed("x")) == 7) } // End-to-end through the CLI entry point. diff --git a/swiftpkgTests/NotarizationServiceTests.swift b/swiftpkgTests/NotarizationServiceTests.swift index 46fea07..953f7b9 100644 --- a/swiftpkgTests/NotarizationServiceTests.swift +++ b/swiftpkgTests/NotarizationServiceTests.swift @@ -32,7 +32,7 @@ struct NotarizationServiceTests { func timeoutThrows() async throws { let runner = ScriptedRunner([try plistResult(["id": "submission-abc"])]) let configuration = NotarizationConfiguration(authentication: .keychainProfile("profile"), staplingTimeout: 0) - await #expect(throws: MunkiPkgError.self) { + await #expect(throws: SwiftPkgError.self) { try await NotarizationService(runner: runner, console: Console(quiet: true)) .notarize(package: URL(fileURLWithPath: "/tmp/does-not-matter.pkg"), configuration: configuration, skipsStapling: false) } diff --git a/swiftpkgTests/PackageNameValidationTests.swift b/swiftpkgTests/PackageNameValidationTests.swift index ff39e05..c8b8185 100644 --- a/swiftpkgTests/PackageNameValidationTests.swift +++ b/swiftpkgTests/PackageNameValidationTests.swift @@ -14,7 +14,7 @@ struct PackageNameValidationTests { "", ]) func rejectsUnsafeNames(_ name: String) { - #expect(throws: MunkiPkgError.self) { + #expect(throws: SwiftPkgError.self) { try PackageBuildCoordinator.validatePackageName(name) } } @@ -45,7 +45,7 @@ struct PackageNameValidationTests { let runner = RecordingRunner() let coordinator = PackageBuildCoordinator(fileManager: .default, runner: runner, console: makeConsole()) - await #expect(throws: MunkiPkgError.self) { + await #expect(throws: SwiftPkgError.self) { try await coordinator.buildPackage(in: project, configuration: PackageBuildOptions()) } #expect(runner.calls.isEmpty) diff --git a/swiftpkgTests/PackageVerifierTests.swift b/swiftpkgTests/PackageVerifierTests.swift index 86205fd..d26c2dd 100644 --- a/swiftpkgTests/PackageVerifierTests.swift +++ b/swiftpkgTests/PackageVerifierTests.swift @@ -45,7 +45,7 @@ struct PackageVerifierTests { func signedFails() throws { let (temp, package) = try makePackage(); defer { temp.remove() } let runner = runnerFailing(ToolPaths.pkgutil) - #expect(throws: MunkiPkgError.self) { + #expect(throws: SwiftPkgError.self) { try PackageVerifier(runner: runner, console: makeConsole()) .verify(package: package, expectedIdentifier: "com.example.app", expectedVersion: "1.0", signed: true, notarized: false) } @@ -56,7 +56,7 @@ struct PackageVerifierTests { let (temp, package) = try makePackage(); defer { temp.remove() } let runner = RecordingRunner() runner.result = ProcessResult(status: 1, stdout: Data(), stderr: Data("corrupt".utf8)) - #expect(throws: MunkiPkgError.self) { + #expect(throws: SwiftPkgError.self) { try PackageVerifier(runner: runner, console: makeConsole()) .verify(package: package, expectedIdentifier: "com.example.app", expectedVersion: "1.0", signed: false, notarized: false) } @@ -66,7 +66,7 @@ struct PackageVerifierTests { func notarizedRunsSpctl() throws { let (temp, package) = try makePackage(); defer { temp.remove() } let runner = runnerFailing(ToolPaths.spctl) - #expect(throws: MunkiPkgError.self) { + #expect(throws: SwiftPkgError.self) { try PackageVerifier(runner: runner, console: makeConsole()) .verify(package: package, expectedIdentifier: "com.example.app", expectedVersion: "1.0", signed: false, notarized: true) } diff --git a/swiftpkgTests/SystemProcessRunnerTests.swift b/swiftpkgTests/SystemProcessRunnerTests.swift index b2edc61..3691ce8 100644 --- a/swiftpkgTests/SystemProcessRunnerTests.swift +++ b/swiftpkgTests/SystemProcessRunnerTests.swift @@ -29,7 +29,7 @@ struct SystemProcessRunnerTests { @Test("reports a structured error when the executable cannot launch") func reportsLaunchFailure() { - #expect(throws: MunkiPkgError.self) { + #expect(throws: SwiftPkgError.self) { try SystemProcessRunner().run(executable: "/nonexistent/tool/swiftpkg-should-not-exist", arguments: []) } }