feat: Updates configuration commands and parsing strategy.
All checks were successful
CI / Ubuntu (push) Successful in 2m59s
All checks were successful
CI / Ubuntu (push) Successful in 2m59s
This commit is contained in:
@@ -1,14 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"target" : {
|
||||||
|
"module" : { "name" : "cli-version" }
|
||||||
|
},
|
||||||
"strategy" : {
|
"strategy" : {
|
||||||
"semvar" : {
|
"semvar" : {
|
||||||
"preRelease" : {
|
"strategy" : { "gitTag": { "exactMatch": false } }
|
||||||
"strategy" : { "gitTag" : {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"target" : {
|
|
||||||
"module" : {
|
|
||||||
"name" : "bump-version"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ extension Configuration.Branch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Configuration.SemVar {
|
extension Configuration.SemVar {
|
||||||
|
// TODO: Merge strategy ??
|
||||||
func merging(_ other: Self?) -> Self {
|
func merging(_ other: Self?) -> Self {
|
||||||
.init(
|
.init(
|
||||||
|
allowPreRelease: other?.allowPreRelease ?? allowPreRelease,
|
||||||
preRelease: preRelease?.merging(other?.preRelease),
|
preRelease: preRelease?.merging(other?.preRelease),
|
||||||
requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
|
requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
|
||||||
requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar
|
requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar,
|
||||||
|
strategy: other?.strategy ?? strategy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public extension Configuration.SemVar {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if requireExistingFile {
|
if requireExistingFile == true {
|
||||||
logger.debug("Failed to parse existing file, and caller requires it.")
|
logger.debug("Failed to parse existing file, and caller requires it.")
|
||||||
throw CliClientError.fileDoesNotExist(path: file.cleanFilePath)
|
throw CliClientError.fileDoesNotExist(path: file.cleanFilePath)
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ public extension Configuration.SemVar {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if requireExistingSemVar {
|
if requireExistingSemVar == true {
|
||||||
logger.trace("Caller requires existing semvar and it was not found in file or git-tag.")
|
logger.trace("Caller requires existing semvar and it was not found in file or git-tag.")
|
||||||
throw CliClientError.semVarNotFound
|
throw CliClientError.semVarNotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public struct Configuration: Codable, Equatable, Sendable {
|
|||||||
public static func mock(module: String = "cli-version") -> Self {
|
public static func mock(module: String = "cli-version") -> Self {
|
||||||
.init(
|
.init(
|
||||||
target: .init(module: .init(module)),
|
target: .init(module: .init(module)),
|
||||||
strategy: .semvar(.init())
|
strategy: .semvar(.init(strategy: .gitTag(exactMatch: false)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,28 +99,36 @@ public extension Configuration {
|
|||||||
///
|
///
|
||||||
struct SemVar: Codable, Equatable, Sendable {
|
struct SemVar: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let allowPreRelease: Bool?
|
||||||
|
|
||||||
/// Optional pre-releas suffix strategy.
|
/// Optional pre-releas suffix strategy.
|
||||||
public let preRelease: PreRelease?
|
public let preRelease: PreRelease?
|
||||||
|
|
||||||
/// Fail if an existing version file does not exist in the target.
|
/// Fail if an existing version file does not exist in the target.
|
||||||
public let requireExistingFile: Bool
|
public let requireExistingFile: Bool?
|
||||||
|
|
||||||
/// Fail if an existing semvar is not parsed from the file or version generation strategy.
|
/// Fail if an existing semvar is not parsed from the file or version generation strategy.
|
||||||
public let requireExistingSemVar: Bool
|
public let requireExistingSemVar: Bool?
|
||||||
|
|
||||||
|
public let strategy: Strategy?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
allowPreRelease: Bool? = true,
|
||||||
preRelease: PreRelease? = nil,
|
preRelease: PreRelease? = nil,
|
||||||
requireExistingFile: Bool = true,
|
requireExistingFile: Bool? = true,
|
||||||
requireExistingSemVar: Bool = true
|
requireExistingSemVar: Bool? = true,
|
||||||
|
strategy: Strategy? = nil
|
||||||
) {
|
) {
|
||||||
|
self.allowPreRelease = allowPreRelease
|
||||||
self.preRelease = preRelease
|
self.preRelease = preRelease
|
||||||
self.requireExistingFile = requireExistingFile
|
self.requireExistingFile = requireExistingFile
|
||||||
self.requireExistingSemVar = requireExistingSemVar
|
self.requireExistingSemVar = requireExistingSemVar
|
||||||
|
self.strategy = strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Strategy: Codable, Equatable, Sendable {
|
public enum Strategy: Codable, Equatable, Sendable {
|
||||||
case command(arguments: [String])
|
case command(arguments: [String])
|
||||||
case gitTag(exactMatch: Bool = false)
|
case gitTag(exactMatch: Bool? = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -172,7 +180,7 @@ public extension Configuration {
|
|||||||
/// - fileName: The file name located in the module directory.
|
/// - fileName: The file name located in the module directory.
|
||||||
public init(
|
public init(
|
||||||
_ name: String,
|
_ name: String,
|
||||||
fileName: String? = "Version.swift"
|
fileName: String? = nil
|
||||||
) {
|
) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.fileName = fileName
|
self.fileName = fileName
|
||||||
@@ -225,9 +233,11 @@ public extension Configuration {
|
|||||||
case branch(includeCommitSha: Bool = true)
|
case branch(includeCommitSha: Bool = true)
|
||||||
|
|
||||||
case semvar(
|
case semvar(
|
||||||
|
allowPreRelease: Bool? = nil,
|
||||||
preRelease: PreRelease? = nil,
|
preRelease: PreRelease? = nil,
|
||||||
requireExistingFile: Bool? = nil,
|
requireExistingFile: Bool? = nil,
|
||||||
requireExistingSemVar: Bool? = nil
|
requireExistingSemVar: Bool? = nil,
|
||||||
|
strategy: SemVar.Strategy? = nil
|
||||||
)
|
)
|
||||||
|
|
||||||
public var branch: Branch? {
|
public var branch: Branch? {
|
||||||
@@ -238,12 +248,14 @@ public extension Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var semvar: SemVar? {
|
public var semvar: SemVar? {
|
||||||
guard case let .semvar(preRelease, requireExistingFile, requireExistingSemVar) = self
|
guard case let .semvar(allowPreRelease, preRelease, requireExistingFile, requireExistingSemVar, strategy) = self
|
||||||
else { return nil }
|
else { return nil }
|
||||||
return .init(
|
return .init(
|
||||||
|
allowPreRelease: allowPreRelease,
|
||||||
preRelease: preRelease,
|
preRelease: preRelease,
|
||||||
requireExistingFile: requireExistingFile ?? false,
|
requireExistingFile: requireExistingFile ?? false,
|
||||||
requireExistingSemVar: requireExistingSemVar ?? false
|
requireExistingSemVar: requireExistingSemVar ?? false,
|
||||||
|
strategy: strategy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +265,11 @@ public extension Configuration {
|
|||||||
|
|
||||||
public static func semvar(_ value: SemVar) -> Self {
|
public static func semvar(_ value: SemVar) -> Self {
|
||||||
.semvar(
|
.semvar(
|
||||||
|
allowPreRelease: value.allowPreRelease,
|
||||||
preRelease: value.preRelease,
|
preRelease: value.preRelease,
|
||||||
requireExistingFile: value.requireExistingFile,
|
requireExistingFile: value.requireExistingFile,
|
||||||
requireExistingSemVar: value.requireExistingSemVar
|
requireExistingSemVar: value.requireExistingSemVar,
|
||||||
|
strategy: value.strategy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ struct Application: AsyncParsableCommand {
|
|||||||
BuildCommand.self,
|
BuildCommand.self,
|
||||||
BumpCommand.self,
|
BumpCommand.self,
|
||||||
GenerateCommand.self,
|
GenerateCommand.self,
|
||||||
UtilsCommand.self
|
ConfigCommand.self
|
||||||
],
|
],
|
||||||
defaultSubcommand: BumpCommand.self
|
defaultSubcommand: BumpCommand.self
|
||||||
)
|
)
|
||||||
|
|||||||
171
Sources/bump-version/Commands/ConfigCommand.swift
Normal file
171
Sources/bump-version/Commands/ConfigCommand.swift
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import ArgumentParser
|
||||||
|
import CliClient
|
||||||
|
import ConfigurationClient
|
||||||
|
import CustomDump
|
||||||
|
import Dependencies
|
||||||
|
import FileClient
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ConfigCommand: AsyncParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(
|
||||||
|
commandName: "config",
|
||||||
|
abstract: "Configuration commands",
|
||||||
|
subcommands: [
|
||||||
|
DumpConfig.self,
|
||||||
|
GenerateConfig.self
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConfigCommand {
|
||||||
|
|
||||||
|
struct DumpConfig: AsyncParsableCommand {
|
||||||
|
static let commandName = "dump"
|
||||||
|
|
||||||
|
static let configuration = CommandConfiguration(
|
||||||
|
commandName: Self.commandName,
|
||||||
|
abstract: "Inspect the parsed configuration.",
|
||||||
|
discussion: """
|
||||||
|
This will load any configuration and merge the options passed in. Then print it to stdout.
|
||||||
|
The default style is to print the output in `swift`, however you can use the `--print` flag to
|
||||||
|
print the output in `json`.
|
||||||
|
""",
|
||||||
|
aliases: ["d"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptionGroup var globals: ConfigCommandOptions
|
||||||
|
|
||||||
|
func run() async throws {
|
||||||
|
let configuration = try await globals
|
||||||
|
.shared(command: Self.commandName)
|
||||||
|
.runClient(\.parsedConfiguration)
|
||||||
|
|
||||||
|
try globals.printConfiguration(configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GenerateConfig: AsyncParsableCommand {
|
||||||
|
static let configuration: CommandConfiguration = .init(
|
||||||
|
commandName: "generate",
|
||||||
|
abstract: "Generate a configuration file.",
|
||||||
|
aliases: ["g"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
help: "The style of the configuration."
|
||||||
|
)
|
||||||
|
var style: ConfigCommand.Style = .semvar
|
||||||
|
|
||||||
|
@OptionGroup var globals: ConfigCommandOptions
|
||||||
|
|
||||||
|
func run() async throws {
|
||||||
|
try await withSetupDependencies {
|
||||||
|
@Dependency(\.configurationClient) var configurationClient
|
||||||
|
|
||||||
|
let configuration = try style.parseConfiguration(
|
||||||
|
configOptions: globals.configOptions,
|
||||||
|
extraOptions: globals.extraOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
switch globals.printJson {
|
||||||
|
case true:
|
||||||
|
try globals.handlePrintJson(configuration)
|
||||||
|
case false:
|
||||||
|
let url = globals.configFileUrl
|
||||||
|
try await configurationClient.write(configuration, url)
|
||||||
|
print(url.cleanFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConfigCommand {
|
||||||
|
enum Style: EnumerableFlag {
|
||||||
|
case branch, semvar
|
||||||
|
|
||||||
|
func parseConfiguration(
|
||||||
|
configOptions: ConfigurationOptions,
|
||||||
|
extraOptions: [String]
|
||||||
|
) throws -> Configuration {
|
||||||
|
let strategy: Configuration.VersionStrategy
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .branch:
|
||||||
|
strategy = .branch(includeCommitSha: configOptions.commitSha)
|
||||||
|
case .semvar:
|
||||||
|
strategy = try .semvar(configOptions.semvarOptions(extraOptions: extraOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
return try Configuration(
|
||||||
|
target: configOptions.target(),
|
||||||
|
strategy: strategy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@dynamicMemberLookup
|
||||||
|
struct ConfigCommandOptions: ParsableArguments {
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
name: .customLong("print"),
|
||||||
|
help: "Print style to stdout."
|
||||||
|
)
|
||||||
|
var printJson: Bool = false
|
||||||
|
|
||||||
|
@OptionGroup var configOptions: ConfigurationOptions
|
||||||
|
|
||||||
|
@Argument(
|
||||||
|
help: """
|
||||||
|
Arguments / options used for custom pre-release, options / flags must proceed a '--' in
|
||||||
|
the command. These are ignored if the `--custom-command` or `--custom-pre-release` flag is not set.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
var extraOptions: [String] = []
|
||||||
|
|
||||||
|
subscript<T>(dynamicMember keyPath: KeyPath<ConfigurationOptions, T>) -> T {
|
||||||
|
configOptions[keyPath: keyPath]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ConfigCommand.ConfigCommandOptions {
|
||||||
|
|
||||||
|
func shared(command: String) throws -> CliClient.SharedOptions {
|
||||||
|
try configOptions.shared(command: command, extraOptions: extraOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePrintJson(_ configuration: Configuration) throws {
|
||||||
|
@Dependency(\.coders) var coders
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let data = try coders.jsonEncoder().encode(configuration)
|
||||||
|
guard let string = String(bytes: data, encoding: .utf8) else {
|
||||||
|
logger.error("Error encoding configuration to json.")
|
||||||
|
throw ConfigurationEncodingError()
|
||||||
|
}
|
||||||
|
print(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printConfiguration(_ configuration: Configuration) throws {
|
||||||
|
guard printJson else {
|
||||||
|
customDump(configuration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try handlePrintJson(configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ConfigurationOptions {
|
||||||
|
var configFileUrl: URL {
|
||||||
|
switch configurationFile {
|
||||||
|
case let .some(path):
|
||||||
|
return URL(filePath: path)
|
||||||
|
case .none:
|
||||||
|
return URL(filePath: ".bump-version.json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigurationEncodingError: Error {}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import ArgumentParser
|
|
||||||
import ConfigurationClient
|
|
||||||
import CustomDump
|
|
||||||
import Dependencies
|
|
||||||
import FileClient
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct UtilsCommand: AsyncParsableCommand {
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
commandName: "utils",
|
|
||||||
abstract: "Utility commands",
|
|
||||||
subcommands: [
|
|
||||||
DumpConfig.self,
|
|
||||||
GenerateConfig.self
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UtilsCommand {
|
|
||||||
struct DumpConfig: AsyncParsableCommand {
|
|
||||||
static let commandName = "dump-config"
|
|
||||||
|
|
||||||
static let configuration = CommandConfiguration(
|
|
||||||
commandName: Self.commandName,
|
|
||||||
abstract: "Show the parsed configuration.",
|
|
||||||
aliases: ["dc"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptionGroup var globals: GlobalOptions
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
let configuration = try await globals.runClient(\.parsedConfiguration, command: Self.commandName)
|
|
||||||
customDump(configuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GenerateConfig: AsyncParsableCommand {
|
|
||||||
static let configuration: CommandConfiguration = .init(
|
|
||||||
commandName: "generate-config",
|
|
||||||
abstract: "Generate a configuration file.",
|
|
||||||
aliases: ["gc"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptionGroup var configOptions: ConfigurationOptions
|
|
||||||
|
|
||||||
@Flag(
|
|
||||||
help: "The style of the configuration."
|
|
||||||
)
|
|
||||||
var style: Style = .semvar
|
|
||||||
|
|
||||||
@Argument(
|
|
||||||
help: """
|
|
||||||
Arguments / options used for custom pre-release, options / flags must proceed a '--' in
|
|
||||||
the command. These are ignored if the `--custom` flag is not set.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
var extraOptions: [String] = []
|
|
||||||
|
|
||||||
func run() async throws {
|
|
||||||
try await withSetupDependencies {
|
|
||||||
@Dependency(\.configurationClient) var configurationClient
|
|
||||||
|
|
||||||
let strategy: Configuration.VersionStrategy
|
|
||||||
|
|
||||||
switch style {
|
|
||||||
case .branch:
|
|
||||||
strategy = .branch(includeCommitSha: configOptions.commitSha)
|
|
||||||
case .semvar:
|
|
||||||
strategy = try .semvar(configOptions.semvarOptions(extraOptions: extraOptions))
|
|
||||||
}
|
|
||||||
|
|
||||||
let configuration = try Configuration(
|
|
||||||
target: configOptions.target(),
|
|
||||||
strategy: strategy
|
|
||||||
)
|
|
||||||
|
|
||||||
let url: URL
|
|
||||||
switch configOptions.configurationFile {
|
|
||||||
case let .some(path):
|
|
||||||
url = URL(filePath: path)
|
|
||||||
case .none:
|
|
||||||
url = URL(filePath: ".bump-version.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
try await configurationClient.write(configuration, url)
|
|
||||||
|
|
||||||
print(url.cleanFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UtilsCommand.GenerateConfig {
|
|
||||||
enum Style: EnumerableFlag {
|
|
||||||
case branch, semvar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,7 +31,7 @@ struct GlobalOptions: ParsableArguments {
|
|||||||
@Argument(
|
@Argument(
|
||||||
help: """
|
help: """
|
||||||
Arguments / options used for custom pre-release, options / flags must proceed a '--' in
|
Arguments / options used for custom pre-release, options / flags must proceed a '--' in
|
||||||
the command. These are ignored if the `--custom` flag is not set.
|
the command. These are ignored if the `--custom` or `--custom-pre-release` flag is not set.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
var extraOptions: [String] = []
|
var extraOptions: [String] = []
|
||||||
@@ -40,7 +40,7 @@ struct GlobalOptions: ParsableArguments {
|
|||||||
|
|
||||||
struct ConfigurationOptions: ParsableArguments {
|
struct ConfigurationOptions: ParsableArguments {
|
||||||
@Option(
|
@Option(
|
||||||
name: .shortAndLong,
|
name: [.customShort("f"), .long],
|
||||||
help: "Specify the path to a configuration file.",
|
help: "Specify the path to a configuration file.",
|
||||||
completion: .file(extensions: ["json"])
|
completion: .file(extensions: ["json"])
|
||||||
)
|
)
|
||||||
@@ -63,22 +63,22 @@ struct ConfigurationOptions: ParsableArguments {
|
|||||||
|
|
||||||
struct TargetOptions: ParsableArguments {
|
struct TargetOptions: ParsableArguments {
|
||||||
@Option(
|
@Option(
|
||||||
name: .shortAndLong,
|
name: [.customShort("p"), .long],
|
||||||
help: "Path to the version file, not required if module is set."
|
help: "Path to the version file, not required if module is set."
|
||||||
)
|
)
|
||||||
var path: String?
|
var targetFilePath: String?
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
name: .shortAndLong,
|
name: [.customShort("m"), .long],
|
||||||
help: "The target module name or directory path, not required if path is set."
|
help: "The target module name or directory path, not required if path is set."
|
||||||
)
|
)
|
||||||
var module: String?
|
var targetModule: String?
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
name: [.customShort("n"), .long],
|
name: [.customShort("n"), .long],
|
||||||
help: "The file name inside the target module, required if module is set."
|
help: "The file name inside the target module. (defaults to: \"Version.swift\")."
|
||||||
)
|
)
|
||||||
var fileName: String = "Version.swift"
|
var targetFileName: String?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ struct PreReleaseOptions: ParsableArguments {
|
|||||||
var disablePreRelease: Bool = false
|
var disablePreRelease: Bool = false
|
||||||
|
|
||||||
@Flag(
|
@Flag(
|
||||||
name: [.customShort("s"), .customLong("pre-release-branch-style")],
|
name: [.customShort("b"), .customLong("pre-release-branch-style")],
|
||||||
help: """
|
help: """
|
||||||
Use branch name and commit sha for pre-release suffix, ignored if branch is set.
|
Use branch name and commit sha for pre-release suffix, ignored if branch is set.
|
||||||
"""
|
"""
|
||||||
@@ -124,8 +124,22 @@ struct PreReleaseOptions: ParsableArguments {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add custom command strategy.
|
||||||
struct SemVarOptions: ParsableArguments {
|
struct SemVarOptions: ParsableArguments {
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
name: .long,
|
||||||
|
inversion: .prefixedEnableDisable,
|
||||||
|
help: "Use git-tag strategy for semvar."
|
||||||
|
)
|
||||||
|
var gitTag: Bool = true
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
name: .long,
|
||||||
|
help: "Require exact match for git tag strategy."
|
||||||
|
)
|
||||||
|
var requireExactMatch: Bool = false
|
||||||
|
|
||||||
@Flag(
|
@Flag(
|
||||||
name: .long,
|
name: .long,
|
||||||
help: """
|
help: """
|
||||||
@@ -136,9 +150,18 @@ struct SemVarOptions: ParsableArguments {
|
|||||||
|
|
||||||
@Flag(
|
@Flag(
|
||||||
name: .long,
|
name: .long,
|
||||||
help: "Fail if a sem-var is not parsed from existing file or git tag, used if branch is not set."
|
help: "Fail if a semvar is not parsed from existing file or git tag, used if branch is not set."
|
||||||
)
|
)
|
||||||
var requireExistingSemvar: Bool = false
|
var requireExistingSemvar: Bool = false
|
||||||
|
|
||||||
|
@Flag(
|
||||||
|
name: .shortAndLong,
|
||||||
|
help: """
|
||||||
|
Custom command strategy, uses extra-options to call an external command.
|
||||||
|
The external command should return a semvar that is used.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
var customCommand: Bool = false
|
||||||
|
|
||||||
@OptionGroup var preRelease: PreReleaseOptions
|
@OptionGroup var preRelease: PreReleaseOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,34 +18,35 @@ func withSetupDependencies<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GlobalOptions {
|
extension CliClient.SharedOptions {
|
||||||
|
|
||||||
func runClient<T>(
|
func runClient<T>(
|
||||||
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> T>,
|
_ keyPath: KeyPath<CliClient, @Sendable (Self) 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(command: command))
|
return try await cliClient[keyPath: keyPath](self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient<A, T>(
|
func runClient<A, T>(
|
||||||
_ keyPath: KeyPath<CliClient, @Sendable (A, CliClient.SharedOptions) async throws -> T>,
|
_ keyPath: KeyPath<CliClient, @Sendable (A, Self) 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(command: command))
|
return try await cliClient[keyPath: keyPath](args, self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GlobalOptions {
|
||||||
|
|
||||||
func run(
|
func run(
|
||||||
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String>,
|
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String>,
|
||||||
command: String
|
command: String
|
||||||
) async throws {
|
) async throws {
|
||||||
let output = try await runClient(keyPath, command: command)
|
let output = try await shared(command: command).runClient(keyPath)
|
||||||
print(output)
|
print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,40 +55,39 @@ extension GlobalOptions {
|
|||||||
command: String,
|
command: String,
|
||||||
args: T
|
args: T
|
||||||
) async throws {
|
) async throws {
|
||||||
let output = try await runClient(keyPath, command: command, args: args)
|
let output = try await shared(command: command).runClient(keyPath, args: args)
|
||||||
print(output)
|
print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shared(command: String) throws -> CliClient.SharedOptions {
|
func shared(command: String) throws -> CliClient.SharedOptions {
|
||||||
try .init(
|
try configOptions.shared(
|
||||||
allowPreReleaseTag: !configOptions.semvarOptions.preRelease.disablePreRelease,
|
command: command,
|
||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
|
extraOptions: extraOptions,
|
||||||
gitDirectory: gitDirectory,
|
gitDirectory: gitDirectory,
|
||||||
loggingOptions: .init(command: command, verbose: verbose),
|
verbose: verbose
|
||||||
target: configOptions.target(),
|
|
||||||
branch: .init(includeCommitSha: configOptions.commitSha),
|
|
||||||
semvar: configOptions.semvarOptions(extraOptions: extraOptions),
|
|
||||||
configurationFile: configOptions.configurationFile
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension TargetOptions {
|
private extension TargetOptions {
|
||||||
func configTarget() throws -> Configuration.Target? {
|
func configTarget() throws -> Configuration.Target? {
|
||||||
guard let path else {
|
guard let targetFilePath else {
|
||||||
guard let module else {
|
guard let targetModule else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(module: .init(module, fileName: fileName))
|
return .init(module: .init(targetModule, fileName: targetFileName))
|
||||||
}
|
}
|
||||||
return .init(path: path)
|
return .init(path: targetFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreReleaseOptions {
|
extension PreReleaseOptions {
|
||||||
|
|
||||||
func configPreReleaseStrategy(includeCommitSha: Bool, extraOptions: [String]) throws -> Configuration.PreRelease? {
|
func configPreReleaseStrategy(
|
||||||
|
includeCommitSha: Bool,
|
||||||
|
extraOptions: [String]
|
||||||
|
) throws -> Configuration.PreRelease? {
|
||||||
if useBranchAsPreRelease {
|
if useBranchAsPreRelease {
|
||||||
return .init(prefix: preReleasePrefix, strategy: .branch(includeCommitSha: includeCommitSha))
|
return .init(prefix: preReleasePrefix, strategy: .branch(includeCommitSha: includeCommitSha))
|
||||||
} else if useTagAsPreRelease {
|
} else if useTagAsPreRelease {
|
||||||
@@ -106,11 +106,48 @@ extension PreReleaseOptions {
|
|||||||
|
|
||||||
extension SemVarOptions {
|
extension SemVarOptions {
|
||||||
|
|
||||||
func configSemVarOptions(includeCommitSha: Bool, extraOptions: [String]) throws -> Configuration.SemVar {
|
func parseStrategy(extraOptions: [String]) throws -> Configuration.SemVar.Strategy? {
|
||||||
try .init(
|
@Dependency(\.logger) var logger
|
||||||
preRelease: preRelease.configPreReleaseStrategy(includeCommitSha: includeCommitSha, extraOptions: extraOptions),
|
|
||||||
requireExistingFile: requireExistingFile,
|
guard customCommand else {
|
||||||
requireExistingSemVar: requireExistingSemvar
|
guard gitTag else { return nil }
|
||||||
|
return .gitTag(exactMatch: requireExactMatch)
|
||||||
|
}
|
||||||
|
guard extraOptions.count > 0 else {
|
||||||
|
logger.error("""
|
||||||
|
Extra options are empty, this does not make sense when using a custom command
|
||||||
|
strategy.
|
||||||
|
""")
|
||||||
|
throw ExtraOptionsEmpty()
|
||||||
|
}
|
||||||
|
return .command(arguments: extraOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configSemVarOptions(
|
||||||
|
includeCommitSha: Bool,
|
||||||
|
extraOptions: [String]
|
||||||
|
) throws -> Configuration.SemVar {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
// TODO: Update when / if there's an update config command.
|
||||||
|
if customCommand && preRelease.customPreRelease {
|
||||||
|
logger.warning("""
|
||||||
|
Custom pre-release can not be used at same time as custom command.
|
||||||
|
Ignoring pre-release...
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
return try .init(
|
||||||
|
allowPreRelease: !preRelease.disablePreRelease,
|
||||||
|
preRelease: customCommand ? nil : preRelease.configPreReleaseStrategy(
|
||||||
|
includeCommitSha: includeCommitSha,
|
||||||
|
extraOptions: extraOptions
|
||||||
|
),
|
||||||
|
// Use nil here if false, which makes them not get used in json / file output, which makes
|
||||||
|
// user config smaller.
|
||||||
|
requireExistingFile: requireExistingFile ? true : nil,
|
||||||
|
requireExistingSemVar: requireExistingSemvar ? true : nil,
|
||||||
|
strategy: parseStrategy(extraOptions: extraOptions)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,8 +158,32 @@ extension ConfigurationOptions {
|
|||||||
try targetOptions.configTarget()
|
try targetOptions.configTarget()
|
||||||
}
|
}
|
||||||
|
|
||||||
func semvarOptions(extraOptions: [String]) throws -> Configuration.SemVar {
|
func semvarOptions(
|
||||||
try semvarOptions.configSemVarOptions(includeCommitSha: commitSha, extraOptions: extraOptions)
|
extraOptions: [String]
|
||||||
|
) throws -> Configuration.SemVar {
|
||||||
|
try semvarOptions.configSemVarOptions(
|
||||||
|
includeCommitSha: commitSha,
|
||||||
|
extraOptions: extraOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shared(
|
||||||
|
command: String,
|
||||||
|
dryRun: Bool = true,
|
||||||
|
extraOptions: [String] = [],
|
||||||
|
gitDirectory: String? = nil,
|
||||||
|
verbose: Int = 0
|
||||||
|
) throws -> CliClient.SharedOptions {
|
||||||
|
try .init(
|
||||||
|
allowPreReleaseTag: !semvarOptions.preRelease.disablePreRelease,
|
||||||
|
dryRun: dryRun,
|
||||||
|
gitDirectory: gitDirectory,
|
||||||
|
loggingOptions: .init(command: command, verbose: verbose),
|
||||||
|
target: target(),
|
||||||
|
branch: .init(includeCommitSha: commitSha),
|
||||||
|
semvar: semvarOptions(extraOptions: extraOptions),
|
||||||
|
configurationFile: configurationFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ struct CliClientTests {
|
|||||||
if type != .preRelease {
|
if type != .preRelease {
|
||||||
#expect(string != nil)
|
#expect(string != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let typeString = optional ? "String?" : "String"
|
let typeString = optional ? "String?" : "String"
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
|
|||||||
Reference in New Issue
Block a user