Compare commits

...

2 Commits

Author SHA1 Message Date
3cfd882f38 feat: Some variable renaming for consistency.
All checks were successful
CI / Ubuntu (push) Successful in 2m58s
2024-12-24 23:16:13 -05:00
a885e3dfa3 feat: Updates logging configuration. 2024-12-24 21:32:14 -05:00
17 changed files with 367 additions and 125 deletions

View File

@@ -40,7 +40,8 @@ let package = Package(
"ConfigurationClient", "ConfigurationClient",
"FileClient", "FileClient",
"GitClient", "GitClient",
.product(name: "Logging", package: "swift-log") .product(name: "Logging", package: "swift-log"),
.product(name: "CustomDump", package: "swift-custom-dump")
] ]
), ),
.testTarget( .testTarget(

View File

@@ -34,12 +34,30 @@ public struct CliClient: Sendable {
case major, minor, patch, preRelease case major, minor, patch, preRelease
} }
// TODO: Need a quiet option, as default log level is warning, need a way to set it to ignore logs.
public struct LoggingOptions: Equatable, Sendable {
let command: String
let executableName: String
let verbose: Int
public init(
executableName: String = "bump-version",
command: String,
verbose: Int
) {
self.executableName = executableName
self.command = command
self.verbose = verbose
}
}
public struct SharedOptions: Equatable, Sendable { public struct SharedOptions: Equatable, Sendable {
let allowPreReleaseTag: Bool let allowPreReleaseTag: Bool
let dryRun: Bool let dryRun: Bool
let gitDirectory: String? let gitDirectory: String?
let logLevel: Logger.Level let loggingOptions: LoggingOptions
let target: Configuration.Target? let target: Configuration.Target?
let branch: Configuration.Branch? let branch: Configuration.Branch?
let semvar: Configuration.SemVar? let semvar: Configuration.SemVar?
@@ -49,7 +67,7 @@ public struct CliClient: Sendable {
allowPreReleaseTag: Bool = true, allowPreReleaseTag: Bool = true,
dryRun: Bool = false, dryRun: Bool = false,
gitDirectory: String? = nil, gitDirectory: String? = nil,
logLevel: Logger.Level = .debug, loggingOptions: LoggingOptions,
target: Configuration.Target? = nil, target: Configuration.Target? = nil,
branch: Configuration.Branch? = nil, branch: Configuration.Branch? = nil,
semvar: Configuration.SemVar? = nil, semvar: Configuration.SemVar? = nil,
@@ -58,34 +76,12 @@ public struct CliClient: Sendable {
self.allowPreReleaseTag = allowPreReleaseTag self.allowPreReleaseTag = allowPreReleaseTag
self.dryRun = dryRun self.dryRun = dryRun
self.gitDirectory = gitDirectory self.gitDirectory = gitDirectory
self.logLevel = logLevel self.loggingOptions = loggingOptions
self.target = target self.target = target
self.branch = branch self.branch = branch
self.semvar = semvar self.semvar = semvar
self.configurationFile = configurationFile self.configurationFile = configurationFile
} }
public init(
allowPreReleaseTag: Bool = true,
dryRun: Bool = false,
gitDirectory: String? = nil,
verbose: Int,
target: Configuration.Target? = nil,
branch: Configuration.Branch? = nil,
semvar: Configuration.SemVar? = nil,
configurationFile: String? = nil
) {
self.init(
allowPreReleaseTag: allowPreReleaseTag,
dryRun: dryRun,
gitDirectory: gitDirectory,
logLevel: .init(verbose: verbose),
target: target,
branch: branch,
semvar: semvar,
configurationFile: configurationFile
)
}
} }
} }

View File

@@ -4,4 +4,5 @@ enum CliClientError: Error {
case fileDoesNotExist(path: String) case fileDoesNotExist(path: String)
case failedToParseVersionFile case failedToParseVersionFile
case semVarNotFound case semVarNotFound
case preReleaseParsingError(String)
} }

View File

