diff --git a/.bump-version.toml b/.bump-version.toml
new file mode 100644
index 0000000..8d7d96e
--- /dev/null
+++ b/.bump-version.toml
@@ -0,0 +1,15 @@
+[strategy.semvar]
+requireExistingFile = true
+requireExistingSemVar = true
+
+[strategy.semvar.preRelease]
+prefix = 'rc'
+style = 'custom'
+usesGitTag = false
+
+[strategy.semvar.preRelease.branch]
+includeCommitSha = true
+
+[target.module]
+fileName = 'Version.swift'
+name = 'cli-version'
\ No newline at end of file
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-cli-version-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-cli-version-Package.xcscheme
index 7fc889d..3342ecf 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-cli-version-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-cli-version-Package.xcscheme
@@ -90,6 +90,20 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
Self {
+ .init(style: .branch, branch: branch)
+ }
+
+ /// Represents a custom strategy that uses the given value, not deriving any other
+ /// data.
+ public static func custom(_ prefix: String) -> Self {
+ .init(style: .custom, prefix: prefix)
+ }
+
+ /// Represents a custom strategy that uses a prefix along with the branch and
+ /// commit sha.
+ public static func customBranchPrefix(
+ _ prefix: String,
+ branch: Branch = .init()
+ ) -> Self {
+ .init(style: .custom, branch: branch, prefix: prefix)
+ }
+
+ /// Represents a custom strategy that uses a prefix along with the output from
+ /// calling `git describe --tags`.
+ public static func customGitTagPrefix(_ prefix: String) -> Self {
+ .init(style: StyleId.custom, prefix: prefix, usesGitTag: true)
+ }
+
+ public enum StyleId: String, Codable, Sendable {
+ case branch
+ case custom
+ case gitTag
+ }
+ }
+
+ /// Represents a semvar version strategy.
+ ///
+ /// ## Example: 1.0.0
+ ///
+ struct SemVar: Codable, Equatable, Sendable {
+
+ /// Optional pre-releas suffix strategy.
+ public let preRelease: PreReleaseStrategy?
+
+ /// 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 init(
+ preRelease: PreReleaseStrategy? = nil,
+ requireExistingFile: Bool = true,
+ requireExistingSemVar: Bool = true
+ ) {
+ self.preRelease = preRelease
+ self.requireExistingFile = requireExistingFile
+ self.requireExistingSemVar = requireExistingSemVar
+ }
+
+ }
+
+ /// 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
+ /// locate the version file.
+ struct Target: Codable, Equatable, Sendable {
+
+ /// The path to a version file.
+ public let path: String?
+
+ /// A module to find the version file in.
+ public let module: Module?
+
+ /// Create a target for the given path.
+ ///
+ /// - Parameters:
+ /// - path: The path to the version file.
+ public init(path: String) {
+ self.path = path
+ self.module = nil
+ }
+
+ /// Create a target for the given module.
+ ///
+ /// - Parameters:
+ /// - module: The module for the version file.
+ public init(module: Module) {
+ self.path = nil
+ self.module = module
+ }
+
+ /// Represents a module target for a version file.
+ ///
public struct Module: Codable, Equatable, Sendable {
+
+ /// The module directory name.
public let name: String
+
+ /// The version file name located in the module directory.
public let fileName: String
+ /// Create a new module target.
+ ///
+ /// - Parameters:
+ /// - name: The module directory name.
+ /// - fileName: The file name located in the module directory.
public init(
_ name: String,
fileName: String = "Version.swift"
@@ -72,4 +208,37 @@ public extension Configuration {
}
}
}
+
+ /// Strategy used to generate a version.
+ ///
+ /// Typically a `SemVar` strategy or `Branch`.
+ ///
+ ///
+ struct VersionStrategy: Codable, Equatable, Sendable {
+
+ /// Set if we're using the branch and commit sha to derive the version.
+ let branch: Branch?
+
+ /// Set if we're using semvar to derive the version.
+ let semvar: SemVar?
+
+ /// Create a new version strategy that uses branch and commit sha to derive the version.
+ ///
+ /// - Parameters:
+ /// - branch: The branch strategy options.
+ public init(branch: Branch) {
+ self.branch = branch
+ self.semvar = nil
+ }
+
+ /// Create a new version strategy that uses semvar to derive the version.
+ ///
+ /// - Parameters:
+ /// - semvar: The semvar strategy options.
+ public init(semvar: SemVar = .init()) {
+ self.branch = nil
+ self.semvar = semvar
+ }
+
+ }
}
diff --git a/Sources/ConfigurationClient/ConfigurationClient.swift b/Sources/ConfigurationClient/ConfigurationClient.swift
index ac6be15..c20ac99 100644
--- a/Sources/ConfigurationClient/ConfigurationClient.swift
+++ b/Sources/ConfigurationClient/ConfigurationClient.swift
@@ -4,18 +4,28 @@ import FileClient
import Foundation
public extension DependencyValues {
+
+ /// Perform operations with configuration files.
var configurationClient: ConfigurationClient {
get { self[ConfigurationClient.self] }
set { self[ConfigurationClient.self] = newValue }
}
}
+/// Handles interactions with configuration files.
@DependencyClient
public struct ConfigurationClient: Sendable {
+
+ /// Find a configuration file in the given directory or in current working directory.
public var find: @Sendable (URL?) async throws -> ConfigruationFile?
+
+ /// Load a configuration file.
public var load: @Sendable (ConfigruationFile) async throws -> Configuration
+
+ /// Write a configuration file.
public var write: @Sendable (Configuration, ConfigruationFile) async throws -> Void
+ /// Find a configuration file and load it if found.
public func findAndLoad(_ url: URL? = nil) async throws -> Configuration {
guard let url = try await find(url) else {
throw ConfigurationClientError.configurationNotFound
@@ -30,7 +40,7 @@ extension ConfigurationClient: DependencyKey {
public static var liveValue: ConfigurationClient {
.init(
find: { try await findConfiguration($0) },
- load: { try await $0.load() ?? .init() },
+ load: { try await $0.load() ?? .mock },
write: { try await $1.write($0) }
)
}
diff --git a/Sources/ConfigurationClient/ConfigurationFile.swift b/Sources/ConfigurationClient/ConfigurationFile.swift
index 568260f..09d93b2 100644
--- a/Sources/ConfigurationClient/ConfigurationFile.swift
+++ b/Sources/ConfigurationClient/ConfigurationFile.swift
@@ -2,10 +2,27 @@ import Dependencies
import FileClient
import Foundation
+/// Represents a configuration file type and location.
public enum ConfigruationFile: Equatable, Sendable {
+
+ /// A json configuration file.
case json(URL)
+
+ /// A toml configuration file.
case toml(URL)
+ /// Default configuration file, which is a toml file
+ /// with the name of '.bump-version.toml'
+ public static var `default`: Self {
+ .toml(URL(
+ filePath: "\(ConfigurationClient.Constants.defaultFileNameWithoutExtension).toml"
+ ))
+ }
+
+ /// Create a new file location from the given url.
+ ///
+ /// - Parameters:
+ /// - url: The url for the file.
public init?(url: URL) {
if url.pathExtension == "toml" {
self = .toml(url)
@@ -16,6 +33,7 @@ public enum ConfigruationFile: Equatable, Sendable {
}
}
+ /// The url of the file.
var url: URL {
switch self {
case let .json(url): return url
diff --git a/Tests/ConfigurationClientTests/ConfigurationClientTests.swift b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift
new file mode 100644
index 0000000..aeab0e0
--- /dev/null
+++ b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift
@@ -0,0 +1,58 @@
+import ConfigurationClient
+import Dependencies
+import Foundation
+import Testing
+
+@Suite("ConfigurationClientTests")
+struct ConfigurationClientTests {
+
+ @Test
+ func codable() async throws {
+ try await run {
+ @Dependency(\.configurationClient) var configurationClient
+ @Dependency(\.coders) var coders
+
+ let configuration = Configuration.customPreRelease
+ let encoded = try coders.jsonEncoder().encode(configuration)
+ let decoded = try coders.jsonDecoder().decode(Configuration.self, from: encoded)
+
+ #expect(decoded == configuration)
+
+ let tomlEncoded = try coders.tomlEncoder().encode(configuration)
+ let tomlDecoded = try coders.tomlDecoder().decode(
+ Configuration.self,
+ from: tomlEncoded
+ )
+ #expect(tomlDecoded == configuration)
+ }
+ }
+
+ @Test(arguments: ["foo", ".foo"])
+ func configurationFile(fileName: String) {
+ for ext in ["toml", "json", "bar"] {
+ let file = ConfigruationFile(url: URL(filePath: "\(fileName).\(ext)"))
+ switch ext {
+ case "toml":
+ #expect(file == .toml(URL(filePath: "\(fileName).toml")))
+ case "json":
+ #expect(file == .json(URL(filePath: "\(fileName).json")))
+ default:
+ #expect(file == nil)
+ }
+ }
+ }
+
+ func run(
+ setupDependencies: @escaping (inout DependencyValues) -> Void = { _ in },
+ operation: () async throws -> Void
+ ) async throws {
+ try await withDependencies {
+ $0.coders = .liveValue
+ $0.fileClient = .liveValue
+ $0.configurationClient = .liveValue
+ setupDependencies(&$0)
+ } operation: {
+ try await operation()
+ }
+ }
+}