feat: Renames some options, adds a require-configuration option that fails if configuration is not found.
All checks were successful
CI / Ubuntu (push) Successful in 2m19s

This commit is contained in:
2024-12-27 16:42:48 -05:00
parent 4420bd428a
commit 20f430fb8f
16 changed files with 133 additions and 154 deletions

View File

@@ -3,13 +3,15 @@
"semvar" : {
"allowPreRelease" : true,
"strategy" : {
"command" : {
"arguments" : [
"my-command",
"--some-option"
]
"gitTag" : {
"exactMatch" : false
}
}
}
},
"target" : {
"module" : {
"name" : "BumpVersion"
}
}
}

View File

@@ -18,7 +18,7 @@ struct BumpCommand: CommandRepresentable {
),
makeExample(
label: "Dry run, just show what the bumped version would be.",
example: "--minor --dry-run"
example: "--minor --print"
)
])
)

View File

@@ -149,8 +149,6 @@ extension ConfigCommand {
}
}
// TODO: Add verbose.
// TODO: Need to be able to generate a branch style config file.
@dynamicMemberLookup
struct ConfigCommandOptions: ParsableArguments {
@@ -210,12 +208,6 @@ private extension ConfigCommand.ConfigCommandOptions {
case .swift:
customDump(configuration)
}
// guard printJson else {
// customDump(configuration)
// return
// }
// try handlePrintJson(configuration)
}
}

View File

@@ -10,7 +10,9 @@ by the module that a `Version.swift` file resides in. It also declares the strat
new versions.
The command-line tool comes with a command to generate the configuration file for you, this should
be ran from the root of your project.
be ran from the root of your project or by specifying the path to write the configuration file to
using the `-f | --configuration-file` option. The below examples assume that you're running in the
root project directory.
```bash
bump-version config generate --target-module my-tool

View File

@@ -39,10 +39,18 @@ are ignored if your configuration or options specify to use a `branch` strategy.
bump-version bump --minor
```
If you want to use the default configuration without generating your own project configuration, then
you can specify the path or module to the bump command. The default configuration will use the
`gitTag` strategy without any pre-release strategy.
```bash
bump-version bump --minor --target-module my-tool
```
Show the output, but don't update the version file.
```bash
bump-version bump --major --dry-run
bump-version bump --major --print
```
### Generate Command
@@ -67,10 +75,9 @@ Generates a configuration file based on the passed in options.
The following options are used to declare strategy used for deriving the version.
| Long | Description |
| -------- | ------------------------------------------------------- |
| -------- | --------------------------------- |
| --branch | Use the branch strategy |
| --semvar | Use the semvar strategy (default) |
| --print | Print the output to stdout instead of generating a file |
##### Generate Configuration Example

View File

@@ -10,9 +10,9 @@ of their usage.
### General Options
| Short | Long | Argument | Description |
| ----- | --------------- | -------- | -------------------------------------------------------------------- |
| N/A | --dry-run | N/A | Perform the command, but don't write any output files |
| N/A | --git-directory | <path> | The path to the root of your project, defaults to current directory |
| ----- | ------------------- | -------- | -------------------------------------------------------------------- |
| N/A | --print | N/A | Perform the command, but don't write any output files |
| N/A | --project-directory | <path> | The path to the root of your project, defaults to current directory |
| -h | --help | N/A | Show help for a command |
| -v | --verbose | N/A | Increase logging level, can be passed multiple times (example: -vvv) |
| N/A | --version | N/A | Show the version of the command line tool |
@@ -30,6 +30,7 @@ of their usage.
| 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 |
#### Pre-Release Options

View File

