diff --git a/Package.resolved b/Package.resolved index be780ce..ef55be9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3640b52c8069b868611efbfbd9b7545872526454802225747f7cd878062df1db", + "originHash" : "9fe004cf869b34d1fe07e8b58a90b044281e8e94805df6723d4604ba5a6400d9", "pins" : [ { "identity" : "combine-schedulers", diff --git a/Sources/CliClient/Internal/CliClient+run.swift b/Sources/CliClient/Internal/CliClient+run.swift index c3838a8..2a93b4c 100644 --- a/Sources/CliClient/Internal/CliClient+run.swift +++ b/Sources/CliClient/Internal/CliClient+run.swift @@ -96,10 +96,14 @@ public extension CliClient.SharedOptions { } } +// TODO: Add optional property for currentVersion (loaded version from file) +// and rename version to nextVersion. + @_spi(Internal) public struct CurrentVersionContainer: Sendable { let targetUrl: URL + let currentVersion: CurrentVersion? let version: Version var usesOptionalType: Bool { @@ -109,8 +113,15 @@ public struct CurrentVersionContainer: Sendable { } } + public enum CurrentVersion: Sendable { + case branch(String, usesOptionalType: Bool) + case semvar(SemVar, usesOptionalType: Bool) + } + public enum Version: Sendable { + // TODO: Call this branch for consistency. case string(String) + // TODO: Remove has changes when currentVersion/nextVersion is implemented. case semvar(SemVar, usesOptionalType: Bool = true, hasChanges: Bool) func string(allowPreReleaseTag: Bool) throws -> String { diff --git a/Sources/CliClient/Internal/ConfigurationExtensions.swift b/Sources/CliClient/Internal/ConfigurationExtensions.swift index 029102e..85263b2 100644 --- a/Sources/CliClient/Internal/ConfigurationExtensions.swift +++ b/Sources/CliClient/Internal/ConfigurationExtensions.swift @@ -23,6 +23,35 @@ extension Configuration { } } +private extension Configuration.SemVar.Strategy { + + func getSemvar(gitDirectory: String? = nil) async throws -> SemVar { + @Dependency(\.asyncShellClient) var asyncShellClient + @Dependency(\.gitClient) var gitClient + @Dependency(\.logger) var logger + + let semvar: SemVar? + + switch self { + case let .command(arguments: arguments): + logger.trace("Using custom command strategy with: \(arguments)") + semvar = try await SemVar(string: asyncShellClient.background(.init(arguments))) + case let .gitTag(exactMatch: exactMatch): + logger.trace("Using gitTag strategy.") + semvar = try await gitClient.version(.init( + gitDirectory: gitDirectory, + style: .tag(exactMatch: exactMatch ?? false) + )).semVar + } + + guard let semvar else { + throw CliClientError.semVarNotFound + } + + return semvar + } +} + @_spi(Internal) public extension Configuration.SemVar { @@ -92,6 +121,9 @@ public extension Configuration.SemVar { private extension Configuration.VersionStrategy { + // TODO: This should just load the `nextVersion`, and should probably live on CurrentVersionContainer. + + // FIX: Fix what's passed to current verions here. func currentVersion(targetUrl: URL, gitDirectory: String?) async throws -> CurrentVersionContainer { @Dependency(\.gitClient) var gitClient @@ -103,11 +135,13 @@ private extension Configuration.VersionStrategy { } return try await .init( targetUrl: targetUrl, + currentVersion: nil, version: semvar.currentVersion(file: targetUrl, gitDirectory: gitDirectory) ) } return try await .init( targetUrl: targetUrl, + currentVersion: nil, version: .string( gitClient.version(includeCommitSha: branch.includeCommitSha, gitDirectory: gitDirectory) ) diff --git a/Sources/CliClient/Internal/FileClient+semVar.swift b/Sources/CliClient/Internal/FileClient+semVar.swift index a8d51c2..e1d250a 100644 --- a/Sources/CliClient/Internal/FileClient+semVar.swift +++ b/Sources/CliClient/Internal/FileClient+semVar.swift @@ -5,6 +5,46 @@ import GitClient @_spi(Internal) public extension FileClient { + + func loadCurrentVersion( + url: URL, + gitDirectory: String?, + expectsBranch: Bool + ) async throws -> CurrentVersionContainer.CurrentVersion? { + @Dependency(\.logger) var logger + + switch expectsBranch { + case true: + let (string, usesOptionalType) = try await branch(file: url, gitDirectory: gitDirectory) + logger.debug("Loaded branch: \(string)") + return .branch(string, usesOptionalType: usesOptionalType) + case false: + let (semvar, usesOptionalType) = try await semvar(file: url, gitDirectory: gitDirectory) + guard let semvar else { return nil } + logger.debug("Semvar: \(semvar)") + return .semvar(semvar, usesOptionalType: usesOptionalType) + } + } + + // TODO: Make private. + func branch( + file: URL, + gitDirectory: String? + ) async throws -> (string: String, usesOptionalType: Bool) { + let (string, usesOptionalType) = try await getVersionString(fileUrl: file, gitDirectory: gitDirectory) + return (string, usesOptionalType) + } + + // TODO: Make private. + func semvar( + file: URL, + gitDirectory: String? + ) async throws -> (semVar: SemVar?, usesOptionalType: Bool) { + let (string, usesOptionalType) = try await getVersionString(fileUrl: file, gitDirectory: gitDirectory) + let semvar = SemVar(string: string) + return (semvar, usesOptionalType) + } + private func getVersionString( fileUrl: URL, gitDirectory: String? @@ -38,15 +78,4 @@ public extension FileClient { return (String(versionString), isOptional) } - func semvar( - file: URL, - gitDirectory: String? - ) async throws -> (semVar: SemVar?, usesOptionalType: Bool) { - @Dependency(\.logger) var logger - let (string, usesOptionalType) = try await getVersionString(fileUrl: file, gitDirectory: gitDirectory) - let semvar = SemVar(string: string) - logger.debug("Semvar: \(String(describing: semvar))") - return (semvar, usesOptionalType) - } - } diff --git a/Sources/ConfigurationClient/Configuration.swift b/Sources/ConfigurationClient/Configuration.swift index 4295fcc..02e699e 100644 --- a/Sources/ConfigurationClient/Configuration.swift +++ b/Sources/ConfigurationClient/Configuration.swift @@ -44,95 +44,6 @@ public struct Configuration: Codable, Equatable, Sendable { public extension Configuration { - /// Represents a branch version or pre-release strategy. - /// - /// This derives the version or pre-release suffix from the branch name and - /// optionally the short version of the commit sha. - struct Branch: Codable, Equatable, Sendable { - - /// Include the commit sha in the output for this strategy. - public let includeCommitSha: Bool - - /// Create a new branch strategy. - /// - /// - Parameters: - /// - includeCommitSha: Whether to include the commit sha. - public init(includeCommitSha: Bool = true) { - self.includeCommitSha = includeCommitSha - } - } - - /// Represents version strategy for pre-release. - /// - /// This appends a suffix to the version that get's generated from the version strategy. - /// For example: `1.0.0-rc-1` - /// - struct PreRelease: Codable, Equatable, Sendable { - - public let prefix: String? - public let strategy: Strategy? - - public init( - prefix: String? = nil, - strategy: Strategy? = nil - ) { - self.prefix = prefix - self.strategy = strategy - } - - public enum Strategy: Codable, Equatable, Sendable { - case branch(includeCommitSha: Bool = true) - case command(arguments: [String], allowPrefix: Bool? = nil) - case gitTag - - public var branch: Branch? { - guard case let .branch(includeCommitSha) = self - else { return nil } - return .init(includeCommitSha: includeCommitSha) - } - } - } - - /// Represents a semvar version strategy. - /// - /// ## Example: 1.0.0 - /// - struct SemVar: Codable, Equatable, Sendable { - - public let allowPreRelease: Bool? - - /// Optional pre-releas suffix strategy. - public let preRelease: PreRelease? - - /// Fail if an existing version file does not exist in the target. - public let requireExistingFile: Bool? - - /// Fail if an existing semvar is not parsed from the file or version generation strategy. - public let requireExistingSemVar: Bool? - - public let strategy: Strategy? - - public init( - allowPreRelease: Bool? = true, - preRelease: PreRelease? = nil, - requireExistingFile: Bool? = false, - requireExistingSemVar: Bool? = false, - strategy: Strategy? = nil - ) { - self.allowPreRelease = allowPreRelease - self.preRelease = preRelease - self.requireExistingFile = requireExistingFile - self.requireExistingSemVar = requireExistingSemVar - self.strategy = strategy - } - - public enum Strategy: Codable, Equatable, Sendable { - case command(arguments: [String]) - case gitTag(exactMatch: Bool? = false) - } - - } - /// Represents the target where we will bump the version in. /// /// This can either be a path to a version file or a module used to @@ -283,4 +194,93 @@ public extension Configuration { } } + /// Represents a branch version or pre-release strategy. + /// + /// This derives the version or pre-release suffix from the branch name and + /// optionally the short version of the commit sha. + struct Branch: Codable, Equatable, Sendable { + + /// Include the commit sha in the output for this strategy. + public let includeCommitSha: Bool + + /// Create a new branch strategy. + /// + /// - Parameters: + /// - includeCommitSha: Whether to include the commit sha. + public init(includeCommitSha: Bool = true) { + self.includeCommitSha = includeCommitSha + } + } + + /// Represents version strategy for pre-release. + /// + /// This appends a suffix to the version that get's generated from the version strategy. + /// For example: `1.0.0-rc-1` + /// + struct PreRelease: Codable, Equatable, Sendable { + + public let prefix: String? + public let strategy: Strategy? + + public init( + prefix: String? = nil, + strategy: Strategy? = nil + ) { + self.prefix = prefix + self.strategy = strategy + } + + public enum Strategy: Codable, Equatable, Sendable { + case branch(includeCommitSha: Bool = true) + case command(arguments: [String], allowPrefix: Bool? = nil) + case gitTag + + public var branch: Branch? { + guard case let .branch(includeCommitSha) = self + else { return nil } + return .init(includeCommitSha: includeCommitSha) + } + } + } + + /// Represents a semvar version strategy. + /// + /// ## Example: 1.0.0 + /// + struct SemVar: Codable, Equatable, Sendable { + + public let allowPreRelease: Bool? + + /// Optional pre-releas suffix strategy. + public let preRelease: PreRelease? + + /// Fail if an existing version file does not exist in the target. + public let requireExistingFile: Bool? + + /// Fail if an existing semvar is not parsed from the file or version generation strategy. + public let requireExistingSemVar: Bool? + + public let strategy: Strategy? + + public init( + allowPreRelease: Bool? = true, + preRelease: PreRelease? = nil, + requireExistingFile: Bool? = false, + requireExistingSemVar: Bool? = false, + strategy: Strategy? = nil + ) { + self.allowPreRelease = allowPreRelease + self.preRelease = preRelease + self.requireExistingFile = requireExistingFile + self.requireExistingSemVar = requireExistingSemVar + self.strategy = strategy + } + + public enum Strategy: Codable, Equatable, Sendable { + case command(arguments: [String]) + case gitTag(exactMatch: Bool? = false) + } + + } + } diff --git a/Sources/ConfigurationClient/ConfigurationClient.swift b/Sources/ConfigurationClient/ConfigurationClient.swift index 247765f..9548c1f 100644 --- a/Sources/ConfigurationClient/ConfigurationClient.swift +++ b/Sources/ConfigurationClient/ConfigurationClient.swift @@ -3,6 +3,8 @@ import DependenciesMacros import FileClient import Foundation +// TODO: Add a method to get a semvar / handle a version strategy's ?? + public extension DependencyValues { /// Perform operations with configuration files. @@ -16,6 +18,12 @@ public extension DependencyValues { @DependencyClient public struct ConfigurationClient: Sendable { + fileprivate enum Constants { + static let defaultFileNameWithoutExtension = ".bump-version" + static let defaultExtension = "json" + static var defaultFileName: String { "\(defaultFileNameWithoutExtension).\(defaultExtension)" } + } + /// The default file name for a configuration file. public var defaultFileName: @Sendable () -> String = { "test.json" } @@ -89,15 +97,13 @@ extension ConfigurationClient: DependencyKey { private func findConfiguration(_ url: URL?) async throws -> URL? { @Dependency(\.fileClient) var fileClient - let defaultFileName = ConfigurationClient.Constants.defaultFileNameWithoutExtension - var url: URL! = url if url == nil { url = try await URL(filePath: fileClient.currentDirectory()) } if try await fileClient.isDirectory(url.cleanFilePath) { - url = url.appending(path: "\(defaultFileName).json") + url = url.appending(path: ConfigurationClient.Constants.defaultFileName) } if fileClient.fileExists(url) { diff --git a/Sources/ConfigurationClient/Constants.swift b/Sources/ConfigurationClient/Constants.swift index 312463d..2581dae 100644 --- a/Sources/ConfigurationClient/Constants.swift +++ b/Sources/ConfigurationClient/Constants.swift @@ -1,7 +1,4 @@ import Foundation extension ConfigurationClient { - enum Constants { - static let defaultFileNameWithoutExtension = ".bump-version" - } }