feat: Adds git client tests, restructures files.
Some checks failed
CI / macOS (debug, 16.1) (push) Has been cancelled
CI / macOS (release, 16.1) (push) Has been cancelled
CI / Ubuntu (push) Failing after 5s

This commit is contained in:
2024-12-24 14:42:28 -05:00
parent 8aa4b73cab
commit 1972260317
26 changed files with 454 additions and 397 deletions

View File

@@ -1,6 +1,6 @@
PLATFORM_MACOS = macOS
CONFIG := debug
DOCC_TARGET ?= CliVersion
DOCC_TARGET ?= CliClient
DOCC_BASEPATH = $(shell basename "$(PWD)")
DOCC_DIR ?= ./docs
SWIFT_VERSION ?= "5.10"

View File

@@ -1,5 +1,5 @@
{
"originHash" : "077fe473b2dff48184d79b6897170a2c87f00a465e6c079889de37e6470001fb",
"originHash" : "6ab0a9c883cfa1490d249a344074ad27369033fab78e1a90272ef07339a8c0ab",
"pins" : [
{
"identity" : "combine-schedulers",
@@ -105,8 +105,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/m-housh/swift-shell-client.git",
"state" : {
"revision" : "ea819f41b87aa94e792f028a031f5db786e79a94",
"version" : "0.2.1"
"revision" : "d67f693f92428ef8adecb48c2b26ccac0d2f98cb",
"version" : "0.2.2"
}
},
{

View File

@@ -18,7 +18,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.2"),
.package(url: "https://github.com/m-housh/swift-shell-client.git", from: "0.2.0"),
.package(url: "https://github.com/m-housh/swift-shell-client.git", from: "0.2.2"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"),
@@ -75,6 +75,10 @@ let package = Package(
.product(name: "ShellClient", package: "swift-shell-client")
]
),
.testTarget(
name: "GitClientTests",
dependencies: ["GitClient"]
),
.target(name: "TestSupport"),
.plugin(
name: "BuildWithVersionPlugin",

View File

@@ -27,6 +27,9 @@ public struct CliClient: Sendable {
/// Generate a version file with an optional version that can be set manually.
public var generate: @Sendable (SharedOptions) async throws -> String
/// Parse the configuration options.
public var parsedConfiguration: @Sendable (SharedOptions) async throws -> Configuration
public enum BumpOption: Sendable, CaseIterable {
case major, minor, patch, preRelease
}
@@ -94,7 +97,10 @@ extension CliClient: DependencyKey {
.init(
build: { try await $0.build(environment) },
bump: { try await $1.bump($0) },
generate: { try await $0.generate() }
generate: { try await $0.generate() },
parsedConfiguration: { options in
try await options.withMergedConfiguration { $0 }
}
)
}

View File

@@ -7,19 +7,19 @@ Learn how to integrate the plugins into your project
Use the plugins by including as a package to your project and declaring in the `plugins` section of
your target.
> Note: You must use swift-tools version 5.6 or greater for package plugins and
> target `macOS(.v10_15)` or greater.
> Note: You must use swift-tools version 5.6 or greater for package plugins and target `macOS(.v13)`
> or greater.
```swift
// swift-tools-version: 5.7
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
platforms:[.macOS(.v10_15)],
platforms:[.macOS(.v13)],
dependencies: [
...,
.package(url: "https://github.com/m-housh/swift-cli-version.git", from: "0.1.0")
.package(url: "https://github.com/m-housh/swift-cli-version.git", from: "0.2.0")
],
targets: [
.executableTarget(
@@ -33,8 +33,8 @@ let package = Package(
)
```
The above example uses the build tool plugin. The `BuildWithVersionPlugin` will give you access
to a `VERSION` variable in your project that you can use to supply the version of the tool.
The above example uses the build tool plugin. The `BuildWithVersionPlugin` will give you access to a
`VERSION` variable in your project that you can use to supply the version of the tool.
### Example
@@ -60,9 +60,9 @@ not declared in your source files.
![Trust & Enable](trust)
> Note: If your `DerivedData` folder lives in a directory that is a mounted volume / or somewhere
> that is not under your home folder then you may get build failures using the build tool
> plugin, it will work if you build from the command line and pass the `--disable-sandbox` flag to the
> build command or use one of the manual methods.
> that is not under your home folder then you may get build failures using the build tool plugin, it
> will work if you build from the command line and pass the `--disable-sandbox` flag to the build
> command or use one of the manual methods.
## See Also

View File

@@ -1,6 +1,6 @@
# ``CliVersion``
# CliClient
Derive a version for a command line tool from git tags or a git sha.
Derive a version for a command-line tool from git tags or a git sha.
## Additional Resources
@@ -9,15 +9,10 @@ Derive a version for a command line tool from git tags or a git sha.
## Overview
This tool exposes several plugins that can be used to derive a version for a command line program at
build time or by manually running the plugin. The version is derived from git tags and falling back to
the branch and git sha if a tag is not set for the current worktree state.
build time or by manually running the plugin. The version is derived from git tags and falling back
to the branch and git sha if a tag is not set for the current worktree state.
## Articles
- <doc:GettingStarted>
- <doc:ManualPlugins>
## Api
- ``FileClient``
- ``GitVersionClient``

View File

@@ -7,18 +7,12 @@ import GitClient
@_spi(Internal)
public extension CliClient.SharedOptions {
// Merges any configuration set via the passed in options.
@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.
func withMergedConfiguration<T>(
operation: (Configuration) async throws -> T
) async throws -> T {
try await withConfiguration(path: configurationFile) { configuration in
@Dependency(\.logger) var logger
// Merge any configuration set from caller into default configuration.
var configuration = configuration
configuration = configuration.mergingTarget(target)
@@ -28,6 +22,21 @@ public extension CliClient.SharedOptions {
configuration = configuration.mergingStrategy(.semvar(semvar))
}
return try await operation(configuration)
}
}
@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.

View File

@@ -21,9 +21,11 @@ public struct Configuration: Codable, Equatable, Sendable {
self.strategy = strategy
}
public static var mock: Self {
public static var `default`: Self { .mock(module: "<my-tool>") }
public static func mock(module: String = "cli-version") -> Self {
.init(
target: .init(module: .init("cli-version")),
target: .init(module: .init(module)),
strategy: .semvar(.init())
)
}

View File

@@ -30,7 +30,7 @@ public struct ConfigurationClient: Sendable {
guard let url = try? await find(url) else {
throw ConfigurationClientError.configurationNotFound
}
return (try? await load(url)) ?? .mock
return (try? await load(url)) ?? .default
}
}

View File

@@ -2,16 +2,16 @@ import ArgumentParser
import Foundation
@main
struct CliVersionCommand: AsyncParsableCommand {
struct Application: AsyncParsableCommand {
static let configuration: CommandConfiguration = .init(
commandName: "cli-version",
commandName: "bump-version",
version: VERSION ?? "0.0.0",
subcommands: [
Build.self,
Bump.self,
Generate.self,
BuildCommand.self,
BumpCommand.self,
GenerateCommand.self,
UtilsCommand.self
],
defaultSubcommand: Bump.self
defaultSubcommand: BumpCommand.self
)
}

View File

@@ -1,19 +0,0 @@
import ArgumentParser
import CliClient
import Foundation
import ShellClient
extension CliVersionCommand {
struct Build: AsyncParsableCommand {
static let configuration: CommandConfiguration = .init(
abstract: "Used for the build with version plugin.",
discussion: "This should generally not be interacted with directly, outside of the build plugin."
)
@OptionGroup var globals: GlobalOptions
func run() async throws {
try await globals.shared().run(\.build)
}
}
}

View File

@@ -1,21 +0,0 @@
import ArgumentParser
import CliClient
import Dependencies
extension CliVersionCommand {
struct Bump: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "bump",
abstract: "Bump version of a command-line tool."
)
@OptionGroup var globals: GlobalOptions
func run() async throws {
try await globals.shared().run(\.bump, args: nil)
}
}
}
extension CliClient.BumpOption: EnumerableFlag {}

View File

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

View File

@@ -0,0 +1,26 @@
import ArgumentParser
import CliClient
import Dependencies
struct BumpCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "bump",
abstract: "Bump version of a command-line tool."
)
@OptionGroup var globals: GlobalOptions
@Flag(
help: """
The semvar bump option, this is ignored if the configuration is set to use a branch/commit sha strategy.
"""
)
var bumpOption: CliClient.BumpOption = .patch
func run() async throws {
try await globals.run(\.bump, args: bumpOption)
}
}
extension CliClient.BumpOption: EnumerableFlag {}

View File

@@ -0,0 +1,19 @@
import ArgumentParser
import CliClient
import Dependencies
import Foundation
import ShellClient
struct GenerateCommand: AsyncParsableCommand {
static let configuration: CommandConfiguration = .init(
commandName: "generate",
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."
)
@OptionGroup var globals: GlobalOptions
func run() async throws {
try await globals.run(\.generate)
}
}

View File

@@ -0,0 +1,95 @@
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 configuration = CommandConfiguration(
commandName: "dump-config",
abstract: "Show the parsed configuration.",
aliases: ["dc"]
)
@OptionGroup var globals: GlobalOptions
func run() async throws {
let configuration = try await globals.runClient(\.parsedConfiguration)
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
}
}

View File

@@ -1,61 +0,0 @@
// import ConfigurationClient
// import Dependencies
// import FileClient
// import Foundation
//
// extension Configuration {
//
// func mergingTarget(_ otherTarget: Configuration.Target?) -> Self {
// .init(
// target: otherTarget ?? target,
// strategy: strategy
// )
// }
//
// func mergingStrategy(_ otherStrategy: Configuration.VersionStrategy?) -> Self {
// .init(
// target: target,
// strategy: strategy?.merging(otherStrategy)
// )
// }
// }
//
// extension Configuration.Branch {
// func merging(_ other: Self?) -> Self {
// return .init(includeCommitSha: other?.includeCommitSha ?? includeCommitSha)
// }
// }
//
// extension Configuration.SemVar {
// func merging(_ other: Self?) -> Self {
// .init(
// preRelease: other?.preRelease ?? preRelease,
// requireExistingFile: other?.requireExistingFile ?? requireExistingFile,
// requireExistingSemVar: other?.requireExistingSemVar ?? requireExistingSemVar
// )
// }
// }
//
// extension Configuration.VersionStrategy {
// func merging(_ other: Self?) -> Self {
// guard let branch else {
// guard let semvar else { return self }
// return .semvar(semvar.merging(other?.semvar))
// }
// return .branch(branch.merging(other?.branch))
// }
// }
//
// @discardableResult
// func withConfiguration<T>(
// path: String?,
// _ operation: (Configuration) async throws -> T
// ) async throws -> T {
// @Dependency(\.configurationClient) var configurationClient
//
// let configuration = try await configurationClient.findAndLoad(
// path != nil ? URL(filePath: path!) : nil
// )
//
// return try await operation(configuration)
// }

View File

@@ -0,0 +1,5 @@
# Application
## Articles
- <doc:Installation>

View File

@@ -0,0 +1,3 @@
# Installation
You can install the command-line application by...

View File

@@ -1,20 +0,0 @@
import ArgumentParser
import CliClient
import Dependencies
import Foundation
import ShellClient
extension CliVersionCommand {
struct Generate: AsyncParsableCommand {
static let configuration: CommandConfiguration = .init(
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."
)
@OptionGroup var globals: GlobalOptions
func run() async throws {
try await globals.shared().run(\.generate)
}
}
}

View File

@@ -7,24 +7,8 @@ import Rainbow
struct GlobalOptions: ParsableArguments {
@Option(
name: .shortAndLong,
help: "Specify the path to a configuration file."
)
var configurationFile: String?
@OptionGroup var targetOptions: TargetOptions
@OptionGroup var semvarOptions: SemVarOptions
@Flag(
name: .long,
inversion: .prefixedNo,
help: """
Include the short commit sha in version or pre-release branch style output.
"""
)
var commitSha: Bool = true
@OptionGroup
var configOptions: ConfigurationOptions
@Option(
name: .customLong("git-directory"),
@@ -44,6 +28,37 @@ struct GlobalOptions: ParsableArguments {
)
var verbose: Int
@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] = []
}
struct ConfigurationOptions: ParsableArguments {
@Option(
name: .shortAndLong,
help: "Specify the path to a configuration file.",
completion: .file(extensions: ["json"])
)
var configurationFile: String?
@OptionGroup var targetOptions: TargetOptions
@OptionGroup var semvarOptions: SemVarOptions
@Flag(
name: .long,
inversion: .prefixedNo,
help: """
Include the short commit sha in version or pre-release branch style output.
"""
)
var commitSha: Bool = true
}
struct TargetOptions: ParsableArguments {
@@ -67,8 +82,6 @@ struct TargetOptions: ParsableArguments {
}
// TODO: Need to be able to pass in arguments for custom command pre-release option.
struct PreReleaseOptions: ParsableArguments {
@Flag(
@@ -101,14 +114,13 @@ struct PreReleaseOptions: ParsableArguments {
)
var preReleasePrefix: String?
@Option(
@Flag(
name: .long,
help: """
Apply custom pre-release suffix, can also use branch or tag along with this
option as a prefix, used if branch is not set. (example: \"rc\")
Apply custom pre-release suffix, using extra options / arguments passed in after a '--'.
"""
)
var custom: String?
var customPreRelease: Bool = false
}
@@ -130,102 +142,3 @@ struct SemVarOptions: ParsableArguments {
@OptionGroup var preRelease: PreReleaseOptions
}
// TODO: Move these to global options.
extension CliClient.SharedOptions {
func run(_ keyPath: KeyPath<CliClient, @Sendable (Self) async throws -> String>) async throws {
try await withDependencies {
$0.fileClient = .liveValue
$0.gitClient = .liveValue
$0.cliClient = .liveValue
} operation: {
@Dependency(\.cliClient) var cliClient
let output = try await cliClient[keyPath: keyPath](self)
print(output)
}
}
func run<T>(
_ keyPath: KeyPath<CliClient, @Sendable (T, Self) async throws -> String>,
args: T
) async throws {
try await withDependencies {
$0.fileClient = .liveValue
$0.gitClient = .liveValue
$0.cliClient = .liveValue
} operation: {
@Dependency(\.cliClient) var cliClient
let output = try await cliClient[keyPath: keyPath](args, self)
print(output)
}
}
}
extension GlobalOptions {
func shared() throws -> CliClient.SharedOptions {
try .init(
allowPreReleaseTag: !semvarOptions.preRelease.disablePreRelease,
dryRun: dryRun,
gitDirectory: gitDirectory,
verbose: verbose,
target: targetOptions.configTarget(),
branch: .init(includeCommitSha: commitSha),
semvar: semvarOptions.configSemVarOptions(),
configurationFile: configurationFile
)
}
}
// MARK: - Helpers
private extension TargetOptions {
func configTarget() throws -> Configuration.Target? {
guard let path else {
guard let module else {
return nil
}
return .init(module: .init(module, fileName: fileName))
}
return .init(path: path)
}
}
extension PreReleaseOptions {
// FIX:
func configPreReleaseStrategy() throws -> Configuration.PreRelease? {
return nil
// guard let custom else {
// if useBranchAsPreRelease {
// return .branch()
// } else if useTagAsPreRelease {
// return .gitTag
// } else {
// return nil
// }
// }
//
// if useBranchAsPreRelease {
// return .customBranchPrefix(custom)
// } else if useTagAsPreRelease {
// return .customGitTagPrefix(custom)
// } else {
// return .custom(custom)
// }
}
}
extension SemVarOptions {
func configSemVarOptions() throws -> Configuration.SemVar {
try .init(
preRelease: preRelease.configPreReleaseStrategy(),
requireExistingFile: requireExistingFile,
requireExistingSemVar: requireExistingSemvar
)
}
}

View File

@@ -0,0 +1,123 @@
import CliClient
import ConfigurationClient
import Dependencies
import FileClient
import GitClient
@discardableResult
func withSetupDependencies<T>(
_ operation: () async throws -> T
) async throws -> T {
try await withDependencies {
$0.fileClient = .liveValue
$0.gitClient = .liveValue
$0.cliClient = .liveValue
$0.configurationClient = .liveValue
} operation: {
try await operation()
}
}
extension GlobalOptions {
func runClient<T>(
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> T>
) async throws -> T {
try await withSetupDependencies {
@Dependency(\.cliClient) var cliClient
return try await cliClient[keyPath: keyPath](shared())
}
}
func runClient<A, T>(
_ keyPath: KeyPath<CliClient, @Sendable (A, CliClient.SharedOptions) async throws -> T>,
args: A
) async throws -> T {
try await withSetupDependencies {
@Dependency(\.cliClient) var cliClient
return try await cliClient[keyPath: keyPath](args, shared())
}
}
func run(
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String>
) async throws {
let output = try await runClient(keyPath)
print(output)
}
func run<T>(
_ keyPath: KeyPath<CliClient, @Sendable (T, CliClient.SharedOptions) async throws -> String>,
args: T
) async throws {
let output = try await runClient(keyPath, args: args)
print(output)
}
func shared() throws -> CliClient.SharedOptions {
try .init(
allowPreReleaseTag: !configOptions.semvarOptions.preRelease.disablePreRelease,
dryRun: dryRun,
gitDirectory: gitDirectory,
verbose: verbose,
target: configOptions.target(),
branch: .init(includeCommitSha: configOptions.commitSha),
semvar: configOptions.semvarOptions(extraOptions: extraOptions),
configurationFile: configOptions.configurationFile
)
}
}
private extension TargetOptions {
func configTarget() throws -> Configuration.Target? {
guard let path else {
guard let module else {
return nil
}
return .init(module: .init(module, fileName: fileName))
}
return .init(path: path)
}
}
extension PreReleaseOptions {
func configPreReleaseStrategy(includeCommitSha: Bool, extraOptions: [String]) throws -> Configuration.PreRelease? {
if useBranchAsPreRelease {
return .init(prefix: preReleasePrefix, strategy: .branch(includeCommitSha: includeCommitSha))
} else if useTagAsPreRelease {
return .init(prefix: preReleasePrefix, strategy: .gitTag)
} else if customPreRelease {
guard extraOptions.count > 0 else {
throw ExtraOptionsEmpty()
}
return .init(prefix: preReleasePrefix, strategy: .command(arguments: extraOptions))
}
return nil
}
}
extension SemVarOptions {
func configSemVarOptions(includeCommitSha: Bool, extraOptions: [String]) throws -> Configuration.SemVar {
try .init(
preRelease: preRelease.configPreReleaseStrategy(includeCommitSha: includeCommitSha, extraOptions: extraOptions),
requireExistingFile: requireExistingFile,
requireExistingSemVar: requireExistingSemvar
)
}
}
extension ConfigurationOptions {
func target() throws -> Configuration.Target? {
try targetOptions.configTarget()
}
func semvarOptions(extraOptions: [String]) throws -> Configuration.SemVar {
try semvarOptions.configSemVarOptions(includeCommitSha: commitSha, extraOptions: extraOptions)
}
}
struct ExtraOptionsEmpty: Error {}

View File

@@ -1,48 +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
]
)
}
extension UtilsCommand {
struct DumpConfig: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "dump-config",
abstract: "Show the parsed configuration."
)
@Argument(
help: """
Optional path to the configuration file, if not supplied will search the current directory
""",
completion: .file(extensions: ["toml", "json"])
)
var file: String?
func run() async throws {
try await withDependencies {
$0.fileClient = .liveValue
$0.configurationClient = .liveValue
} operation: {
@Dependency(\.configurationClient) var configurationClient
let configuration = try await configurationClient.findAndLoad(
file != nil ? URL(filePath: file!) : nil
)
customDump(configuration)
}
}
}
}

