feat: Adds git client tests, restructures files.
This commit is contained in:
2
Makefile
2
Makefile
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||

|
||||
|
||||
> 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
|
||||
|
||||
|
||||
@@ -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``
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
19
Sources/cli-version/Commands/BuildCommand.swift
Normal file
19
Sources/cli-version/Commands/BuildCommand.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
26
Sources/cli-version/Commands/BumpCommand.swift
Normal file
26
Sources/cli-version/Commands/BumpCommand.swift
Normal 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 {}
|
||||
19
Sources/cli-version/Commands/GenerateCommand.swift
Normal file
19
Sources/cli-version/Commands/GenerateCommand.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
95
Sources/cli-version/Commands/UtilsCommand.swift
Normal file
95
Sources/cli-version/Commands/UtilsCommand.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
// }
|
||||
5
Sources/cli-version/Documentation.docc/Application.md
Normal file
5
Sources/cli-version/Documentation.docc/Application.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Application
|
||||
|
||||
## Articles
|
||||
|
||||
- <doc:Installation>
|
||||
@@ -0,0 +1,3 @@
|
||||
# Installation
|
||||
|
||||
You can install the command-line application by...
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
123
Sources/cli-version/Helpers/GlobalOptions+run.swift
Normal file
123
Sources/cli-version/Helpers/GlobalOptions+run.swift
Normal 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 {}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// )
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
59
Tests/GitClientTests/GitClientTests.swift
Normal file
59
Tests/GitClientTests/GitClientTests.swift
Normal 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"]
|
||||
]
|
||||
)
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user