@@ -1,4 +1,5 @@
import ConfigurationClient import ConfigurationClient
import CustomDump
import Dependencies import Dependencies
import FileClient import FileClient
import Foundation import Foundation
@@ -7,6 +8,40 @@ import GitClient
@_spi(Internal) @_spi(Internal)
public extension CliClient.SharedOptions { public extension CliClient.SharedOptions {
/// All cli-client calls should run through this, it set's up logging,
/// loads configuration, and generates the current version based on the
/// configuration.
@discardableResult
func run(
_ operation: (CurrentVersionContainer) async throws -> Void
) async rethrows -> String {
try await loggingOptions.withLogger {
// Load the default configuration, if it exists.
try await withMergedConfiguration { configuration in
@Dependency(\.logger) var logger
var configurationString = ""
customDump(configuration, to: &configurationString)
logger.trace("\nConfiguration: \(configurationString)")
// This will fail if the target url is not set properly.
let targetUrl = try configuration.targetUrl(gitDirectory: gitDirectory)
logger.debug("Target: \(targetUrl.cleanFilePath)")
// Perform the operation, which generates the new version and writes it.
try await operation(
configuration.currentVersion(
targetUrl: targetUrl,
gitDirectory: gitDirectory
)
)
// Return the file path we wrote the version to.
return targetUrl.cleanFilePath
}
}
}
// Merges any configuration set via the passed in options. // Merges any configuration set via the passed in options.
@discardableResult @discardableResult
func withMergedConfiguration<T>( func withMergedConfiguration<T>(
@@ -26,37 +61,6 @@ public extension CliClient.SharedOptions {
} }
} }
@discardableResult
func run(
_ operation: (CurrentVersionContainer) async throws -> Void
) async rethrows -> String {
try await withDependencies {
$0.logger.logLevel = logLevel
} operation: {
// Load the default configuration, if it exists.
try await withMergedConfiguration { configuration in
@Dependency(\.logger) var logger
logger.debug("Configuration: \(configuration)")
// This will fail if the target url is not set properly.
let targetUrl = try configuration.targetUrl(gitDirectory: gitDirectory)
logger.debug("Target: \(targetUrl.cleanFilePath)")
// Perform the operation, which generates the new version and writes it.
try await operation(
configuration.currentVersion(
targetUrl: targetUrl,
gitDirectory: gitDirectory
)
)
// Return the file path we wrote the version to.
return targetUrl.cleanFilePath
}
}
}
func write(_ string: String, to url: URL) async throws { func write(_ string: String, to url: URL) async throws {
@Dependency(\.fileClient) var fileClient @Dependency(\.fileClient) var fileClient
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
@@ -64,7 +68,7 @@ public extension CliClient.SharedOptions {
try await fileClient.write(string: string, to: url) try await fileClient.write(string: string, to: url)
} else { } else {
logger.debug("Skipping, due to dry-run being passed.") logger.debug("Skipping, due to dry-run being passed.")
logger.debug("\(string)") logger.debug("\n\(string)\n")
} }
} }
@@ -90,20 +94,20 @@ public struct CurrentVersionContainer: Sendable {
var usesOptionalType: Bool { var usesOptionalType: Bool {
switch version { switch version {
case .string: return false case .string: return false
case let .semVar(_, usesOptionalType): return usesOptionalType case let .semvar(_, usesOptionalType): return usesOptionalType
} }
} }
public enum Version: Sendable { public enum Version: Sendable {
case string(String) case string(String)
case semVar(SemVar, usesOptionalType: Bool = true) case semvar(SemVar, usesOptionalType: Bool = true)
func string(allowPreReleaseTag: Bool) throws -> String { func string(allowPreReleaseTag: Bool) throws -> String {
switch self { switch self {
case let .string(string): case let .string(string):
return string return string
case let .semVar(semVar, usesOptionalType: _): case let .semvar(semvar, usesOptionalType: _):
return semVar.versionString(withPreReleaseTag: allowPreReleaseTag) return semvar.versionString(withPreReleaseTag: allowPreReleaseTag)
} }
} }
} }
@@ -127,14 +131,18 @@ extension CliClient.SharedOptions {
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
switch container.version { switch container.version {
case .string: // When we did not parse a semVar, just write whatever we parsed for the current version. case .string: // When we did not parse a semvar, just write whatever we parsed for the current version.
logger.debug("Failed to parse semvar, but got current version string.") logger.debug("Failed to parse semvar, but got current version string.")
try await write(container) try await write(container)
case let .semVar(semVar, usesOptionalType: usesOptionalType): case let .semvar(semvar, usesOptionalType: usesOptionalType):
logger.debug("Semvar prior to bumping: \(semVar)") logger.debug("Semvar prior to bumping: \(semvar)")
let bumped = semVar.bump(type, preRelease: nil) // preRelease is already set on semVar. let bumped = semvar.bump(type)
let version = bumped.versionString(withPreReleaseTag: allowPreReleaseTag) let version = bumped.versionString(withPreReleaseTag: allowPreReleaseTag)
guard bumped != semvar else {
logger.debug("No change, skipping.")
return
}
logger.debug("Bumped version: \(version)") logger.debug("Bumped version: \(version)")
let template = usesOptionalType ? Template.optional(version) : Template.build(version) let template = usesOptionalType ? Template.optional(version) : Template.build(version)
try await write(template, to: container.targetUrl) try await write(template, to: container.targetUrl)

View File

@@ -20,6 +20,15 @@ extension Configuration {
} }
} }
extension Configuration.PreRelease {
func merging(_ other: Self?) -> Self {
.init(
prefix: other?.prefix ?? prefix,
strategy: other?.strategy ?? strategy
)
}
}
extension Configuration.Branch { extension Configuration.Branch {
func merging(_ other: Self?) -> Self { func merging(_ other: Self?) -> Self {
return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha) return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha)
@@ -29,7 +38,7 @@ extension Configuration.Branch {
extension Configuration.SemVar { extension Configuration.SemVar {
func merging(_ other: Self?) -> Self { func merging(_ other: Self?) -> Self {
.init( .init(
preRelease: other?.preRelease ?? preRelease, preRelease: preRelease?.merging(other?.preRelease),
requireExistingFile: other?.requireExistingFile ?? requireExistingFile, requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar
) )

View File

@@ -2,6 +2,7 @@ import ConfigurationClient
import Dependencies import Dependencies
import Foundation import Foundation
import GitClient import GitClient
import ShellClient
extension Configuration { extension Configuration {
func targetUrl(gitDirectory: String?) throws -> URL { func targetUrl(gitDirectory: String?) throws -> URL {
@@ -58,26 +59,46 @@ extension Configuration.Target {
} }
extension GitClient { extension GitClient {
func version(branch: Configuration.Branch, gitDirectory: String?) async throws -> String { func version(includeCommitSha: Bool, gitDirectory: String?) async throws -> String {
@Dependency(\.gitClient) var gitClient @Dependency(\.gitClient) var gitClient
return try await gitClient.version(.init( return try await gitClient.version(.init(
gitDirectory: gitDirectory, gitDirectory: gitDirectory,
style: .branch(commitSha: branch.includeCommitSha) style: .branch(commitSha: includeCommitSha)
)).description )).description
} }
} }
extension Configuration.PreRelease { extension Configuration.PreRelease {
func preReleaseString(gitDirectory: String?) async throws -> String { // FIX: This needs to handle the pre-release type appropriatly.
func preReleaseString(gitDirectory: String?) async throws -> PreReleaseString? {
guard let strategy else { return nil }
@Dependency(\.asyncShellClient) var asyncShellClient
@Dependency(\.gitClient) var gitClient @Dependency(\.gitClient) var gitClient
@Dependency(\.logger) var logger
let preReleaseString: String var preReleaseString: String
var suffix = true
var allowsPrefix = true
if let branch = strategy?.branch { switch strategy {
preReleaseString = try await gitClient.version(branch: branch, gitDirectory: gitDirectory) case let .branch(includeCommitSha: includeCommitSha):
} else { logger.trace("Branch pre-release strategy, includeCommitSha: \(includeCommitSha).")
preReleaseString = try await gitClient.version(
includeCommitSha: includeCommitSha,
gitDirectory: gitDirectory
)
case let .command(arguments: arguments):
logger.trace("Command pre-release strategy, arguments: \(arguments).")
// TODO: What to do with allows prefix? Need a configuration setting for commands.
preReleaseString = try await asyncShellClient.background(.init(arguments))
case .gitTag:
logger.trace("Git tag pre-release strategy.")
logger.trace("This will ignore any set prefix.")
suffix = false
allowsPrefix = false
preReleaseString = try await gitClient.version(.init( preReleaseString = try await gitClient.version(.init(
gitDirectory: gitDirectory, gitDirectory: gitDirectory,
style: .tag(exactMatch: false) style: .tag(exactMatch: false)
@@ -85,27 +106,53 @@ extension Configuration.PreRelease {
} }
if let prefix { if let prefix {
return "\(prefix)-\(preReleaseString)" if allowsPrefix {
preReleaseString = "\(prefix)-\(preReleaseString)"
} else {
logger.warning("Found prefix, but pre-release strategy may not work properly, ignoring prefix.")
} }
return preReleaseString }
guard suffix else { return .semvar(preReleaseString) }
return .suffix(preReleaseString)
// return preReleaseString
}
enum PreReleaseString: Sendable {
case suffix(String)
case semvar(String)
} }
} }
@_spi(Internal) @_spi(Internal)
public extension Configuration.SemVar { public extension Configuration.SemVar {
private func applyingPreRelease(_ semVar: SemVar, _ gitDirectory: String?) async throws -> SemVar { private func applyingPreRelease(_ semvar: SemVar, _ gitDirectory: String?) async throws -> SemVar {
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
logger.trace("Start apply pre-release to: \(semVar)") logger.trace("Start apply pre-release to: \(semvar)")
guard let preReleaseStrategy = self.preRelease else {
guard let preReleaseStrategy = self.preRelease,
let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
else {
logger.trace("No pre-release strategy, returning original semvar.") logger.trace("No pre-release strategy, returning original semvar.")
return semVar return semvar
} }
let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory) // let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
logger.trace("Pre-release string: \(preRelease)") logger.trace("Pre-release string: \(preRelease)")
return semVar.applyingPreRelease(preRelease) switch preRelease {
case let .suffix(string):
return semvar.applyingPreRelease(string)
case let .semvar(string):
guard let semvar = SemVar(string: string) else {
throw CliClientError.preReleaseParsingError(string)
}
return semvar
}
// return semVar.applyingPreRelease(preRelease)
} }
func currentVersion(file: URL, gitDirectory: String? = nil) async throws -> CurrentVersionContainer.Version { func currentVersion(file: URL, gitDirectory: String? = nil) async throws -> CurrentVersionContainer.Version {
@@ -113,7 +160,7 @@ public extension Configuration.SemVar {
@Dependency(\.gitClient) var gitClient @Dependency(\.gitClient) var gitClient
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
let fileOutput = try? await fileClient.semVar(file: file, gitDirectory: gitDirectory) let fileOutput = try? await fileClient.semvar(file: file, gitDirectory: gitDirectory)
var semVar = fileOutput?.semVar var semVar = fileOutput?.semVar
logger.trace("file output semvar: \(String(describing: semVar))") logger.trace("file output semvar: \(String(describing: semVar))")
@@ -122,7 +169,7 @@ public extension Configuration.SemVar {
// We parsed a semvar from the existing file, use it. // We parsed a semvar from the existing file, use it.
if semVar != nil { if semVar != nil {
return try await .semVar( return try await .semvar(
applyingPreRelease(semVar!, gitDirectory), applyingPreRelease(semVar!, gitDirectory),
usesOptionalType: usesOptionalType ?? false usesOptionalType: usesOptionalType ?? false
) )
@@ -142,7 +189,7 @@ public extension Configuration.SemVar {
)).semVar )).semVar
if semVar != nil { if semVar != nil {
return try await .semVar( return try await .semvar(
applyingPreRelease(semVar!, gitDirectory), applyingPreRelease(semVar!, gitDirectory),
usesOptionalType: usesOptionalType ?? false usesOptionalType: usesOptionalType ?? false
) )
@@ -155,7 +202,7 @@ public extension Configuration.SemVar {
// Semvar doesn't exist, so create a new one. // Semvar doesn't exist, so create a new one.
logger.trace("Generating new semvar.") logger.trace("Generating new semvar.")
return try await .semVar( return try await .semvar(
applyingPreRelease(.init(), gitDirectory), applyingPreRelease(.init(), gitDirectory),
usesOptionalType: usesOptionalType ?? false usesOptionalType: usesOptionalType ?? false
) )
@@ -181,7 +228,7 @@ extension Configuration.VersionStrategy {
return try await .init( return try await .init(
targetUrl: targetUrl, targetUrl: targetUrl,
version: .string( version: .string(
gitClient.version(branch: branch, gitDirectory: gitDirectory) gitClient.version(includeCommitSha: branch.includeCommitSha, gitDirectory: gitDirectory)
) )
) )
} }

View File

@@ -28,17 +28,17 @@ public extension FileClient {
logger.debug("Version line: \(versionLine)") logger.debug("Version line: \(versionLine)")
let isOptional = versionLine.contains("String?") let isOptional = versionLine.contains("String?")
logger.debug("Uses optional: \(isOptional)") logger.trace("Uses optional: \(isOptional)")
let versionString = versionLine.split(separator: "let VERSION: \(isOptional ? "String?" : "String") = ").last let versionString = versionLine.split(separator: "let VERSION: \(isOptional ? "String?" : "String") = ").last
guard let versionString else { guard let versionString else {
throw CliClientError.failedToParseVersionFile throw CliClientError.failedToParseVersionFile
} }
logger.debug("Parsed version string: \(versionString)") logger.trace("Parsed version string: \(versionString)")
return (String(versionString), isOptional) return (String(versionString), isOptional)
} }
func semVar( func semvar(
file: URL, file: URL,
gitDirectory: String? gitDirectory: String?
) async throws -> (semVar: SemVar?, usesOptionalType: Bool) { ) async throws -> (semVar: SemVar?, usesOptionalType: Bool) {

View File

@@ -1,15 +0,0 @@
import Logging
// TODO: Move.
@_spi(Internal)
public extension Logger.Level {
init(verbose: Int) {
switch verbose {
case 1: self = .warning
case 2: self = .debug
case 3...: self = .trace
default: self = .info
}
}
}

View File

@@ -0,0 +1,172 @@
import Dependencies
import Foundation
import Logging
import LoggingFormatAndPipe
import Rainbow
import ShellClient
extension String {
var orange: Self {
bit24(255, 165, 0)
}
var magena: Self {
// bit24(186, 85, 211)
bit24(238, 130, 238)
}
}
@_spi(Internal)
public extension Logger.Level {
init(verbose: Int) {
switch verbose {
case 1: self = .debug
case 2...: self = .trace
default: self = .warning
}
}
var coloredString: String {
switch self {
case .info:
return "\(self)".cyan
case .warning:
return "\(self)".orange.bold
case .debug:
return "\(self)".green
case .trace:
return "\(self)".yellow
case .error:
return "\(self)".red.bold
default:
return "\(self)"
}
}
}
struct LevelFormatter: LoggingFormatAndPipe.Formatter {
let basic: BasicFormatter
var timestampFormatter: DateFormatter { basic.timestampFormatter }
// swiftlint:disable function_parameter_count
func processLog(
level: Logger.Level,
message: Logger.Message,
prettyMetadata: String?,
file: String,
function: String,
line: UInt
) -> String {
let now = Date()
return basic.format.map { component -> String in
return processComponent(
component,
now: now,
level: level,
message: message,
prettyMetadata: prettyMetadata,
file: file,
function: function,
line: line
)
}
.filter { $0.count > 0 }
.joined(separator: basic.separator ?? "")
}
public func processComponent(
_ component: LogComponent,
now: Date,
level: Logger.Level,
message: Logger.Message,
prettyMetadata: String?,
file: String,
function: String,
line: UInt
) -> String {
switch component {
case .level:
let maxLen = "\(Logger.Level.warning)".count
let paddingCount = (maxLen - "\(level)".count) / 2
var padding = ""
for _ in 0 ... paddingCount {
padding += " "
}
return "\(padding)\(level.coloredString)\(padding)"
case let .group(components):
return components.map { component -> String in
self.processComponent(
component,
now: now,
level: level,
message: message,
prettyMetadata: prettyMetadata,
file: file,
function: function,
line: line
)
}.joined()
case .message:
return basic.processComponent(
component,
now: now,
level: level,
message: message,
prettyMetadata: prettyMetadata,
file: file,
function: function,
line: line
).italic
default:
return basic.processComponent(
component,
now: now,
level: level,
message: message,
prettyMetadata: prettyMetadata,
file: file,
function: function,
line: line
)
}
}
// swiftlint:enable function_parameter_count
}
extension CliClient.LoggingOptions {
func makeLogger() -> Logger {
let formatters: [LogComponent] = [
.text(executableName.magena),
.text(command.blue),
.level,
.group([
.text("-> "),
.message
])
]
return Logger(label: executableName) { _ in
LoggingFormatAndPipe.Handler(
formatter: LevelFormatter(basic: BasicFormatter(
formatters,
separator: " | "
)),
pipe: LoggerTextOutputStreamPipe.standardOutput
)
}
}
func withLogger<T>(_ operation: () async throws -> T) async rethrows -> T {
try await withDependencies {
$0.logger = makeLogger()
$0.logger.logLevel = .init(verbose: verbose)
} operation: {
try await operation()
}
}
}

View File

@@ -90,7 +90,7 @@ public struct SemVar: CustomStringConvertible, Equatable, Sendable {
} }
// Bumps the sem-var by the given option (major, minor, patch) // Bumps the sem-var by the given option (major, minor, patch)
public func bump(_ option: CliClient.BumpOption, preRelease: String?) -> Self { public func bump(_ option: CliClient.BumpOption, preRelease: String? = nil) -> Self {
switch option { switch option {
case .major: case .major:
return .init( return .init(

View File

@@ -68,6 +68,7 @@ public extension Configuration {
struct PreRelease: Codable, Equatable, Sendable { struct PreRelease: Codable, Equatable, Sendable {
public let prefix: String? public let prefix: String?
// TODO: Remove optional.
public let strategy: Strategy? public let strategy: Strategy?
public init( public init(
@@ -81,6 +82,7 @@ public extension Configuration {
public enum Strategy: Codable, Equatable, Sendable { public enum Strategy: Codable, Equatable, Sendable {
case branch(includeCommitSha: Bool = true) case branch(includeCommitSha: Bool = true)
case command(arguments: [String]) case command(arguments: [String])
// TODO: Remove.
case gitTag case gitTag
public var branch: Branch? { public var branch: Branch? {
@@ -116,6 +118,11 @@ public extension Configuration {
self.requireExistingSemVar = requireExistingSemVar self.requireExistingSemVar = requireExistingSemVar
} }
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. /// Represents the target where we will bump the version in.

View File

@@ -4,8 +4,10 @@ import Foundation
import ShellClient import ShellClient
struct BuildCommand: AsyncParsableCommand { struct BuildCommand: AsyncParsableCommand {
static let commandName = "build"
static let configuration: CommandConfiguration = .init( static let configuration: CommandConfiguration = .init(
commandName: "build", commandName: Self.commandName,
abstract: "Used for the build with version plugin.", abstract: "Used for the build with version plugin.",
discussion: "This should generally not be interacted with directly, outside of the build plugin.", discussion: "This should generally not be interacted with directly, outside of the build plugin.",
shouldDisplay: false shouldDisplay: false
@@ -14,6 +16,6 @@ struct BuildCommand: AsyncParsableCommand {
@OptionGroup var globals: GlobalOptions @OptionGroup var globals: GlobalOptions
func run() async throws { func run() async throws {
try await globals.run(\.build) try await globals.run(\.build, command: Self.commandName)
} }
} }

View File

@@ -4,8 +4,10 @@ import Dependencies
struct BumpCommand: AsyncParsableCommand { struct BumpCommand: AsyncParsableCommand {
static let commandName = "bump"
static let configuration = CommandConfiguration( static let configuration = CommandConfiguration(
commandName: "bump", commandName: Self.commandName,
abstract: "Bump version of a command-line tool." abstract: "Bump version of a command-line tool."
) )
@@ -19,7 +21,7 @@ struct BumpCommand: AsyncParsableCommand {
var bumpOption: CliClient.BumpOption = .patch var bumpOption: CliClient.BumpOption = .patch
func run() async throws { func run() async throws {
try await globals.run(\.bump, args: bumpOption) try await globals.run(\.bump, command: Self.commandName, args: bumpOption)
} }
} }

View File

@@ -5,8 +5,10 @@ import Foundation
import ShellClient import ShellClient
struct GenerateCommand: AsyncParsableCommand { struct GenerateCommand: AsyncParsableCommand {
static let commandName = "generate"
static let configuration: CommandConfiguration = .init( static let configuration: CommandConfiguration = .init(
commandName: "generate", commandName: Self.commandName,
abstract: "Generates a version file in a command line tool that can be set via the git tag or git sha.", abstract: "Generates a version file in a command line tool that can be set via the git tag or git sha.",
discussion: "This command can be interacted with directly, outside of the plugin usage context." discussion: "This command can be interacted with directly, outside of the plugin usage context."
) )
@@ -14,6 +16,6 @@ struct GenerateCommand: AsyncParsableCommand {
@OptionGroup var globals: GlobalOptions @OptionGroup var globals: GlobalOptions
func run() async throws { func run() async throws {
try await globals.run(\.generate) try await globals.run(\.generate, command: Self.commandName)
} }
} }

View File

@@ -18,8 +18,10 @@ struct UtilsCommand: AsyncParsableCommand {
extension UtilsCommand { extension UtilsCommand {
struct DumpConfig: AsyncParsableCommand { struct DumpConfig: AsyncParsableCommand {
static let commandName = "dump-config"
static let configuration = CommandConfiguration( static let configuration = CommandConfiguration(
commandName: "dump-config", commandName: Self.commandName,
abstract: "Show the parsed configuration.", abstract: "Show the parsed configuration.",
aliases: ["dc"] aliases: ["dc"]
) )
@@ -27,7 +29,7 @@ extension UtilsCommand {
@OptionGroup var globals: GlobalOptions @OptionGroup var globals: GlobalOptions
func run() async throws { func run() async throws {
let configuration = try await globals.runClient(\.parsedConfiguration) let configuration = try await globals.runClient(\.parsedConfiguration, command: Self.commandName)
customDump(configuration) customDump(configuration)
} }
} }

View File

@@ -21,45 +21,49 @@ func withSetupDependencies<T>(
extension GlobalOptions { extension GlobalOptions {
func runClient<T>( func runClient<T>(
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> T> _ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> T>,
command: String
) async throws -> T { ) async throws -> T {
try await withSetupDependencies { try await withSetupDependencies {
@Dependency(\.cliClient) var cliClient @Dependency(\.cliClient) var cliClient
return try await cliClient[keyPath: keyPath](shared()) return try await cliClient[keyPath: keyPath](shared(command: command))
} }
} }
func runClient<A, T>( func runClient<A, T>(
_ keyPath: KeyPath<CliClient, @Sendable (A, CliClient.SharedOptions) async throws -> T>, _ keyPath: KeyPath<CliClient, @Sendable (A, CliClient.SharedOptions) async throws -> T>,
command: String,
args: A args: A
) async throws -> T { ) async throws -> T {
try await withSetupDependencies { try await withSetupDependencies {
@Dependency(\.cliClient) var cliClient @Dependency(\.cliClient) var cliClient
return try await cliClient[keyPath: keyPath](args, shared()) return try await cliClient[keyPath: keyPath](args, shared(command: command))
} }
} }
func run( func run(
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String> _ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String>,
command: String
) async throws { ) async throws {
let output = try await runClient(keyPath) let output = try await runClient(keyPath, command: command)
print(output) print(output)
} }
func run<T>( func run<T>(
_ keyPath: KeyPath<CliClient, @Sendable (T, CliClient.SharedOptions) async throws -> String>, _ keyPath: KeyPath<CliClient, @Sendable (T, CliClient.SharedOptions) async throws -> String>,
command: String,
args: T args: T
) async throws { ) async throws {
let output = try await runClient(keyPath, args: args) let output = try await runClient(keyPath, command: command, args: args)
print(output) print(output)
} }
func shared() throws -> CliClient.SharedOptions { func shared(command: String) throws -> CliClient.SharedOptions {
try .init( try .init(
allowPreReleaseTag: !configOptions.semvarOptions.preRelease.disablePreRelease, allowPreReleaseTag: !configOptions.semvarOptions.preRelease.disablePreRelease,
dryRun: dryRun, dryRun: dryRun,
gitDirectory: gitDirectory, gitDirectory: gitDirectory,
verbose: verbose, loggingOptions: .init(command: command, verbose: verbose),
target: configOptions.target(), target: configOptions.target(),
branch: .init(includeCommitSha: configOptions.commitSha), branch: .init(includeCommitSha: configOptions.commitSha),
semvar: configOptions.semvarOptions(extraOptions: extraOptions), semvar: configOptions.semvarOptions(extraOptions: extraOptions),
@@ -93,6 +97,8 @@ extension PreReleaseOptions {
throw ExtraOptionsEmpty() throw ExtraOptionsEmpty()
} }
return .init(prefix: preReleasePrefix, strategy: .command(arguments: extraOptions)) return .init(prefix: preReleasePrefix, strategy: .command(arguments: extraOptions))
} else if let preReleasePrefix {
return .init(prefix: preReleasePrefix, strategy: nil)
} }
return nil return nil
} }

View File

@@ -41,7 +41,10 @@ struct CliClientTests {
#expect(output == "/baz/Sources/bar/Version.swift") #expect(output == "/baz/Sources/bar/Version.swift")
} assert: { string, _ in } assert: { string, _ in
if type != .preRelease {
#expect(string != nil) #expect(string != nil)
}
let typeString = optional ? "String?" : "String" let typeString = optional ? "String?" : "String"
switch type { switch type {
@@ -132,13 +135,12 @@ extension CliClient.SharedOptions {
gitDirectory: String? = "/baz", gitDirectory: String? = "/baz",
dryRun: Bool = false, dryRun: Bool = false,
target: String = "bar", target: String = "bar",
logLevel: Logger.Level = .trace,
versionStrategy: Configuration.VersionStrategy = .semvar(.init()) versionStrategy: Configuration.VersionStrategy = .semvar(.init())
) -> Self { ) -> Self {
return .init( return .init(
dryRun: dryRun, dryRun: dryRun,
gitDirectory: gitDirectory, gitDirectory: gitDirectory,
logLevel: logLevel, loggingOptions: .init(command: "test", verbose: 2),
target: .init(module: .init(target)), target: .init(module: .init(target)),
branch: versionStrategy.branch, branch: versionStrategy.branch,
semvar: versionStrategy.semvar semvar: versionStrategy.semvar