diff --git a/Package.resolved b/Package.resolved index 0e11c10..10a03c4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "fed7a14478b4c8a52a4008849ebf19e69bc3eca3567f1751720d7a3a620a2fd8", + "originHash" : "0bdceed19ddcad249a7e6a65e3ec30681548b31614dc32c9b7e7137100a7a60f", "pins" : [ { "identity" : "ansiterminalmodified", @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nikolainobadi/NnSwiftDataKit.git", "state" : { - "revision" : "bc2aac114b8c708ef9446f2aa77e4c74014ff4ad", - "version" : "0.5.0" + "revision" : "40a87578928ea9f706eb42f391795393450ace38", + "version" : "0.9.0" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nikolainobadi/SwiftPickerKit.git", "state" : { - "revision" : "6773ad737b1d9acd9647fd03300a1ab774eb3afa", - "version" : "0.8.1" + "branch" : "open-mock-picker", + "revision" : "29dd6a78d82eef7ae13a1340824d9f806acf5f2c" } } ], diff --git a/Package.swift b/Package.swift index b79ea9d..6ecc508 100644 --- a/Package.swift +++ b/Package.swift @@ -26,9 +26,9 @@ let package = Package( .package(url: "https://github.com/JohnSundell/Files", from: "4.0.0"), .package(url: "https://github.com/nikolainobadi/NnGitKit.git", from: "0.6.0"), .package(url: "https://github.com/nikolainobadi/NnShellKit.git", from: "2.2.0"), - .package(url: "https://github.com/nikolainobadi/NnSwiftDataKit", exact: "0.5.0"), + .package(url: "https://github.com/nikolainobadi/NnSwiftDataKit", from: "0.9.0"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), - .package(url: "https://github.com/nikolainobadi/SwiftPickerKit.git", from: "0.8.0") + .package(url: "https://github.com/nikolainobadi/SwiftPickerKit.git", branch: "open-mock-picker") ], targets: [ .executableTarget( diff --git a/Sources/NnexKit/Building/BuildConfig.swift b/Sources/NnexKit/Building/BuildConfig.swift index 888bccd..eefb51a 100644 --- a/Sources/NnexKit/Building/BuildConfig.swift +++ b/Sources/NnexKit/Building/BuildConfig.swift @@ -11,7 +11,7 @@ public struct BuildConfig: Sendable { public let buildType: BuildType public let extraBuildArgs: [String] public let skipClean: Bool - public let testCommand: TestCommand? + public let testCommand: CurrentSchema.TestCommand? /// Initializes a new `BuildConfig` with the specified settings. /// @@ -22,7 +22,7 @@ public struct BuildConfig: Sendable { /// - extraBuildArgs: Additional arguments to pass to the build command. /// - shouldClean: Indicates whether the project should be cleaned before building. Defaults to `true`. /// - testCommand: An optional command to run tests after building. Defaults to `nil`, meaning no tests will be run. - public init(projectName: String, projectPath: String, buildType: BuildType, extraBuildArgs: [String], skipClean: Bool, testCommand: TestCommand?) { + public init(projectName: String, projectPath: String, buildType: BuildType, extraBuildArgs: [String], skipClean: Bool, testCommand: CurrentSchema.TestCommand?) { self.projectName = projectName self.projectPath = projectPath.hasSuffix("/") ? projectPath : projectPath + "/" self.buildType = buildType diff --git a/Sources/NnexKit/Formula/PublishUtilities.swift b/Sources/NnexKit/Formula/PublishUtilities.swift index 4c2df77..468a0b7 100644 --- a/Sources/NnexKit/Formula/PublishUtilities.swift +++ b/Sources/NnexKit/Formula/PublishUtilities.swift @@ -17,7 +17,7 @@ public enum PublishUtilities { /// - shell: The shell instance to use for building. /// - Returns: The binary output including path(s) and hash(es). /// - Throws: An error if the build process fails. - public static func buildBinary(formula: SwiftDataFormula, buildType: BuildType, skipTesting: Bool, shell: any NnexShell) throws -> BinaryOutput { + public static func buildBinary(formula: SwiftDataHomebrewFormula, buildType: BuildType, skipTesting: Bool, shell: any NnexShell) throws -> BinaryOutput { let testCommand = skipTesting ? nil : formula.testCommand let config = BuildConfig(projectName: formula.name, projectPath: formula.localProjectPath, buildType: buildType, extraBuildArgs: formula.extraBuildArgs, skipClean: false, testCommand: testCommand) let builder = ProjectBuilder(shell: shell, config: config) @@ -52,7 +52,7 @@ public enum PublishUtilities { /// - assetURLs: The asset URLs from the GitHub release. /// - Returns: The formula content as a string. /// - Throws: An error if formula generation fails. - public static func makeFormulaContent(formula: SwiftDataFormula, version: String, archivedBinaries: [ArchivedBinary], assetURLs: [String]) throws -> String { + public static func makeFormulaContent(formula: SwiftDataHomebrewFormula, version: String, archivedBinaries: [ArchivedBinary], assetURLs: [String]) throws -> String { let formulaName = formula.name if archivedBinaries.count == 1 { diff --git a/Sources/NnexKit/Formula/SwiftDataFormula.swift b/Sources/NnexKit/Formula/SwiftDataFormula.swift deleted file mode 100644 index 1d23155..0000000 --- a/Sources/NnexKit/Formula/SwiftDataFormula.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// SwiftDataFormula.swift -// nnex -// -// Created by Nikolai Nobadi on 3/22/25. -// - -import SwiftData - -/// Represents a Homebrew formula with metadata and build configuration. -@Model -public final class SwiftDataFormula { - /// The name of the formula. - public var name: String - - /// A description of the formula. - public var details: String - - /// The homepage URL of the formula. - public var homepage: String - - /// The license under which the formula is distributed. - public var license: String - - /// The local path of the project associated with the formula. - public var localProjectPath: String - - /// The upload type for the formula (binary or tarball). - public var uploadType: FormulaUploadType - - /// The test command to use before building the formula's executable. - public var testCommand: TestCommand? - - /// Additional build arguments for the formula. - public var extraBuildArgs: [String] - - /// The tap associated with the formula. - public var tap: SwiftDataTap? - - /// Initializes a new instance of SwiftDataFormula. - /// - Parameters: - /// - name: The name of the formula. - /// - details: A description of the formula. - /// - homepage: The homepage URL of the formula. - /// - license: The license of the formula. - /// - localProjectPath: The local path of the associated project. - /// - uploadType: The upload type (binary or tarball). - /// - testCommand: The test command to use before building - /// - extraBuildArgs: Additional build arguments. - public init(name: String, details: String, homepage: String, license: String, localProjectPath: String, uploadType: FormulaUploadType, testCommand: TestCommand?, extraBuildArgs: [String]) { - self.name = name - self.details = details - self.homepage = homepage - self.license = license - self.localProjectPath = localProjectPath - self.uploadType = uploadType - self.testCommand = testCommand - self.extraBuildArgs = extraBuildArgs - } -} - - -// MARK: - Dependencies -/// Represents the upload type for a formula. -public enum FormulaUploadType: String, Codable, Sendable { - /// Upload as a binary file. - case binary - - /// Upload as a tarball file. - case tarball -} - -/// Specifies the command to use for running tests. -public enum TestCommand: Codable, Sendable { - /// Uses the default `swift test` command. - case defaultCommand - /// Uses a custom test command provided as a string. - case custom(String) -} diff --git a/Sources/NnexKit/Persistence/NnexContext.swift b/Sources/NnexKit/SwiftData/Context/NnexContext.swift similarity index 53% rename from Sources/NnexKit/Persistence/NnexContext.swift rename to Sources/NnexKit/SwiftData/Context/NnexContext.swift index 3a0c97d..f3dd3b6 100644 --- a/Sources/NnexKit/Persistence/NnexContext.swift +++ b/Sources/NnexKit/SwiftData/Context/NnexContext.swift @@ -9,81 +9,90 @@ import SwiftData import Foundation import NnSwiftDataKit -/// Manages the SwiftData model context and application configuration. public final class NnexContext { + private let context: ModelContext private let defaults: UserDefaults private let defaultBuildTypeKey = "defaultBuildTypeKey" private let tapListFolderPathKey = "tapListFolderPathKey" - /// The model context for interacting with SwiftData models. - public let context: ModelContext - - /// Initializes a new NnexContext instance. - /// - Parameters: - /// - appGroupId: The application group identifier. - /// - config: An optional model configuration. - /// - defaults: An optional UserDefaults instance. - public init(appGroupId: String, config: ModelConfiguration? = nil, defaults: UserDefaults? = nil) throws { - if let config, let defaults { - let container = try ModelContainer(for: SwiftDataTap.self, configurations: config) - self.context = .init(container) - self.defaults = defaults + init(schema: Schema, userDefaultsTestSuiteName: String?) throws { + let identifier = "com.nobadi.nnex" + let oldAppGroupId = "R8SJ24LQF3.\(identifier)" +// let appGroupId = "group.\(identifier)" // TODO: - + let appGroupId = oldAppGroupId + + if let userDefaultsTestSuiteName { + defaults = .init(suiteName: userDefaultsTestSuiteName)! + defaults.removePersistentDomain(forName: userDefaultsTestSuiteName) + context = try .init(.init(for: schema, configurations: .init(isStoredInMemoryOnly: true))) } else { - let (config, defaults) = try configureSwiftDataContainer(appGroupId: appGroupId) - let container = try ModelContainer(for: SwiftDataTap.self, configurations: config) - self.context = .init(container) + // TODO: - +// try migrateAppGroupSwiftDataStoreIfNeeded(from: oldAppGroupId, to: appGroupId) + let (container, defaults) = try makeAppGroupModelContainer(schema: schema, appGroupId: appGroupId) + self.defaults = defaults + self.context = .init(container) } } } + +// MARK: - Init +public extension NnexContext { + convenience init( userDefaultsTestSuiteName: String? = nil) throws { + try self.init(schema: .init(versionedSchema: CurrentSchema.self), userDefaultsTestSuiteName: userDefaultsTestSuiteName) + } +} + + // MARK: - UserDefaults -extension NnexContext { +public extension NnexContext { /// Saves the folder path for the tap list. /// - Parameter path: The folder path to save. - public func saveTapListFolderPath(path: String) { + func saveTapListFolderPath(path: String) { defaults.set(path, forKey: tapListFolderPathKey) } /// Loads the folder path for the tap list. /// - Returns: The saved folder path or nil if not set. - public func loadTapListFolderPath() -> String? { + func loadTapListFolderPath() -> String? { guard let path = defaults.string(forKey: tapListFolderPathKey), !path.isEmpty else { return nil } return path } /// Saves the default build type. /// - Parameter buildType: The build type to save. - public func saveDefaultBuildType(_ buildType: BuildType) { + func saveDefaultBuildType(_ buildType: BuildType) { defaults.set(buildType, forKey: defaultBuildTypeKey) } /// Loads the default build type. /// - Returns: The saved build type or a default value if not set. - public func loadDefaultBuildType() -> BuildType { + func loadDefaultBuildType() -> BuildType { return defaults.object(forKey: defaultBuildTypeKey) as? BuildType ?? .universal } } + // MARK: - SwiftData -extension NnexContext { +public extension NnexContext { /// Loads all saved taps from the SwiftData context. - /// - Returns: An array of SwiftDataTap objects. - public func loadTaps() throws -> [SwiftDataTap] { - return try context.fetch(FetchDescriptor()) + /// - Returns: An array of SwiftDataHomebrewTap objects. + func loadTaps() throws -> [SwiftDataHomebrewTap] { + return try context.fetch(FetchDescriptor()) } /// Loads all saved formulas from the SwiftData context. - /// - Returns: An array of SwiftDataFormula objects. - public func loadFormulas() throws -> [SwiftDataFormula] { - return try context.fetch(FetchDescriptor()) + /// - Returns: An array of SwiftDataHomebrewFormula objects. + func loadFormulas() throws -> [SwiftDataHomebrewFormula] { + return try context.fetch(FetchDescriptor()) } /// Saves a new tap with associated formulas. /// - Parameters: /// - tap: The tap to save. /// - formulas: An optional array of formulas to associate with the tap. - public func saveNewTap(_ tap: SwiftDataTap, formulas: [SwiftDataFormula] = []) throws { + func saveNewTap(_ tap: SwiftDataHomebrewTap, formulas: [SwiftDataHomebrewFormula] = []) throws { context.insert(tap) for formula in formulas { context.insert(formula) @@ -95,7 +104,7 @@ extension NnexContext { /// Deletes the specified tap and its associated formulas. /// - Parameter tap: The tap to delete. - public func deleteTap(_ tap: SwiftDataTap) throws { + func deleteTap(_ tap: SwiftDataHomebrewTap) throws { for formula in tap.formulas { context.delete(formula) } @@ -107,19 +116,19 @@ extension NnexContext { /// - Parameters: /// - formula: The formula to save. /// - tap: The tap to associate with the formula. - public func saveNewFormula(_ formula: SwiftDataFormula, in tap: SwiftDataTap) throws { + func saveNewFormula(_ formula: SwiftDataHomebrewFormula, in tap: SwiftDataHomebrewTap) throws { context.insert(formula) tap.formulas.append(formula) formula.tap = tap try context.save() } - public func deleteFormula(_ formula: SwiftDataFormula) throws { + func deleteFormula(_ formula: SwiftDataHomebrewFormula) throws { context.delete(formula) try context.save() } - public func saveChanges() throws { + func saveChanges() throws { try context.save() } } diff --git a/Sources/NnexKit/Formula/SwiftDataFormula+Extensions.swift b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula+Extensions.swift similarity index 74% rename from Sources/NnexKit/Formula/SwiftDataFormula+Extensions.swift rename to Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula+Extensions.swift index 7ac6397..336db88 100644 --- a/Sources/NnexKit/Formula/SwiftDataFormula+Extensions.swift +++ b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula+Extensions.swift @@ -1,15 +1,15 @@ // -// SwiftDataFormula+Extensions.swift +// SwiftDataHomebrewFormula+Extensions.swift // nnex // // Created by Nikolai Nobadi on 3/25/25. // -public extension SwiftDataFormula { - /// Initializes a SwiftDataFormula instance from a BrewFormula. +public extension SwiftDataHomebrewFormula { + /// Initializes a SwiftDataHomebrewFormula instance from a BrewFormula. /// - Parameter brewFormula: The BrewFormula to convert. convenience init(from brewFormula: BrewFormula) { - var uploadType = FormulaUploadType.binary + var uploadType = CurrentSchema.FormulaUploadType.binary if let stableURL = brewFormula.versions.stable { uploadType = stableURL.contains(".tar.gz") ? .tarball : .binary @@ -26,4 +26,4 @@ public extension SwiftDataFormula { extraBuildArgs: [] ) } -} \ No newline at end of file +} diff --git a/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula.swift b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula.swift new file mode 100644 index 0000000..fe74aba --- /dev/null +++ b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewFormula.swift @@ -0,0 +1,8 @@ +// +// SwiftDataHomebrewFormula.swift +// nnex +// +// Created by Nikolai Nobadi on 3/22/25. +// + +public typealias SwiftDataHomebrewFormula = CurrentSchema.SwiftDataFormula diff --git a/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewTap.swift b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewTap.swift new file mode 100644 index 0000000..7de07df --- /dev/null +++ b/Sources/NnexKit/SwiftData/Models/SwiftDataHomebrewTap.swift @@ -0,0 +1,8 @@ +// +// SwiftDataHomebrewTap.swift +// nnex +// +// Created by Nikolai Nobadi on 3/22/25. +// + +public typealias SwiftDataHomebrewTap = CurrentSchema.SwiftDataTap diff --git a/Sources/NnexKit/SwiftData/Schema/CurrentSchema.swift b/Sources/NnexKit/SwiftData/Schema/CurrentSchema.swift new file mode 100644 index 0000000..4700ebc --- /dev/null +++ b/Sources/NnexKit/SwiftData/Schema/CurrentSchema.swift @@ -0,0 +1,8 @@ +// +// CurrentSchema.swift +// nnex +// +// Created by Nikolai Nobadi on 12/10/25. +// + +public typealias CurrentSchema = FirstSchema diff --git a/Sources/NnexKit/SwiftData/Schema/FirstSchema.swift b/Sources/NnexKit/SwiftData/Schema/FirstSchema.swift new file mode 100644 index 0000000..6ddc0d5 --- /dev/null +++ b/Sources/NnexKit/SwiftData/Schema/FirstSchema.swift @@ -0,0 +1,83 @@ +// +// FirstSchema.swift +// nnex +// +// Created by Nikolai Nobadi on 12/10/25. +// + +@preconcurrency import SwiftData + +public enum FirstSchema: VersionedSchema { + public static let versionIdentifier: Schema.Version = .init(1, 0, 0) + public static var models: [any PersistentModel.Type] { + return [ + SwiftDataTap.self, + SwiftDataFormula.self + ] + } +} + + +// MARK: - Tap +extension FirstSchema { + @Model + public final class SwiftDataTap { + @Attribute(.unique) public var name: String + @Attribute(.unique) public var localPath: String + @Attribute(.unique) public var remotePath: String + @Relationship(deleteRule: .cascade, inverse: \SwiftDataFormula.tap) public var formulas: [SwiftDataFormula] = [] + + public init(name: String, localPath: String, remotePath: String) { + self.name = name + self.localPath = localPath + self.remotePath = remotePath + } + } +} + + +// MARK: - Formula +extension FirstSchema { + @Model + public final class SwiftDataFormula { + public var name: String + public var details: String + public var homepage: String + public var license: String + public var localProjectPath: String + public var uploadType: FormulaUploadType + public var testCommand: TestCommand? + public var extraBuildArgs: [String] + public var tap: SwiftDataTap? + + public init( + name: String, + details: String, + homepage: String, + license: String, + localProjectPath: String, + uploadType: FormulaUploadType, + testCommand: TestCommand?, + extraBuildArgs: [String] + ) { + self.name = name + self.details = details + self.homepage = homepage + self.license = license + self.localProjectPath = localProjectPath + self.uploadType = uploadType + self.testCommand = testCommand + self.extraBuildArgs = extraBuildArgs + } + } + + public enum FormulaUploadType: String, Codable, Sendable { + case binary + case tarball + } + + public enum TestCommand: Codable, Sendable { + case defaultCommand + case custom(String) + } +} diff --git a/Sources/NnexKit/Tap/SwiftDataTap.swift b/Sources/NnexKit/Tap/SwiftDataTap.swift deleted file mode 100644 index 0a66fb6..0000000 --- a/Sources/NnexKit/Tap/SwiftDataTap.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// SwiftDataTap.swift -// nnex -// -// Created by Nikolai Nobadi on 3/22/25. -// - -import SwiftData - -/// Represents a Homebrew Tap with associated formulas. -@Model -public final class SwiftDataTap { - /// The name of the tap (unique identifier). - @Attribute(.unique) public var name: String - - /// The local file path of the tap (unique identifier). - @Attribute(.unique) public var localPath: String - - /// The remote URL of the tap (unique identifier). - @Attribute(.unique) public var remotePath: String - - /// The list of formulas associated with this tap. - @Relationship(deleteRule: .cascade, inverse: \SwiftDataFormula.tap) public var formulas: [SwiftDataFormula] = [] - - /// Initializes a new SwiftDataTap instance. - /// - Parameters: - /// - name: The name of the tap. - /// - localPath: The local file path of the tap. - /// - remotePath: The remote URL of the tap. - public init(name: String, localPath: String, remotePath: String) { - self.name = name - self.localPath = localPath - self.remotePath = remotePath - } -} diff --git a/Sources/nnex/Commands/Brew/ImportTap.swift b/Sources/nnex/Commands/Brew/ImportTap.swift index 4d1e99a..f291cbf 100644 --- a/Sources/nnex/Commands/Brew/ImportTap.swift +++ b/Sources/nnex/Commands/Brew/ImportTap.swift @@ -33,9 +33,9 @@ extension Nnex.Brew { formulaFiles = [] } - let tap = SwiftDataTap(name: tapName, localPath: folder.path, remotePath: remotePath) + let tap = SwiftDataHomebrewTap(name: tapName, localPath: folder.path, remotePath: remotePath) - var formulas: [SwiftDataFormula] = [] + var formulas: [SwiftDataHomebrewFormula] = [] for file in formulaFiles { if let brewFormula = try decodeBrewFormula(file) { diff --git a/Sources/nnex/Core/Context/DefaultContextFactory.swift b/Sources/nnex/Core/Context/DefaultContextFactory.swift index 2390c2f..8efa95c 100644 --- a/Sources/nnex/Core/Context/DefaultContextFactory.swift +++ b/Sources/nnex/Core/Context/DefaultContextFactory.swift @@ -8,8 +8,6 @@ import NnexKit import SwiftPickerKit -let APP_GROUP_ID = "R8SJ24LQF3.com.nobadi.nnex" - struct DefaultContextFactory: ContextFactory { func makeShell() -> any NnexShell { return DefaultShell() @@ -24,7 +22,7 @@ struct DefaultContextFactory: ContextFactory { } func makeContext() throws -> NnexContext { - return try .init(appGroupId: APP_GROUP_ID) + return try .init() } func makeProjectDetector() -> any ProjectDetector { diff --git a/Sources/nnex/Domain/Execution/CreateTapManager.swift b/Sources/nnex/Domain/Execution/CreateTapManager.swift index 95c7308..5054e7e 100644 --- a/Sources/nnex/Domain/Execution/CreateTapManager.swift +++ b/Sources/nnex/Domain/Execution/CreateTapManager.swift @@ -46,7 +46,7 @@ extension CreateTapManager { visibility: visibility ) - let newTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: remotePath) + let newTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: remotePath) try context.saveNewTap(newTap) } } diff --git a/Sources/nnex/Domain/Execution/PublishExecutionManager.swift b/Sources/nnex/Domain/Execution/PublishExecutionManager.swift index e9c7955..15b3c23 100644 --- a/Sources/nnex/Domain/Execution/PublishExecutionManager.swift +++ b/Sources/nnex/Domain/Execution/PublishExecutionManager.swift @@ -83,7 +83,7 @@ private extension PublishExecutionManager { /// - skipTests: Whether to skip tests during loading. /// - Returns: A tuple containing the tap, formula, and build type. /// - Throws: An error if the tap or formula cannot be found. - func getTapAndFormula(projectFolder: Folder, buildType: BuildType, skipTests: Bool) throws -> (SwiftDataTap, SwiftDataFormula, BuildType) { + func getTapAndFormula(projectFolder: Folder, buildType: BuildType, skipTests: Bool) throws -> (SwiftDataHomebrewTap, SwiftDataHomebrewFormula, BuildType) { let (tap, formula) = try publishInfoLoader.loadPublishInfo() // Note: The formula's localProjectPath update is now handled by PublishInfoLoader if needed @@ -112,7 +112,7 @@ private extension PublishExecutionManager { /// - message: An optional commit message. /// - tap: The Homebrew tap to publish to. /// - Throws: An error if the formula cannot be published. - func publishFormula(_ content: String, formulaName: String, message: String?, tap: SwiftDataTap) throws { + func publishFormula(_ content: String, formulaName: String, message: String?, tap: SwiftDataHomebrewTap) throws { let publisher = FormulaPublisher(gitHandler: gitHandler) let commitMessage = try getMessage(message: message) let formulaPath = try publisher.publishFormula(content, formulaName: formulaName, commitMessage: commitMessage, tapFolderPath: tap.localPath) diff --git a/Sources/nnex/Domain/Handlers/ReleaseNotesHandler.swift b/Sources/nnex/Domain/Handlers/ReleaseNotesHandler.swift index 716bc5f..f1d495c 100644 --- a/Sources/nnex/Domain/Handlers/ReleaseNotesHandler.swift +++ b/Sources/nnex/Domain/Handlers/ReleaseNotesHandler.swift @@ -6,6 +6,7 @@ // import Files +import NnexKit import Foundation import GitCommandGen @@ -31,10 +32,12 @@ extension ReleaseNotesHandler { return .init(content: notes, isFromFile: false) case .selectFile: - fatalError() // TODO: - need to abstract Files in order to enable this -// let selection = try picker.requiredBrowseSelection(prompt: "Select the file containing your release notes", allowSelectingFolders: false) -// -// return .init(content: selection.url.path(), isFromFile: true) + let homeDirectoryURL = FileManager.default.homeDirectoryForCurrentUser + guard let selection = picker.browseDirectories(prompt: "Select the file containing your release notes.", startURL: homeDirectoryURL, showPromptText: true, showSelectedItemText: true, selectionType: .onlyFiles) else { + throw NnexError.selectionRequired + } + + return .init(content: selection.url.path(), isFromFile: true) case .fromPath: let filePath = try picker.getRequiredInput(prompt: "Enter the path to the file for the \(projectName) release notes.") diff --git a/Sources/nnex/Domain/Services/PublishInfoLoader.swift b/Sources/nnex/Domain/Services/PublishInfoLoader.swift index 91cdb68..91d0673 100644 --- a/Sources/nnex/Domain/Services/PublishInfoLoader.swift +++ b/Sources/nnex/Domain/Services/PublishInfoLoader.swift @@ -39,7 +39,7 @@ extension PublishInfoLoader { /// Loads the publishing information, including the selected tap and formula. /// - Returns: A tuple containing the selected tap and formula. /// - Throws: An error if the loading process fails. - func loadPublishInfo() throws -> (SwiftDataTap, SwiftDataFormula) { + func loadPublishInfo() throws -> (SwiftDataHomebrewTap, SwiftDataHomebrewFormula) { let allTaps = try context.loadTaps() let tap = try getTap(allTaps: allTaps) ?? picker.requiredSingleSelection("\(projectFolder.name) does not yet have a formula. Select a tap for this formula.", items: allTaps) @@ -66,8 +66,8 @@ extension PublishInfoLoader { private extension PublishInfoLoader { /// Retrieves an existing tap matching the project name, if available. /// - Parameter allTaps: An array of available taps. - /// - Returns: A SwiftDataTap instance if a matching tap is found, or nil otherwise. - func getTap(allTaps: [SwiftDataTap]) -> SwiftDataTap? { + /// - Returns: A SwiftDataHomebrewTap instance if a matching tap is found, or nil otherwise. + func getTap(allTaps: [SwiftDataHomebrewTap]) -> SwiftDataHomebrewTap? { return allTaps.first { tap in return tap.formulas.contains(where: { $0.name.lowercased() == projectFolder.name.lowercased() }) } @@ -75,9 +75,9 @@ private extension PublishInfoLoader { /// Creates a new formula for the given project folder. /// - Parameter folder: The project folder for which to create a formula. - /// - Returns: A SwiftDataFormula instance representing the created formula. + /// - Returns: A SwiftDataHomebrewFormula instance representing the created formula. /// - Throws: An error if the creation process fails. - func createNewFormula(for folder: Folder) throws -> SwiftDataFormula { + func createNewFormula(for folder: Folder) throws -> SwiftDataHomebrewFormula { let name = try getExecutableName() let details = try picker.getRequiredInput(prompt: "Enter the description for this formula.") let homepage = try gitHandler.getRemoteURL(path: folder.path) @@ -114,7 +114,7 @@ private extension PublishInfoLoader { /// Retrieves the test command based on user input or configuration. /// - Returns: A `TestCommand` instance if tests are to be run, or `nil` if tests are skipped. /// - Throws: An error if the test command cannot be determined. - func getTestCommand() throws -> TestCommand? { + func getTestCommand() throws -> CurrentSchema.TestCommand? { if skipTests { return nil } diff --git a/Sources/nnex/Picker/DisplayablePickerItemConformance.swift b/Sources/nnex/Picker/DisplayablePickerItemConformance.swift index da9f7fc..e12be55 100644 --- a/Sources/nnex/Picker/DisplayablePickerItemConformance.swift +++ b/Sources/nnex/Picker/DisplayablePickerItemConformance.swift @@ -9,13 +9,13 @@ import Files import NnexKit import SwiftPickerKit -extension SwiftDataTap: DisplayablePickerItem { +extension SwiftDataHomebrewTap: DisplayablePickerItem { public var displayName: String { return name } } -extension SwiftDataFormula: DisplayablePickerItem { +extension SwiftDataHomebrewFormula: DisplayablePickerItem { public var displayName: String { return name } diff --git a/Sources/nnex/Picker/Picker.swift b/Sources/nnex/Picker/Picker.swift index 7a399ae..904bfae 100644 --- a/Sources/nnex/Picker/Picker.swift +++ b/Sources/nnex/Picker/Picker.swift @@ -7,12 +7,14 @@ import Files import NnexKit +import Foundation import SwiftPickerKit protocol NnexPicker { func getPermission(prompt: String) -> Bool func requiredPermission(prompt: String) throws func getRequiredInput(prompt: String) throws -> String + func browseDirectories(prompt: String, startURL: URL, showPromptText: Bool, showSelectedItemText: Bool, selectionType: FileSystemNode.SelectionType) -> FileSystemNode? func treeNavigation(prompt: String, root: TreeNavigationRoot, newScreen: Bool, showPromptText: Bool, showSelectedItemText: Bool) -> Item? func requiredSingleSelection(prompt: String, items: [Item], layout: PickerLayout, newScreen: Bool, showSelectedItemText: Bool) throws -> Item } diff --git a/Tests/NnexKitTests/ProjectBuilderTests.swift b/Tests/NnexKitTests/ProjectBuilderTests.swift index 29461b1..0aee778 100644 --- a/Tests/NnexKitTests/ProjectBuilderTests.swift +++ b/Tests/NnexKitTests/ProjectBuilderTests.swift @@ -252,7 +252,7 @@ extension ProjectBuilderTests { // MARK: - SUT private extension ProjectBuilderTests { - func makeSUT(buildType: BuildType = .universal, runResults: [String] = [], throwShellError: Bool = false, testCommand: TestCommand? = nil, skipClean: Bool = false) -> (sut: ProjectBuilder, shell: MockShell) { + func makeSUT(buildType: BuildType = .universal, runResults: [String] = [], throwShellError: Bool = false, testCommand: CurrentSchema.TestCommand? = nil, skipClean: Bool = false) -> (sut: ProjectBuilder, shell: MockShell) { let shell = MockShell(results: runResults, shouldThrowErrorOnFinal: throwShellError) let config = BuildConfig( projectName: projectName, diff --git a/Tests/NnexKitTests/PublishUtilitiesTests.swift b/Tests/NnexKitTests/PublishUtilitiesTests.swift index 80caee3..b3170cf 100644 --- a/Tests/NnexKitTests/PublishUtilitiesTests.swift +++ b/Tests/NnexKitTests/PublishUtilitiesTests.swift @@ -260,13 +260,13 @@ private struct MockFormula { let homepage: String let license: String let localProjectPath: String - let testCommand: TestCommand? + let testCommand: CurrentSchema.TestCommand? let extraBuildArgs: [String] } // MARK: - Private Helpers private extension PublishUtilitiesTests { - func makeFormula(testCommand: TestCommand? = nil, extraBuildArgs: [String] = []) -> MockFormula { + func makeFormula(testCommand: CurrentSchema.TestCommand? = nil, extraBuildArgs: [String] = []) -> MockFormula { return MockFormula( name: projectName, details: details, diff --git a/Tests/nnexTests/PublishTests/PublishExecutionManagerTests.swift b/Tests/nnexTests/PublishTests/PublishExecutionManagerTests.swift index eb7167b..066fb84 100644 --- a/Tests/nnexTests/PublishTests/PublishExecutionManagerTests.swift +++ b/Tests/nnexTests/PublishTests/PublishExecutionManagerTests.swift @@ -58,8 +58,8 @@ extension PublishExecutionManagerTests { ) let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") - let existingFormula = SwiftDataFormula( + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingFormula = SwiftDataHomebrewFormula( name: executableName, details: "Test formula", homepage: "https://github.com/test/repo", @@ -114,7 +114,7 @@ extension PublishExecutionManagerTests { ) let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") try context.saveNewTap(existingTap) @@ -157,8 +157,8 @@ extension PublishExecutionManagerTests { ) let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") - let existingFormula = SwiftDataFormula( + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingFormula = SwiftDataHomebrewFormula( name: executableName, details: "Test formula", homepage: "https://github.com/test/repo", @@ -211,8 +211,8 @@ extension PublishExecutionManagerTests { ) let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") - let existingFormula = SwiftDataFormula( + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingFormula = SwiftDataHomebrewFormula( name: executableName, details: "Test formula", homepage: "https://github.com/test/repo", @@ -304,8 +304,8 @@ extension PublishExecutionManagerTests { let factory = MockContextFactory(shell: MockShell(shouldThrowErrorOnFinal: true)) let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") - let existingFormula = SwiftDataFormula( + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingFormula = SwiftDataHomebrewFormula( name: executableName, details: "Test formula", homepage: "https://github.com/test/repo", diff --git a/Tests/nnexTests/PublishTests/PublishInfoLoaderTests.swift b/Tests/nnexTests/PublishTests/PublishInfoLoaderTests.swift index c78f2e8..f528cb7 100644 --- a/Tests/nnexTests/PublishTests/PublishInfoLoaderTests.swift +++ b/Tests/nnexTests/PublishTests/PublishInfoLoaderTests.swift @@ -31,7 +31,7 @@ extension PublishInfoLoaderTests { func createsNewFormula() throws { let factory = MockContextFactory() let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") try context.saveNewTap(existingTap) @@ -54,10 +54,10 @@ extension PublishInfoLoaderTests { func updatesFormulaProjectPath() throws { let factory = MockContextFactory() let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") // Create a formula with a different project path - let existingFormula = SwiftDataFormula( + let existingFormula = SwiftDataHomebrewFormula( name: projectName, details: "Test formula", homepage: "https://github.com/test/test", @@ -86,7 +86,7 @@ extension PublishInfoLoaderTests { func preservesMatchingProjectPath() throws { let factory = MockContextFactory() let context = try factory.makeContext() - let existingTap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let existingTap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") let sut = try makeSUT(context: context) @@ -94,7 +94,7 @@ extension PublishInfoLoaderTests { try createPackageSwift() // Create a formula with the same project path - let existingFormula = SwiftDataFormula( + let existingFormula = SwiftDataHomebrewFormula( name: projectName, details: "Test formula", homepage: "https://github.com/test/test", diff --git a/Tests/nnexTests/PublishTests/PublishTests.swift b/Tests/nnexTests/PublishTests/PublishTests.swift index 794675d..92e917a 100644 --- a/Tests/nnexTests/PublishTests/PublishTests.swift +++ b/Tests/nnexTests/PublishTests/PublishTests.swift @@ -204,8 +204,8 @@ extension PublishTests { #expect(shell.executedCommands.contains { $0.contains(testCommand) }) } - @Test("Skips tests when indicated in arg even when formula contains test command", arguments: [TestCommand.defaultCommand, TestCommand.custom("some command"), nil]) - func skipsTests(testCommand: TestCommand?) throws { + @Test("Skips tests when indicated in arg even when formula contains test command", arguments: [CurrentSchema.TestCommand.defaultCommand, CurrentSchema.TestCommand.custom("some command"), nil]) + func skipsTests(testCommand: CurrentSchema.TestCommand?) throws { let gitHandler = MockGitHandler(assetURL: assetURL) let shell = createMockShell(includeTestCommand: false) let factory = MockContextFactory(gitHandler: gitHandler, shell: shell) @@ -340,12 +340,12 @@ private extension PublishTests { return .init(commands: commandResults.map({ .init(command: $0, output: $1) })) } - func createTestTapAndFormula(factory: MockContextFactory, formulaPath: String? = nil, testCommand: TestCommand? = nil, extraBuildArgs: [String] = [], projectName: String? = nil, projectFolder: Folder? = nil) throws { + func createTestTapAndFormula(factory: MockContextFactory, formulaPath: String? = nil, testCommand: CurrentSchema.TestCommand? = nil, extraBuildArgs: [String] = [], projectName: String? = nil, projectFolder: Folder? = nil) throws { let context = try factory.makeContext() - let tap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "") + let tap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "") let effectiveProjectName = projectName ?? self.projectName let effectiveProjectFolder = projectFolder ?? self.projectFolder - let formula = SwiftDataFormula(name: effectiveProjectName, details: "details", homepage: "homepage", license: "MIT", localProjectPath: formulaPath ?? effectiveProjectFolder.path, uploadType: .binary, testCommand: testCommand, extraBuildArgs: extraBuildArgs) + let formula = SwiftDataHomebrewFormula(name: effectiveProjectName, details: "details", homepage: "homepage", license: "MIT", localProjectPath: formulaPath ?? effectiveProjectFolder.path, uploadType: .binary, testCommand: testCommand, extraBuildArgs: extraBuildArgs) try context.saveNewTap(tap, formulas: [formula]) } diff --git a/Tests/nnexTests/RemoveFormulaTests/RemoveFormulaTests.swift b/Tests/nnexTests/RemoveFormulaTests/RemoveFormulaTests.swift index 5751669..f28fc47 100644 --- a/Tests/nnexTests/RemoveFormulaTests/RemoveFormulaTests.swift +++ b/Tests/nnexTests/RemoveFormulaTests/RemoveFormulaTests.swift @@ -123,8 +123,8 @@ private extension RemoveFormulaTests { func createTestTapAndFormula(factory: MockContextFactory, formulaName: String, createFormulaFile: Bool = false) throws { let context = try factory.makeContext() - let tap = SwiftDataTap(name: tapName, localPath: tapFolder.path, remotePath: "https://github.com/user/\(tapName)") - let formula = SwiftDataFormula(name: formulaName, details: "formula details", homepage: "https://github.com/user/\(formulaName)", license: "MIT", localProjectPath: "/path/to/project", uploadType: .binary, testCommand: nil, extraBuildArgs: []) + let tap = SwiftDataHomebrewTap(name: tapName, localPath: tapFolder.path, remotePath: "https://github.com/user/\(tapName)") + let formula = SwiftDataHomebrewFormula(name: formulaName, details: "formula details", homepage: "https://github.com/user/\(formulaName)", license: "MIT", localProjectPath: "/path/to/project", uploadType: .binary, testCommand: nil, extraBuildArgs: []) try context.saveNewTap(tap, formulas: [formula]) diff --git a/Tests/nnexTests/Shared/MockContextFactory.swift b/Tests/nnexTests/Shared/MockContextFactory.swift index 8613b5c..d326fb7 100644 --- a/Tests/nnexTests/Shared/MockContextFactory.swift +++ b/Tests/nnexTests/Shared/MockContextFactory.swift @@ -6,7 +6,6 @@ // import NnexKit -import SwiftData import Foundation import NnShellTesting import SwiftPickerTesting @@ -94,9 +93,7 @@ extension MockContextFactory: ContextFactory { return context } - let defaults = makeDefaults() - let config = ModelConfiguration(isStoredInMemoryOnly: true) - let context = try NnexContext(appGroupId: "not needed", config: config, defaults: defaults) + let context = try NnexContext(userDefaultsTestSuiteName: "testSuiteDefaults_\(UUID().uuidString)") if let tapListFolderPath { context.saveTapListFolderPath(path: tapListFolderPath) @@ -133,14 +130,3 @@ extension MockContextFactory: ContextFactory { return newTrashHandler } } - -// MARK: - Private -private extension MockContextFactory { - func makeDefaults() -> UserDefaults { - let testSuiteName = "testSuiteDefaults_\(UUID().uuidString)" - let userDefaults = UserDefaults(suiteName: testSuiteName)! - userDefaults.removePersistentDomain(forName: testSuiteName) - - return userDefaults - } -} diff --git a/Tests/nnexTests/TapListTests/BrewTapListTests.swift b/Tests/nnexTests/TapListTests/BrewTapListTests.swift index 27ab4d3..19ff1af 100644 --- a/Tests/nnexTests/TapListTests/BrewTapListTests.swift +++ b/Tests/nnexTests/TapListTests/BrewTapListTests.swift @@ -39,7 +39,7 @@ // //// MARK: - Helpers //private extension BrewTapListTests { -// func makeTap() -> SwiftDataTap { +// func makeTap() -> SwiftDataHomebrewTap { // return .init(name: "test-tap", localPath: "/usr/local/test", remotePath: "https://github.com/test") // } //}