@@ -13,13 +13,13 @@ struct GlobalOptions: ParsableArguments {
var configOptions: ConfigurationOptions
@Option(
name: .customLong("git-directory"),
help: "The git directory for the version (default: current directory)"
name: .customLong("project-directory"),
help: "The project directory. (default: current directory)"
)
var gitDirectory: String?
var projectDirectory: String?
@Flag(
name: .customLong("dry-run"),
name: .customLong("print"),
help: "Print's what would be written to a target version file."
)
var dryRun: Bool = false
@@ -41,6 +41,7 @@ struct GlobalOptions: ParsableArguments {
}
struct ConfigurationOptions: ParsableArguments {
@Option(
name: [.customShort("f"), .long],
help: "Specify the path to a configuration file. (default: .bump-version.json)",
@@ -61,6 +62,14 @@ struct ConfigurationOptions: ParsableArguments {
)
var commitSha: Bool = true
@Flag(
name: .long,
help: """
Require a configuration file, otherwise fail.
"""
)
var requireConfiguration: Bool = false
}
struct TargetOptions: ParsableArguments {
@@ -126,7 +135,6 @@ struct PreReleaseOptions: ParsableArguments {
}
// TODO: Add custom command strategy.
struct SemVarOptions: ParsableArguments {
@Flag(

View File

@@ -64,7 +64,7 @@ extension GlobalOptions {
command: command,
dryRun: dryRun,
extraOptions: extraOptions,
gitDirectory: gitDirectory,
gitDirectory: projectDirectory,
verbose: verbose
)
}
@@ -129,7 +129,6 @@ extension SemVarOptions {
) throws -> Configuration.SemVar {
@Dependency(\.logger) var logger
// TODO: Update when / if there's an update config command.
if customCommand && preRelease.customPreRelease {
logger.warning("""
Custom pre-release can not be used at same time as custom command.
@@ -167,6 +166,15 @@ extension ConfigurationOptions {
)
}
private func configurationToMerge(extraOptions: [String]) throws -> Configuration {
try .init(
target: target(),
strategy: semvarOptions.gitTag
? .semvar(semvarOptions(extraOptions: extraOptions))
: .branch(includeCommitSha: commitSha)
)
}
func shared(
command: String,
dryRun: Bool = true,
@@ -177,12 +185,11 @@ extension ConfigurationOptions {
try .init(
allowPreReleaseTag: !semvarOptions.preRelease.disablePreRelease,
dryRun: dryRun,
gitDirectory: gitDirectory,
projectDirectory: gitDirectory,
loggingOptions: .init(command: command, verbose: verbose),
target: target(),
branch: semvarOptions.gitTag ? nil : .init(includeCommitSha: commitSha),
semvar: semvarOptions(extraOptions: extraOptions),
configurationFile: configurationFile
configurationToMerge: configurationToMerge(extraOptions: extraOptions),
configurationFile: configurationFile,
requireConfigurationFile: requireConfiguration
)
}
}

View File

@@ -36,35 +36,46 @@ public struct CliClient: Sendable {
case major, minor, patch, preRelease
}
/// Represents options that are used by all the commands.
public struct SharedOptions: Equatable, Sendable {
/// Whether to allow pre-release suffixes.
let allowPreReleaseTag: Bool
/// Flag on if we write to files or not.
let dryRun: Bool
let gitDirectory: String?
/// Specify a path to the project directory.
let projectDirectory: String?
/// The logging options to use.
let loggingOptions: LoggingOptions
let target: Configuration.Target?
let branch: Configuration.Branch?
let semvar: Configuration.SemVar?
/// Configuration that gets merged with the loaded (or default) configuration.
let configurationToMerge: Configuration?
/// Path to the configuration file to load.
let configurationFile: String?
/// Fail if a configuration file is not found.
let requireConfigurationFile: Bool
public init(
allowPreReleaseTag: Bool = true,
dryRun: Bool = false,
gitDirectory: String? = nil,
projectDirectory: String? = nil,
loggingOptions: LoggingOptions,
target: Configuration.Target? = nil,
branch: Configuration.Branch? = nil,
semvar: Configuration.SemVar? = nil,
configurationFile: String? = nil
configurationToMerge: Configuration? = nil,
configurationFile: String? = nil,
requireConfigurationFile: Bool = false
) {
self.allowPreReleaseTag = allowPreReleaseTag
self.dryRun = dryRun
self.gitDirectory = gitDirectory
self.projectDirectory = projectDirectory
self.loggingOptions = loggingOptions
self.target = target
self.branch = branch
self.semvar = semvar
self.configurationFile = configurationFile
self.configurationToMerge = configurationToMerge
self.requireConfigurationFile = requireConfigurationFile
}
}

View File

@@ -37,7 +37,7 @@ to come after the plugin name.
| Option | Description |
| ---------- | --------------------------------------------------------------------- |
| --dry-run | Do not write to any files, but describe where values would be written |
| --print | Do not write to any files, but describe where values would be written |
| --filename | Override the file name to be written in the target directory |
| --verbose | Increase the logging output |
@@ -47,7 +47,7 @@ to come after the plugin name.
swift package \
--allow-writing-to-package-directory \
generate-version \
--dry-run \
--print \
--verbose \
<target>
```

View File

@@ -25,14 +25,14 @@ public extension CliClient.SharedOptions {
logger.trace("\nConfiguration: \(configurationString)")
// This will fail if the target url is not set properly.
let targetUrl = try configuration.targetUrl(gitDirectory: gitDirectory)
let targetUrl = try configuration.targetUrl(gitDirectory: 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: gitDirectory
gitDirectory: projectDirectory
)
)
@@ -50,27 +50,20 @@ public extension CliClient.SharedOptions {
@Dependency(\.configurationClient) var configurationClient
@Dependency(\.logger) var logger
var strategy: Configuration.VersionStrategy?
if let branch {
if configurationToMerge?.strategy?.branch != nil {
logger.trace("Merging branch strategy.")
strategy = .branch(branch)
} else if let semvar {
// strategy = .branch(branch)
} else if let semvar = configurationToMerge?.strategy?.semvar {
logger.trace("Merging semvar strategy.")
var semvarString = ""
customDump(semvar, to: &semvarString)
logger.trace("\(semvarString)")
strategy = .semvar(semvar)
}
let configuration = Configuration(
target: target,
strategy: strategy
)
return try await configurationClient.withConfiguration(
path: configurationFile,
merging: configuration,
merging: configurationToMerge,
strict: requireConfigurationFile,
operation: operation
)
}
@@ -82,7 +75,7 @@ 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")
// logger.info("\n\(string)\n")
}
}
@@ -90,7 +83,11 @@ public extension CliClient.SharedOptions {
@Dependency(\.logger) var logger
let version = try currentVersion.version.string(allowPreReleaseTag: allowPreReleaseTag)
if !dryRun {
logger.debug("Version: \(version)")
} else {
logger.info("Version: \(version)")
}
let template = currentVersion.usesOptionalType ? Template.optional(version) : Template.nonOptional(version)
logger.trace("Template string: \(template)")
@@ -160,6 +157,12 @@ extension CliClient.SharedOptions {
}
logger.debug("Bumped version: \(version)")
if dryRun {
logger.info("Version: \(version)")
return
}
let template = usesOptionalType ? Template.optional(version) : Template.build(version)
try await write(template, to: container.targetUrl)
}

View File

@@ -70,7 +70,6 @@ public extension Configuration {
struct PreRelease: Codable, Equatable, Sendable {
public let prefix: String?
// TODO: Remove optional.
public let strategy: Strategy?
public init(
@@ -84,7 +83,6 @@ public extension Configuration {
public enum Strategy: Codable, Equatable, Sendable {
case branch(includeCommitSha: Bool = true)
case command(arguments: [String])
// TODO: Remove.
case gitTag
public var branch: Branch? {

View File

@@ -29,11 +29,26 @@ public struct ConfigurationClient: Sendable {
public var write: @Sendable (Configuration, URL) async throws -> Void
/// Find a configuration file and load it if found.
public func findAndLoad(_ url: URL? = nil) async throws -> Configuration {
///
/// - Parameters:
/// - url: The optional path to the configuration file.
/// - strict: Fail if a configuration file is not found, otherwise return default configuration.
public func findAndLoad(_ url: URL? = nil, strict: Bool = true) async throws -> Configuration {
guard let url = try? await find(url) else {
if strict {
throw ConfigurationClientError.configurationNotFound
}
return (try? await load(url)) ?? .default
return .default
}
let loaded = try? await load(url)
guard let loaded else {
if strict {
throw ConfigurationClientError.configurationNotFound
}
return .default
}
return loaded
}
/// Loads configuration from the given path, or searches for the default file and loads it.
@@ -47,10 +62,12 @@ public struct ConfigurationClient: Sendable {
public func withConfiguration<T>(
path: String?,
merging other: Configuration? = nil,
strict: Bool = true,
operation: (Configuration) async throws -> T
) async throws -> T {
let configuration = try await findAndLoad(
path != nil ? URL(filePath: path!) : nil
path != nil ? URL(filePath: path!) : nil,
strict: strict
)
return try await operation(configuration.merging(other))
}

View File

@@ -138,11 +138,12 @@ extension CliClient.SharedOptions {
) -> Self {
return .init(
dryRun: dryRun,
gitDirectory: gitDirectory,
projectDirectory: gitDirectory,
loggingOptions: .init(command: "test", verbose: 2),
configurationToMerge: .init(
target: .init(module: .init(target)),
branch: versionStrategy.branch,
semvar: versionStrategy.semvar
strategy: versionStrategy
)
)
}
}

View File

@@ -1,71 +0,0 @@
@_spi(Internal) import CliClient
import Dependencies
import FileClient
import GitClient
import ShellClient
import TestSupport
import XCTest
// TODO: Remove
final class GitVersionTests: XCTestCase {
override func invokeTest() {
withDependencies({
$0.logger.logLevel = .debug
$0.logger = .liveValue
$0.asyncShellClient = .liveValue
$0.gitClient = .liveValue
$0.fileClient = .liveValue
}, operation: {
super.invokeTest()
})
}
var gitDir: String {
URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.cleanFilePath
}
// #if !os(Linux)
// func test_live() async throws {
// @Dependency(\.gitClient) var versionClient: GitClient
//
// let version = try await versionClient.currentVersion(in: gitDir)
// print("VERSION: \(version)")
// // can't really have a predictable result for the live client.
// XCTAssertNotEqual(version, "blob")
// }
// #endif
func test_file_client() async throws {
try await withTemporaryDirectory { tmpDir in
@Dependency(\.fileClient) var fileClient
let filePath = tmpDir.appendingPathComponent("blob.txt")
try await fileClient.write(string: "Blob", to: filePath)
let contents = try await fileClient.read(filePath)
.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(contents, "Blob")
}
}
func test_file_client_with_string_path() async throws {
try await withTemporaryDirectory { tmpDir in
@Dependency(\.fileClient) var fileClient
let filePath = tmpDir.appendingPathComponent("blob.txt")
let fileString = filePath.cleanFilePath
try await fileClient.write(string: "Blob", to: fileString)
let contents = try await fileClient.read(fileString)
.trimmingCharacters(in: .whitespacesAndNewlines)
XCTAssertEqual(contents, "Blob")
}
}
}

View File

@@ -1,4 +1,5 @@
@_spi(Internal) import ConfigurationClient
import CustomDump
import Dependencies
import Foundation
import Testing
@@ -91,8 +92,8 @@ struct ConfigurationClientTests {
let other = Configuration.VersionStrategy.semvar(.init(
allowPreRelease: true,
preRelease: .init(prefix: "foo", strategy: .gitTag),
requireExistingFile: true,
requireExistingSemVar: true,
requireExistingFile: false,
requireExistingSemVar: false,
strategy: .gitTag()
))
let merged = strategy1.merging(other)