Compare commits
7 Commits
b9cf913528
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
016f0d6c3f
|
|||
|
147f6df1b3
|
|||
|
5c22250a63
|
|||
|
9dd30a1745
|
|||
|
f1eb883b93
|
|||
|
9631c62ee3
|
|||
|
6fe459c39e
|
@@ -55,6 +55,7 @@ let package = Package(
|
||||
name: "ConfigurationClient",
|
||||
dependencies: [
|
||||
"FileClient",
|
||||
"LoggingExtensions",
|
||||
.product(name: "CustomDump", package: "swift-custom-dump"),
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||
@@ -87,6 +88,7 @@ let package = Package(
|
||||
.target(
|
||||
name: "LoggingExtensions",
|
||||
dependencies: [
|
||||
.product(name: "CustomDump", package: "swift-custom-dump"),
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "ShellClient", package: "swift-shell-client")
|
||||
]
|
||||
|
||||
@@ -60,6 +60,7 @@ extension ConfigCommand {
|
||||
@OptionGroup var globals: ConfigCommandOptions
|
||||
|
||||
func run() async throws {
|
||||
@Dependency(\.logger) var logger
|
||||
let configuration = try await globals
|
||||
.shared(command: Self.commandName)
|
||||
.runClient(\.parsedConfiguration)
|
||||
@@ -183,7 +184,11 @@ private extension ConfigCommand.DumpConfig {
|
||||
private extension ConfigCommand.ConfigCommandOptions {
|
||||
|
||||
func shared(command: String) throws -> CliClient.SharedOptions {
|
||||
try configOptions.shared(command: command, extraOptions: extraOptions, verbose: verbose)
|
||||
try configOptions.shared(
|
||||
command: command,
|
||||
extraOptions: extraOptions,
|
||||
verbose: verbose
|
||||
)
|
||||
}
|
||||
|
||||
func handlePrintJson(_ configuration: Configuration) throws {
|
||||
|
||||
@@ -19,18 +19,24 @@ of their usage.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Short | Long | Argument | Description |
|
||||
| ----- | ---------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
|
||||
| -f | --configuration-file | <path> | The path to the configuration to use. |
|
||||
| -m | --target-module | <name> | The target module name inside your project |
|
||||
| -n | --target-file-name | <name> | The file name for the version to be found inside the module |
|
||||
| -p | --target-file-path | <path> | Path to a version file in your project |
|
||||
| N/A | --enable-git-tag/--disable-git-tag | N/A | Use the git-tag version strategy |
|
||||
| N/A | --require-exact-match | N/A | Fail if a tag is not specifically set on the commit |
|
||||
| N/A | --require-existing-semvar | N/A | Fail if an existing semvar is not found in the version file. |
|
||||
| -c | --custom-command | <arguments> | Use a custom command strategy for the version (any options need to proceed a '--') |
|
||||
| N/A | --commit-sha/--no-commit-sha | N/A | Use the commit sha with branch version or pre-release strategy |
|
||||
| N/A | --require-configuration | N/A | Fail if a configuration file is not found |
|
||||
| Short | Long | Argument | Description |
|
||||
| ----- | ---------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
|
||||
| -f | --configuration-file | <path> | The path to the configuration to use. |
|
||||
| -m | --target-module | <name> | The target module name inside your project |
|
||||
| -n | --target-file-name | <name> | The file name for the version to be found inside the module |
|
||||
| -p | --target-file-path | <path> | Path to a version file in your project |
|
||||
| N/A | --enable-git-tag/--disable-git-tag | N/A | Use the git-tag version strategy |
|
||||
| N/A | --require-exact-match | N/A | Fail if a tag is not specifically set on the commit |
|
||||
| N/A | --require-existing-semvar | N/A | Fail if an existing semvar is not found in the version file. |
|
||||
| -c | --custom-command | <arguments> | Use a custom command strategy for the version (any options need to proceed a '--') |
|
||||
| N/A | --commit-sha/--no-commit-sha | N/A | Use the commit sha with branch version or pre-release strategy |
|
||||
| N/A | --require-configuration | N/A | Fail if a configuration file is not found |
|
||||
| N/A | --precedence | <precedence> | The precedence for when a file exists (values: ['file', 'strategy'], default: file) |
|
||||
|
||||
> Note: Precedence is used as tie breaker if the version in the file does not agree with the version
|
||||
> from the configured strategy. This can happen if a file was edited / bumped manually or the value
|
||||
> returned from the external command is not similar to the version in the file. By default the file
|
||||
> will take precedence over what is returned from the strategy.
|
||||
|
||||
#### Pre-Release Options
|
||||
|
||||
|
||||
@@ -173,5 +173,19 @@ struct SemVarOptions: ParsableArguments {
|
||||
)
|
||||
var customCommand: Bool = false
|
||||
|
||||
@Option(
|
||||
name: .long,
|
||||
help: """
|
||||
Set the precence to prefer version from file or strategy.
|
||||
"""
|
||||
)
|
||||
var precedence: Configuration.SemVar.Precedence?
|
||||
|
||||
@OptionGroup var preRelease: PreReleaseOptions
|
||||
}
|
||||
|
||||
extension Configuration.SemVar.Precedence: ExpressibleByArgument {
|
||||
public init?(argument: String) {
|
||||
self.init(rawValue: argument)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +136,11 @@ extension SemVarOptions {
|
||||
""")
|
||||
}
|
||||
|
||||
logger.trace("precedence: \(String(describing: precedence))")
|
||||
|
||||
return try .init(
|
||||
allowPreRelease: !preRelease.disablePreRelease,
|
||||
precedence: precedence,
|
||||
preRelease: customCommand ? nil : preRelease.configPreReleaseStrategy(
|
||||
includeCommitSha: includeCommitSha,
|
||||
extraOptions: extraOptions
|
||||
|
||||
@@ -24,7 +24,7 @@ public struct CliClient: Sendable {
|
||||
public var build: @Sendable (SharedOptions) async throws -> String
|
||||
|
||||
/// Bump the existing version.
|
||||
public var bump: @Sendable (BumpOption?, SharedOptions) async throws -> String
|
||||
public var bump: @Sendable (BumpOption, SharedOptions) async throws -> String
|
||||
|
||||
/// Generate a version file with an optional version that can be set manually.
|
||||
public var generate: @Sendable (SharedOptions) async throws -> String
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import ConfigurationClient
|
||||
|
||||
enum CliClientError: Error {
|
||||
case gitDirectoryNotFound
|
||||
case fileExists(path: String)
|
||||
case fileDoesNotExist(path: String)
|
||||
case failedToParseVersionFile
|
||||
case semVarNotFound
|
||||
case semVarNotFound(message: String)
|
||||
case strategyNotFound(configuration: Configuration)
|
||||
case preReleaseParsingError(String)
|
||||
case versionStringNotFound
|
||||
}
|
||||
|
||||
@@ -4,36 +4,37 @@ import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
import GitClient
|
||||
import LoggingExtensions
|
||||
|
||||
@_spi(Internal)
|
||||
public extension CliClient.SharedOptions {
|
||||
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
|
||||
_ operation: (VersionContainer) 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)")
|
||||
guard let strategy = configuration.strategy else {
|
||||
throw CliClientError.strategyNotFound(configuration: configuration)
|
||||
}
|
||||
|
||||
logger.dump(configuration, level: .trace) {
|
||||
"\nConfiguration: \($0)"
|
||||
}
|
||||
|
||||
// This will fail if the target url is not set properly.
|
||||
let targetUrl = try configuration.targetUrl(gitDirectory: projectDirectory)
|
||||
let targetUrl = try configuration.targetUrl(projectDirectory: projectDirectory)
|
||||
logger.debug("Target: \(targetUrl.cleanFilePath)")
|
||||
|
||||
// Perform the operation, which generates the new version and writes it.
|
||||
try await operation(
|
||||
configuration.currentVersion(
|
||||
targetUrl: targetUrl,
|
||||
gitDirectory: projectDirectory
|
||||
)
|
||||
.load(projectDirectory: projectDirectory, strategy: strategy, url: targetUrl)
|
||||
)
|
||||
|
||||
// Return the file path we wrote the version to.
|
||||
@@ -54,10 +55,9 @@ public extension CliClient.SharedOptions {
|
||||
logger.trace("Merging branch strategy.")
|
||||
// strategy = .branch(branch)
|
||||
} else if let semvar = configurationToMerge?.strategy?.semvar {
|
||||
logger.trace("Merging semvar strategy.")
|
||||
var semvarString = ""
|
||||
customDump(semvar, to: &semvarString)
|
||||
logger.trace("\(semvarString)")
|
||||
logger.dump(semvar, level: .trace) {
|
||||
"Merging semvar strategy:\n\($0)"
|
||||
}
|
||||
}
|
||||
|
||||
return try await configurationClient.withConfiguration(
|
||||
@@ -75,63 +75,52 @@ public extension CliClient.SharedOptions {
|
||||
try await fileClient.write(string: string, to: url)
|
||||
} else {
|
||||
logger.debug("Skipping, due to dry-run being passed.")
|
||||
// logger.info("\n\(string)\n")
|
||||
}
|
||||
}
|
||||
|
||||
func write(_ currentVersion: CurrentVersionContainer) async throws {
|
||||
func write(_ currentVersion: VersionContainer) async throws {
|
||||
@Dependency(\.logger) var logger
|
||||
logger.trace("Begin writing version.")
|
||||
|
||||
let version = try currentVersion.version.string(allowPreReleaseTag: allowPreReleaseTag)
|
||||
if !dryRun {
|
||||
logger.debug("Version: \(version)")
|
||||
} else {
|
||||
logger.info("Version: \(version)")
|
||||
// let hasChanges: Bool
|
||||
let targetUrl: URL
|
||||
let usesOptionalType: Bool
|
||||
let versionString: String?
|
||||
|
||||
switch currentVersion {
|
||||
case let .branch(branch):
|
||||
// hasChanges = branch.hasChanges
|
||||
targetUrl = branch.targetUrl
|
||||
usesOptionalType = branch.usesOptionalType
|
||||
versionString = branch.versionString
|
||||
case let .semvar(semvar):
|
||||
// hasChanges = semvar.hasChanges
|
||||
targetUrl = semvar.targetUrl
|
||||
usesOptionalType = semvar.usesOptionalType
|
||||
versionString = semvar.versionString(withPreRelease: allowPreReleaseTag)
|
||||
}
|
||||
|
||||
let template = currentVersion.usesOptionalType ? Template.optional(version) : Template.nonOptional(version)
|
||||
// if !hasChanges {
|
||||
// logger.debug("No changes from loaded version, not writing next version.")
|
||||
// return
|
||||
// }
|
||||
|
||||
guard let versionString else {
|
||||
throw CliClientError.versionStringNotFound
|
||||
}
|
||||
|
||||
// let version = try currentVersion.version.string(allowPreReleaseTag: allowPreReleaseTag)
|
||||
|
||||
if !dryRun {
|
||||
logger.debug("Version: \(versionString)")
|
||||
} else {
|
||||
logger.info("Version: \(versionString)")
|
||||
}
|
||||
|
||||
let template = usesOptionalType ? Template.optional(versionString) : Template.nonOptional(versionString)
|
||||
logger.trace("Template string: \(template)")
|
||||
|
||||
try await write(template, to: currentVersion.targetUrl)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
switch version {
|
||||
case .string: return false
|
||||
case let .semvar(_, usesOptionalType, _): return usesOptionalType
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
switch self {
|
||||
case let .string(string):
|
||||
return string
|
||||
case let .semvar(semvar, usesOptionalType: _, hasChanges: _):
|
||||
return semvar.versionString(withPreReleaseTag: allowPreReleaseTag)
|
||||
}
|
||||
}
|
||||
try await write(template, to: targetUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,39 +132,35 @@ extension CliClient.SharedOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func bump(_ type: CliClient.BumpOption?) async throws -> String {
|
||||
guard let type else {
|
||||
return try await generate()
|
||||
}
|
||||
|
||||
func bump(_ type: CliClient.BumpOption) async throws -> String {
|
||||
return try await run { container in
|
||||
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
switch container.version {
|
||||
case .string: // When we did not parse a semvar, just write whatever we parsed for the current version.
|
||||
switch container {
|
||||
case .branch: // 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.")
|
||||
try await write(container)
|
||||
|
||||
case let .semvar(semvar, usesOptionalType: usesOptionalType, hasChanges: hasChanges):
|
||||
logger.debug("Semvar prior to bumping: \(semvar)")
|
||||
let bumped = semvar.bump(type)
|
||||
let version = bumped.versionString(withPreReleaseTag: allowPreReleaseTag)
|
||||
case let .semvar(semvar):
|
||||
|
||||
guard bumped != semvar || hasChanges else {
|
||||
logger.debug("No change, skipping.")
|
||||
return
|
||||
let version: SemVar?
|
||||
|
||||
switch semvar.precedence ?? .default {
|
||||
case .file:
|
||||
version = semvar.loadedVersion ?? semvar.strategyVersion
|
||||
case .strategy:
|
||||
version = semvar.strategyVersion ?? semvar.loadedVersion
|
||||
}
|
||||
|
||||
logger.debug("Bumped version: \(version)")
|
||||
|
||||
if dryRun {
|
||||
logger.info("Version: \(version)")
|
||||
return
|
||||
// let version = semvar.loadedVersion ?? semvar.nextVersion
|
||||
guard let version else {
|
||||
throw CliClientError.semVarNotFound(message: "Failed to parse a valid semvar to bump.")
|
||||
}
|
||||
|
||||
let template = usesOptionalType ? Template.optional(version) : Template.build(version)
|
||||
try await write(template, to: container.targetUrl)
|
||||
logger.dump(version, level: .debug) { "Version prior to bumping:\n\($0)" }
|
||||
let bumped = version.bump(type)
|
||||
logger.dump(bumped, level: .trace) { "Bumped version:\n\($0)" }
|
||||
try await write(.semvar(semvar.withUpdateNextVersion(bumped)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,3 +171,15 @@ extension CliClient.SharedOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CurrentVersionContainer where Version == SemVar {
|
||||
func withUpdateNextVersion(_ next: SemVar) -> Self {
|
||||
.init(
|
||||
targetUrl: targetUrl,
|
||||
usesOptionalType: usesOptionalType,
|
||||
loadedVersion: loadedVersion,
|
||||
precedence: .strategy, // make sure to use the next version, since it was specified, as this is called from `bump`.
|
||||
strategyVersion: next
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
57
Sources/CliClient/Internal/Configuration+targetUrl.swift
Normal file
57
Sources/CliClient/Internal/Configuration+targetUrl.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import ConfigurationClient
|
||||
import CustomDump
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import GitClient
|
||||
import ShellClient
|
||||
|
||||
extension Configuration {
|
||||
func targetUrl(projectDirectory: String?) throws -> URL {
|
||||
guard let target else {
|
||||
throw ConfigurationParsingError.targetNotFound
|
||||
}
|
||||
return try target.url(projectDirectory: projectDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Configuration.Target {
|
||||
func url(projectDirectory: String?) throws -> URL {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let filePath: String
|
||||
|
||||
if let path {
|
||||
filePath = path
|
||||
} else {
|
||||
guard let module else {
|
||||
throw ConfigurationParsingError.pathOrModuleNotSet
|
||||
}
|
||||
|
||||
var path = module.name
|
||||
logger.debug("module.name: \(path)")
|
||||
|
||||
if path.hasPrefix("./") {
|
||||
path = String(path.dropFirst(2))
|
||||
}
|
||||
|
||||
if !path.hasPrefix("Sources") {
|
||||
logger.debug("no prefix")
|
||||
path = "Sources/\(path)"
|
||||
}
|
||||
|
||||
filePath = "\(path)/\(module.fileNameOrDefault)"
|
||||
}
|
||||
|
||||
if let projectDirectory {
|
||||
return URL(filePath: "\(projectDirectory)/\(filePath)")
|
||||
}
|
||||
return URL(filePath: filePath)
|
||||
}
|
||||
}
|
||||
|
||||
enum ConfigurationParsingError: Error {
|
||||
case targetNotFound
|
||||
case pathOrModuleNotSet
|
||||
case versionStrategyError(message: String)
|
||||
case versionStrategyNotFound
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import GitClient
|
||||
import ShellClient
|
||||
|
||||
extension Configuration {
|
||||
func targetUrl(gitDirectory: String?) throws -> URL {
|
||||
guard let target else {
|
||||
throw ConfigurationParsingError.targetNotFound
|
||||
}
|
||||
return try target.url(gitDirectory: gitDirectory)
|
||||
}
|
||||
|
||||
func currentVersion(targetUrl: URL, gitDirectory: String?) async throws -> CurrentVersionContainer {
|
||||
guard let strategy else {
|
||||
throw ConfigurationParsingError.versionStrategyNotFound
|
||||
}
|
||||
return try await strategy.currentVersion(
|
||||
targetUrl: targetUrl,
|
||||
gitDirectory: gitDirectory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
// TODO: Need to handle custom command semvar strategy.
|
||||
|
||||
func currentVersion(file: URL, gitDirectory: String? = nil) async throws -> CurrentVersionContainer.Version {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let fileOutput = try? await fileClient.semvar(file: file, gitDirectory: gitDirectory)
|
||||
var semVar = fileOutput?.semVar
|
||||
|
||||
logger.trace("file output semvar: \(String(describing: semVar))")
|
||||
|
||||
let usesOptionalType = fileOutput?.usesOptionalType
|
||||
|
||||
// We parsed a semvar from the existing file, use it.
|
||||
if semVar != nil {
|
||||
let semvarWithPreRelease = try await applyingPreRelease(semVar!, gitDirectory)
|
||||
|
||||
return .semvar(
|
||||
semvarWithPreRelease,
|
||||
usesOptionalType: usesOptionalType ?? false,
|
||||
hasChanges: semvarWithPreRelease != semVar
|
||||
)
|
||||
}
|
||||
|
||||
if requireExistingFile == true {
|
||||
logger.debug("Failed to parse existing file, and caller requires it.")
|
||||
throw CliClientError.fileDoesNotExist(path: file.cleanFilePath)
|
||||
}
|
||||
|
||||
logger.trace("Does not require existing file, checking git-tag.")
|
||||
|
||||
// TODO: Is this what we want to do here? Seems that the strategy should be set by the client / configuration.
|
||||
|
||||
// Didn't have existing semVar loaded from file, so check for git-tag.
|
||||
semVar = try await gitClient.version(.init(
|
||||
gitDirectory: gitDirectory,
|
||||
style: .tag(exactMatch: false)
|
||||
)).semVar
|
||||
|
||||
if semVar != nil {
|
||||
let semvarWithPreRelease = try await applyingPreRelease(semVar!, gitDirectory)
|
||||
return .semvar(
|
||||
semvarWithPreRelease,
|
||||
usesOptionalType: usesOptionalType ?? false,
|
||||
hasChanges: semvarWithPreRelease != semVar
|
||||
)
|
||||
}
|
||||
|
||||
if requireExistingSemVar == true {
|
||||
logger.trace("Caller requires existing semvar and it was not found in file or git-tag.")
|
||||
throw CliClientError.semVarNotFound
|
||||
}
|
||||
|
||||
// Semvar doesn't exist, so create a new one.
|
||||
logger.trace("Generating new semvar.")
|
||||
return try await .semvar(
|
||||
applyingPreRelease(.init(), gitDirectory),
|
||||
usesOptionalType: usesOptionalType ?? false,
|
||||
hasChanges: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
guard let branch else {
|
||||
guard let semvar else {
|
||||
throw ConfigurationParsingError.versionStrategyError(
|
||||
message: "Neither branch nor semvar set on configuration."
|
||||
)
|
||||
}
|
||||
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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Configuration.Target {
|
||||
func url(gitDirectory: String?) throws -> URL {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let filePath: String
|
||||
|
||||
if let path {
|
||||
filePath = path
|
||||
} else {
|
||||
guard let module else {
|
||||
throw ConfigurationParsingError.pathOrModuleNotSet
|
||||
}
|
||||
|
||||
var path = module.name
|
||||
logger.debug("module.name: \(path)")
|
||||
|
||||
if path.hasPrefix("./") {
|
||||
path = String(path.dropFirst(2))
|
||||
}
|
||||
|
||||
if !path.hasPrefix("Sources") {
|
||||
logger.debug("no prefix")
|
||||
path = "Sources/\(path)"
|
||||
}
|
||||
|
||||
filePath = "\(path)/\(module.fileNameOrDefault)"
|
||||
}
|
||||
|
||||
if let gitDirectory {
|
||||
return URL(filePath: "\(gitDirectory)/\(filePath)")
|
||||
}
|
||||
return URL(filePath: filePath)
|
||||
}
|
||||
}
|
||||
|
||||
private extension GitClient {
|
||||
func version(includeCommitSha: Bool, gitDirectory: String?) async throws -> String {
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
|
||||
return try await gitClient.version(.init(
|
||||
gitDirectory: gitDirectory,
|
||||
style: .branch(commitSha: includeCommitSha)
|
||||
)).description
|
||||
}
|
||||
}
|
||||
|
||||
private extension Configuration.PreRelease {
|
||||
|
||||
func preReleaseString(gitDirectory: String?) async throws -> PreReleaseString? {
|
||||
guard let strategy else { return nil }
|
||||
|
||||
@Dependency(\.asyncShellClient) var asyncShellClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
var preReleaseString: String
|
||||
var suffix = true
|
||||
var allowsPrefix = true
|
||||
|
||||
switch strategy {
|
||||
case let .branch(includeCommitSha: includeCommitSha):
|
||||
logger.trace("Branch pre-release strategy, includeCommitSha: \(includeCommitSha).")
|
||||
preReleaseString = try await gitClient.version(
|
||||
includeCommitSha: includeCommitSha,
|
||||
gitDirectory: gitDirectory
|
||||
)
|
||||
case let .command(arguments: arguments, allowPrefix: allowPrefix):
|
||||
logger.trace("Command pre-release strategy, arguments: \(arguments).")
|
||||
preReleaseString = try await asyncShellClient.background(.init(arguments))
|
||||
allowsPrefix = allowPrefix ?? false
|
||||
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(
|
||||
gitDirectory: gitDirectory,
|
||||
style: .tag(exactMatch: false)
|
||||
)).description
|
||||
}
|
||||
|
||||
if let prefix, allowsPrefix {
|
||||
preReleaseString = "\(prefix)-\(preReleaseString)"
|
||||
}
|
||||
|
||||
guard suffix else { return .semvar(preReleaseString) }
|
||||
return .suffix(preReleaseString)
|
||||
}
|
||||
|
||||
enum PreReleaseString: Sendable {
|
||||
case suffix(String)
|
||||
case semvar(String)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Configuration.SemVar {
|
||||
|
||||
func applyingPreRelease(_ semvar: SemVar, _ gitDirectory: String?) async throws -> SemVar {
|
||||
@Dependency(\.logger) var logger
|
||||
logger.trace("Start apply pre-release to: \(semvar)")
|
||||
|
||||
guard let preReleaseStrategy = self.preRelease,
|
||||
let preRelease = try await preReleaseStrategy.preReleaseString(gitDirectory: gitDirectory)
|
||||
else {
|
||||
logger.trace("No pre-release strategy, returning original semvar.")
|
||||
return semvar
|
||||
}
|
||||
|
||||
logger.trace("Pre-release string: \(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)
|
||||
}
|
||||
if let prefix = self.preRelease?.prefix {
|
||||
var prefixString = prefix
|
||||
if let preReleaseString = semvar.preRelease {
|
||||
prefixString = "\(prefix)-\(preReleaseString)"
|
||||
}
|
||||
return semvar.applyingPreRelease(prefixString)
|
||||
}
|
||||
return semvar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ConfigurationParsingError: Error {
|
||||
case targetNotFound
|
||||
case pathOrModuleNotSet
|
||||
case versionStrategyError(message: String)
|
||||
case versionStrategyNotFound
|
||||
}
|
||||
@@ -5,49 +5,40 @@ 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)
|
||||
projectDirectory: String?,
|
||||
requireExistingFile: Bool
|
||||
) async throws -> (string: String, usesOptionalType: Bool)? {
|
||||
let loaded = try? await getVersionString(fileUrl: file, projectDirectory: projectDirectory)
|
||||
guard let loaded else {
|
||||
if requireExistingFile {
|
||||
throw CliClientError.fileDoesNotExist(path: file.cleanFilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return (loaded.0, loaded.1)
|
||||
}
|
||||
|
||||
// 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)
|
||||
projectDirectory: String?,
|
||||
requireExistingFile: Bool
|
||||
) async throws -> (semVar: SemVar?, usesOptionalType: Bool)? {
|
||||
let loaded = try? await getVersionString(fileUrl: file, projectDirectory: projectDirectory)
|
||||
guard let loaded else {
|
||||
if requireExistingFile {
|
||||
throw CliClientError.fileDoesNotExist(path: file.cleanFilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
let semvar = SemVar(string: loaded.0)
|
||||
return (semvar, loaded.1)
|
||||
}
|
||||
|
||||
private func getVersionString(
|
||||
fileUrl: URL,
|
||||
gitDirectory: String?
|
||||
projectDirectory: String?
|
||||
) async throws -> (version: String, usesOptionalType: Bool) {
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
90
Sources/CliClient/Internal/Semvar+nextVersion.swift
Normal file
90
Sources/CliClient/Internal/Semvar+nextVersion.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
import ConfigurationClient
|
||||
import Foundation
|
||||
import GitClient
|
||||
import LoggingExtensions
|
||||
import ShellClient
|
||||
|
||||
extension SemVar {
|
||||
static func nextVersion(
|
||||
configuration: Configuration.SemVar,
|
||||
projectDirectory: String?
|
||||
) async throws -> Self? {
|
||||
@Dependency(\.asyncShellClient) var asyncShellClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
guard let strategy = configuration.strategy else { return nil }
|
||||
|
||||
let semvarString: String
|
||||
|
||||
switch strategy {
|
||||
case let .gitTag(exactMatch: exactMatch):
|
||||
logger.trace("Loading semvar gitTag strategy...")
|
||||
|
||||
semvarString = try await gitClient.version(.init(
|
||||
gitDirectory: projectDirectory,
|
||||
style: .tag(exactMatch: exactMatch ?? false)
|
||||
)).description
|
||||
|
||||
case let .command(arguments: arguments):
|
||||
logger.trace("Loading semvar custom command strategy: \(arguments)")
|
||||
semvarString = try await asyncShellClient.background(.init(arguments))
|
||||
}
|
||||
|
||||
var preReleaseString: String?
|
||||
if let preRelease = configuration.preRelease,
|
||||
configuration.allowPreRelease ?? true
|
||||
{
|
||||
preReleaseString = try await preRelease.get(projectDirectory: projectDirectory)
|
||||
}
|
||||
|
||||
let semvar = SemVar(string: semvarString)
|
||||
|
||||
if let preReleaseString {
|
||||
return semvar?.applyingPreRelease(preReleaseString)
|
||||
}
|
||||
|
||||
return semvar
|
||||
}
|
||||
}
|
||||
|
||||
private extension Configuration.PreRelease {
|
||||
|
||||
func get(projectDirectory: String?) async throws -> String? {
|
||||
@Dependency(\.asyncShellClient) var asyncShellClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
var allowsPrefix = true
|
||||
var preReleaseString: String
|
||||
|
||||
guard let strategy else { return nil }
|
||||
switch strategy {
|
||||
case let .branch(includeCommitSha: includeCommitSha):
|
||||
logger.trace("Loading pre-relase branch strategy...")
|
||||
preReleaseString = try await gitClient.version(.init(
|
||||
gitDirectory: projectDirectory,
|
||||
style: .branch(commitSha: includeCommitSha)
|
||||
)).description
|
||||
|
||||
case .gitTag:
|
||||
logger.trace("Loading pre-relase gitTag strategy...")
|
||||
preReleaseString = try await gitClient.version(.init(
|
||||
gitDirectory: projectDirectory,
|
||||
style: .tag(exactMatch: false)
|
||||
)).description
|
||||
|
||||
case let .command(arguments: arguments, allowPrefix: allowPrefix):
|
||||
logger.trace("Loading pre-relase custom command strategy...")
|
||||
allowsPrefix = allowPrefix ?? false
|
||||
preReleaseString = try await asyncShellClient.background(.init(arguments))
|
||||
}
|
||||
|
||||
if let prefix, allowsPrefix {
|
||||
preReleaseString = "\(prefix)-\(preReleaseString)"
|
||||
}
|
||||
|
||||
logger.trace("Pre-release string: \(preReleaseString)")
|
||||
return preReleaseString
|
||||
}
|
||||
}
|
||||
172
Sources/CliClient/Internal/VersionContainer.swift
Normal file
172
Sources/CliClient/Internal/VersionContainer.swift
Normal file
@@ -0,0 +1,172 @@
|
||||
import ConfigurationClient
|
||||
import CustomDump
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
import LoggingExtensions
|
||||
|
||||
enum VersionContainer: Sendable {
|
||||
case branch(CurrentVersionContainer<String>)
|
||||
case semvar(CurrentVersionContainer<SemVar>)
|
||||
|
||||
static func load(
|
||||
projectDirectory: String?,
|
||||
strategy: Configuration.VersionStrategy,
|
||||
url: URL
|
||||
) async throws -> Self {
|
||||
switch strategy {
|
||||
case let .branch(includeCommitSha: includeCommitSha):
|
||||
return try await .branch(.load(
|
||||
branch: .init(includeCommitSha: includeCommitSha),
|
||||
projectDirectory: projectDirectory,
|
||||
url: url
|
||||
))
|
||||
case .semvar:
|
||||
return try await .semvar(.load(
|
||||
semvar: strategy.semvar!,
|
||||
projectDirectory: projectDirectory,
|
||||
url: url
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add a precedence field for which version to prefer, should also be specified in
|
||||
// configuration.
|
||||
struct CurrentVersionContainer<Version> {
|
||||
let targetUrl: URL
|
||||
let usesOptionalType: Bool
|
||||
let loadedVersion: Version?
|
||||
let precedence: Configuration.SemVar.Precedence?
|
||||
let strategyVersion: Version?
|
||||
}
|
||||
|
||||
extension CurrentVersionContainer: Equatable where Version: Equatable {
|
||||
|
||||
var hasChanges: Bool {
|
||||
switch (loadedVersion, strategyVersion) {
|
||||
case (.none, .none):
|
||||
return false
|
||||
case (.some, .none),
|
||||
(.none, .some):
|
||||
return true
|
||||
case let (.some(loaded), .some(next)):
|
||||
return loaded == next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentVersionContainer: Sendable where Version: Sendable {}
|
||||
|
||||
extension CurrentVersionContainer where Version == String {
|
||||
|
||||
static func load(
|
||||
branch: Configuration.Branch,
|
||||
projectDirectory: String?,
|
||||
url: URL
|
||||
) async throws -> Self {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.gitClient) var gitClient
|
||||
|
||||
let loaded = try await fileClient.branch(
|
||||
file: url,
|
||||
projectDirectory: projectDirectory,
|
||||
requireExistingFile: false
|
||||
)
|
||||
|
||||
let next = try await gitClient.version(.init(
|
||||
gitDirectory: projectDirectory,
|
||||
style: .branch(commitSha: branch.includeCommitSha)
|
||||
))
|
||||
|
||||
return .init(
|
||||
targetUrl: url,
|
||||
usesOptionalType: loaded?.1 ?? true,
|
||||
loadedVersion: loaded?.0,
|
||||
precedence: nil,
|
||||
strategyVersion: next.description
|
||||
)
|
||||
}
|
||||
|
||||
var versionString: String? {
|
||||
loadedVersion ?? strategyVersion
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentVersionContainer where Version == SemVar {
|
||||
|
||||
// TODO: Update to use precedence and not fetch `nextVersion` if we loaded a file version.
|
||||
static func load(semvar: Configuration.SemVar, projectDirectory: String?, url: URL) async throws -> Self {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
logger.trace("Begin loading semvar from: \(url.cleanFilePath)")
|
||||
|
||||
async let (loaded, usesOptionalType) = try await loadCurrentVersion(
|
||||
semvar: semvar,
|
||||
projectDirectory: projectDirectory,
|
||||
url: url
|
||||
)
|
||||
async let next = try await loadNextVersion(semvar: semvar, projectDirectory: projectDirectory)
|
||||
|
||||
return try await .init(
|
||||
targetUrl: url,
|
||||
usesOptionalType: usesOptionalType,
|
||||
loadedVersion: loaded,
|
||||
precedence: semvar.precedence,
|
||||
strategyVersion: next
|
||||
)
|
||||
}
|
||||
|
||||
static func loadCurrentVersion(
|
||||
semvar: Configuration.SemVar,
|
||||
projectDirectory: String?,
|
||||
url: URL
|
||||
) async throws -> (SemVar?, Bool) {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
logger.trace("Begin loading current version from: \(url.cleanFilePath)")
|
||||
|
||||
let loadedOptional = try await fileClient.semvar(
|
||||
file: url,
|
||||
projectDirectory: projectDirectory,
|
||||
requireExistingFile: semvar.requireExistingFile ?? false
|
||||
)
|
||||
guard let loadedStrong = loadedOptional else {
|
||||
if semvar.requireExistingFile ?? false {
|
||||
throw CliClientError.semVarNotFound(message: "Required by configuration's 'requireExistingFile' variable.")
|
||||
}
|
||||
return (nil, true)
|
||||
}
|
||||
|
||||
let (loaded, usesOptionalType) = loadedStrong
|
||||
|
||||
logger.dump(loaded) { "Loaded version:\n\($0)" }
|
||||
return (loaded, usesOptionalType)
|
||||
}
|
||||
|
||||
static func loadNextVersion(semvar: Configuration.SemVar, projectDirectory: String?) async throws -> SemVar? {
|
||||
@Dependency(\.logger) var logger
|
||||
let next = try await SemVar.nextVersion(
|
||||
configuration: semvar,
|
||||
projectDirectory: projectDirectory
|
||||
)
|
||||
logger.dump(next) { "Next version:\n\($0)" }
|
||||
return next
|
||||
}
|
||||
|
||||
func versionString(withPreRelease: Bool) -> String? {
|
||||
let version: SemVar?
|
||||
|
||||
switch precedence ?? .default {
|
||||
case .file:
|
||||
version = loadedVersion ?? strategyVersion
|
||||
case .strategy:
|
||||
version = strategyVersion ?? loadedVersion
|
||||
}
|
||||
|
||||
return version?.versionString(withPreReleaseTag: withPreRelease)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
import LoggingExtensions
|
||||
|
||||
@_spi(Internal)
|
||||
public extension Configuration {
|
||||
@@ -44,8 +45,12 @@ public extension Configuration.Branch {
|
||||
@_spi(Internal)
|
||||
public extension Configuration.SemVar {
|
||||
func merging(_ other: Self?) -> Self {
|
||||
.init(
|
||||
@Dependency(\.logger) var logger
|
||||
logger.dump(other, level: .trace) { "Merging semvar:\n\($0)" }
|
||||
|
||||
return .init(
|
||||
allowPreRelease: other?.allowPreRelease ?? allowPreRelease,
|
||||
precedence: other?.precedence ?? precedence,
|
||||
preRelease: preRelease == nil ? other?.preRelease : preRelease!.merging(other?.preRelease),
|
||||
requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
|
||||
requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar,
|
||||
|
||||
@@ -145,6 +145,7 @@ public extension Configuration {
|
||||
|
||||
case semvar(
|
||||
allowPreRelease: Bool? = nil,
|
||||
precedence: SemVar.Precedence? = nil,
|
||||
preRelease: PreRelease? = nil,
|
||||
requireExistingFile: Bool? = nil,
|
||||
requireExistingSemVar: Bool? = nil,
|
||||
@@ -159,10 +160,16 @@ public extension Configuration {
|
||||
}
|
||||
|
||||
public var semvar: SemVar? {
|
||||
guard case let .semvar(allowPreRelease, preRelease, requireExistingFile, requireExistingSemVar, strategy) = self
|
||||
guard case let .semvar(
|
||||
allowPreRelease,
|
||||
precedence,
|
||||
preRelease,
|
||||
requireExistingFile, requireExistingSemVar, strategy
|
||||
) = self
|
||||
else { return nil }
|
||||
return .init(
|
||||
allowPreRelease: allowPreRelease,
|
||||
precedence: precedence,
|
||||
preRelease: preRelease,
|
||||
requireExistingFile: requireExistingFile ?? false,
|
||||
requireExistingSemVar: requireExistingSemVar ?? false,
|
||||
@@ -177,6 +184,7 @@ public extension Configuration {
|
||||
public static func semvar(_ value: SemVar) -> Self {
|
||||
.semvar(
|
||||
allowPreRelease: value.allowPreRelease,
|
||||
precedence: value.precedence,
|
||||
preRelease: value.preRelease,
|
||||
requireExistingFile: value.requireExistingFile,
|
||||
requireExistingSemVar: value.requireExistingSemVar,
|
||||
@@ -249,8 +257,17 @@ public extension Configuration {
|
||||
///
|
||||
struct SemVar: Codable, Equatable, Sendable {
|
||||
|
||||
/// Allow semvar to include a pre-release suffix.
|
||||
public let allowPreRelease: Bool?
|
||||
|
||||
/// Set the precedence of version loaded from file versus
|
||||
/// the version returned by the strategy.
|
||||
///
|
||||
/// These can not always agree / reflect the same version,
|
||||
/// so the default is to give the file version precedence over
|
||||
/// the strategy.
|
||||
public let precedence: Precedence?
|
||||
|
||||
/// Optional pre-releas suffix strategy.
|
||||
public let preRelease: PreRelease?
|
||||
|
||||
@@ -260,27 +277,53 @@ public extension Configuration {
|
||||
/// Fail if an existing semvar is not parsed from the file or version generation strategy.
|
||||
public let requireExistingSemVar: Bool?
|
||||
|
||||
/// The strategy used to derive a version for the project.
|
||||
public let strategy: Strategy?
|
||||
|
||||
public init(
|
||||
allowPreRelease: Bool? = true,
|
||||
precedence: Precedence? = nil,
|
||||
preRelease: PreRelease? = nil,
|
||||
requireExistingFile: Bool? = false,
|
||||
requireExistingSemVar: Bool? = false,
|
||||
strategy: Strategy? = nil
|
||||
) {
|
||||
self.allowPreRelease = allowPreRelease
|
||||
self.precedence = precedence
|
||||
self.preRelease = preRelease
|
||||
self.requireExistingFile = requireExistingFile
|
||||
self.requireExistingSemVar = requireExistingSemVar
|
||||
self.strategy = strategy
|
||||
}
|
||||
|
||||
/// Represents a strategy to derive a version for a project.
|
||||
public enum Strategy: Codable, Equatable, Sendable {
|
||||
/// A custom external command that should return a string that
|
||||
/// can be parsed as a semvar.
|
||||
case command(arguments: [String])
|
||||
|
||||
/// Use `git describe --tags` optionally as an exact match.
|
||||
case gitTag(exactMatch: Bool? = false)
|
||||
}
|
||||
|
||||
/// Represents the precedence for which version to use when a file
|
||||
/// exists, as they don't always agree. For example, a file could be edited
|
||||
/// manually or the tag doesn't represent what is parsed from calling the
|
||||
/// strategy.
|
||||
///
|
||||
/// The default is to defer to the file (if it exists) as having precedence over
|
||||
/// the strategy.
|
||||
public enum Precedence: String, CaseIterable, Codable, Equatable, Sendable {
|
||||
|
||||
/// Give the file precedence over the strategy.
|
||||
case file
|
||||
/// Give the strategy precedence over the file.
|
||||
case strategy
|
||||
|
||||
/// The default precedence.
|
||||
public static var `default`: Self { .file }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import CustomDump
|
||||
import Dependencies
|
||||
import Logging
|
||||
import ShellClient
|
||||
|
||||
public struct LoggingOptions: Equatable, Sendable {
|
||||
@@ -26,3 +28,15 @@ public struct LoggingOptions: Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Logger {
|
||||
func dump<T>(
|
||||
_ type: T,
|
||||
level: Level = .trace,
|
||||
buildMessage: @escaping (String) -> String = { $0 }
|
||||
) {
|
||||
var message = ""
|
||||
customDump(type, to: &message)
|
||||
log(level: level, "\(buildMessage(message))")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@ struct CliClientTests {
|
||||
arguments: TestArguments.testCases
|
||||
)
|
||||
func testBuild(target: String) async throws {
|
||||
let template = Template.build("1.0.0")
|
||||
try await run {
|
||||
$0.fileClient.fileExists = { _ in true }
|
||||
$0.fileClient.read = { @Sendable _ in template }
|
||||
} operation: {
|
||||
@Dependency(\.cliClient) var client
|
||||
|
||||
let output = try await client.build(.testOptions(
|
||||
target: target,
|
||||
versionStrategy: .semvar(requireExistingFile: false)
|
||||
|
||||
Reference in New Issue
Block a user