View File

@@ -97,6 +97,8 @@ struct CliClientTests {
$0.fileClient.fileExists = { _ in false }
$0.gitClient = .mock(.tag("1.0.0"))
$0.cliClient = .liveValue
$0.configurationClient = .liveValue
$0.configurationClient.find = { _ in URL(filePath: "/") }
setupDependencies(&$0)
} operation: {
try await operation()
@@ -140,10 +142,6 @@ extension CliClient.SharedOptions {
target: .init(module: .init(target)),
branch: versionStrategy.branch,
semvar: versionStrategy.semvar
// configuration: .init(
// target: .init(module: .init(target)),
// strategy: versionStrategy
// )
)
}
}

View File

@@ -29,7 +29,7 @@ struct ConfigurationClientTests {
for ext in ["json"] {
let fileUrl = url.appending(path: "test.\(ext)")
let configuration = Configuration.mock
let configuration = Configuration.mock()
try await configurationClient.write(configuration, fileUrl)
let loaded = try await configurationClient.load(fileUrl)
@@ -62,7 +62,7 @@ struct ConfigurationClientTests {
for ext in ["json"] {
let fileUrl = url.appending(path: ".bump-version.\(ext)")
let configuration = Configuration.mock
let configuration = Configuration.mock()
try await configurationClient.write(configuration, fileUrl)
let loaded = try await configurationClient.findAndLoad(fileUrl)
@@ -74,56 +74,6 @@ struct ConfigurationClientTests {
}
}
// @Test
// func writeDefault() async throws {
// try await run {
// @Dependency(\.coders) var coders
// @Dependency(\.configurationClient) var configurationClient
//
// // let configuration = Configuration.customPreRelease
// // try await configurationClient.write(configuration, .json(URL(filePath: ".bump-version.json")))
//
// // let target = Configuration.Target2.path("foo")
// // let target = Configuration.Target2.gitTag
// // let target = Configuration.Target2.branch()
// let target = Configuration2.mock
//
// let encoded = try coders.jsonEncoder().encode(target)
// let url = URL(filePath: ".bump-version.json")
// try encoded.write(to: url)
//
// let data = try Data(contentsOf: url)
// let decoded = try coders.jsonDecoder().decode(Configuration2.self, from: data)
// print(decoded)
// }
// }
// @Test
// func tomlPlayground() throws {
// let jsonEncoder = JSONEncoder()
// let encoder = TOMLEncoder()
// let decoder = TOMLDecoder()
//
// enum TestType: Codable {
// case one
// case hello(Hello)
//
// struct Hello: Codable {
// let value: String
// }
// }
//
// struct TestContainer: Codable {
// let testType: TestType
// }
//
// let sut = TestContainer(testType: .hello(.init(value: "world")))
// let encoded = try encoder.encode(sut)
// print(encoded)
// // let decoded = try decoder.decode(TestContainer.self, from: encoded)
// // #expect(decoded.testType == sut.testType)
// }
func run(
setupDependencies: @escaping (inout DependencyValues) -> Void = { _ in },
operation: () async throws -> Void

View File

@@ -0,0 +1,59 @@
import Dependencies
import GitClient
import ShellClient
import Testing
@Suite("GitClientTests")
struct GitClientTests {
@Test(arguments: GitClientVersionTestArgument.testCases)
func testGitClient(input: GitClientVersionTestArgument) async throws {
let arguments = try await run {
@Dependency(\.gitClient) var gitClient
_ = try await gitClient.version(.init(style: input.style))
}
#expect(arguments == input.expected)
}
func run(
_ operation: () async throws -> Void
) async throws -> [[String]] {
let captured = CapturedCommand()
try await withDependencies {
$0.asyncShellClient = .capturing(captured)
$0.fileClient = .noop
$0.gitClient = .liveValue
} operation: {
try await operation()
}
return await captured.commands.map(\.arguments)
}
}
struct GitClientVersionTestArgument {
let style: GitClient.CurrentVersionOption.Style
let expected: [[String]]
static let testCases: [Self] = [
.init(
style: .tag(exactMatch: true),
expected: [["git", "describe", "--tags", "--exact-match"]]
),
.init(
style: .tag(exactMatch: false),
expected: [["git", "describe", "--tags"]]
),
.init(
style: .branch(commitSha: false),
expected: [["git", "symbolic-ref", "--quiet", "--short", "HEAD"]]
),
.init(
style: .branch(commitSha: true),
expected: [
["git", "rev-parse", "--short", "HEAD"],
["git", "symbolic-ref", "--quiet", "--short", "HEAD"]
]
)
]
}