From 20f430fb8f5b1b4d0300a67fcf29bf0974b78f5b Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Fri, 27 Dec 2024 16:42:48 -0500 Subject: [PATCH] feat: Renames some options, adds a require-configuration option that fails if configuration is not found. --- .bump-version.json | 12 ++-- .../BumpVersion/Commands/BumpCommand.swift | 2 +- .../BumpVersion/Commands/ConfigCommand.swift | 8 --- .../Articles/BasicConfiguration.md | 4 +- .../Articles/CommandReference.md | 19 +++-- .../Articles/OptionsReference.md | 15 ++-- Sources/BumpVersion/GlobalOptions.swift | 18 +++-- .../Helpers/GlobalOptions+run.swift | 21 ++++-- Sources/CliClient/CliClient.swift | 37 ++++++---- .../Articles/ManualPlugins.md | 4 +- .../CliClient/Internal/CliClient+run.swift | 35 ++++----- .../ConfigurationClient/Configuration.swift | 2 - .../ConfigurationClient.swift | 25 +++++-- Tests/CliVersionTests/CliClientTests.swift | 9 +-- Tests/CliVersionTests/CliVersionTests.swift | 71 ------------------- .../ConfigurationClientTests.swift | 5 +- 16 files changed, 133 insertions(+), 154 deletions(-) delete mode 100644 Tests/CliVersionTests/CliVersionTests.swift diff --git a/.bump-version.json b/.bump-version.json index ad3a164..582d7ee 100644 --- a/.bump-version.json +++ b/.bump-version.json @@ -3,13 +3,15 @@ "semvar" : { "allowPreRelease" : true, "strategy" : { - "command" : { - "arguments" : [ - "my-command", - "--some-option" - ] + "gitTag" : { + "exactMatch" : false } } } + }, + "target" : { + "module" : { + "name" : "BumpVersion" + } } } \ No newline at end of file diff --git a/Sources/BumpVersion/Commands/BumpCommand.swift b/Sources/BumpVersion/Commands/BumpCommand.swift index ad350a5..d8395cf 100644 --- a/Sources/BumpVersion/Commands/BumpCommand.swift +++ b/Sources/BumpVersion/Commands/BumpCommand.swift @@ -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" ) ]) ) diff --git a/Sources/BumpVersion/Commands/ConfigCommand.swift b/Sources/BumpVersion/Commands/ConfigCommand.swift index b0a02fc..32ff697 100644 --- a/Sources/BumpVersion/Commands/ConfigCommand.swift +++ b/Sources/BumpVersion/Commands/ConfigCommand.swift @@ -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) } } diff --git a/Sources/BumpVersion/Documentation.docc/Articles/BasicConfiguration.md b/Sources/BumpVersion/Documentation.docc/Articles/BasicConfiguration.md index f344cb4..5a75df3 100644 --- a/Sources/BumpVersion/Documentation.docc/Articles/BasicConfiguration.md +++ b/Sources/BumpVersion/Documentation.docc/Articles/BasicConfiguration.md @@ -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 diff --git a/Sources/BumpVersion/Documentation.docc/Articles/CommandReference.md b/Sources/BumpVersion/Documentation.docc/Articles/CommandReference.md index c749634..917c84e 100644 --- a/Sources/BumpVersion/Documentation.docc/Articles/CommandReference.md +++ b/Sources/BumpVersion/Documentation.docc/Articles/CommandReference.md @@ -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 @@ -66,11 +74,10 @@ 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 | +| Long | Description | +| -------- | --------------------------------- | +| --branch | Use the branch strategy | +| --semvar | Use the semvar strategy (default) | ##### Generate Configuration Example diff --git a/Sources/BumpVersion/Documentation.docc/Articles/OptionsReference.md b/Sources/BumpVersion/Documentation.docc/Articles/OptionsReference.md index 7614a35..d5beded 100644 --- a/Sources/BumpVersion/Documentation.docc/Articles/OptionsReference.md +++ b/Sources/BumpVersion/Documentation.docc/Articles/OptionsReference.md @@ -9,13 +9,13 @@ 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 | | 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 | +| Short | Long | Argument | Description | +| ----- | ------------------- | -------- | -------------------------------------------------------------------- | +| N/A | --print | N/A | Perform the command, but don't write any output files | +| N/A | --project-directory | | 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 | ### Configuration Options @@ -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 | | 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 diff --git a/Sources/BumpVersion/GlobalOptions.swift b/Sources/BumpVersion/GlobalOptions.swift index 7559499..089c78f 100644 --- a/Sources/BumpVersion/GlobalOptions.swift +++ b/Sources/BumpVersion/GlobalOptions.swift @@ -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( diff --git a/Sources/BumpVersion/Helpers/GlobalOptions+run.swift b/Sources/BumpVersion/Helpers/GlobalOptions+run.swift index 580f9ec..4e2f453 100644 --- a/Sources/BumpVersion/Helpers/GlobalOptions+run.swift +++ b/Sources/BumpVersion/Helpers/GlobalOptions+run.swift @@ -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 ) } } diff --git a/Sources/CliClient/CliClient.swift b/Sources/CliClient/CliClient.swift index c4a6d4b..9054a8b 100644 --- a/Sources/CliClient/CliClient.swift +++ b/Sources/CliClient/CliClient.swift @@ -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 } } diff --git a/Sources/CliClient/Documentation.docc/Articles/ManualPlugins.md b/Sources/CliClient/Documentation.docc/Articles/ManualPlugins.md index 16c2ed3..b1314e2 100644 --- a/Sources/CliClient/Documentation.docc/Articles/ManualPlugins.md +++ b/Sources/CliClient/Documentation.docc/Articles/ManualPlugins.md @@ -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 \ ``` diff --git a/Sources/CliClient/Internal/CliClient+run.swift b/Sources/CliClient/Internal/CliClient+run.swift index 73dad84..c3838a8 100644 --- a/Sources/CliClient/Internal/CliClient+run.swift +++ b/Sources/CliClient/Internal/CliClient+run.swift @@ -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) - logger.debug("Version: \(version)") + 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) } diff --git a/Sources/ConfigurationClient/Configuration.swift b/Sources/ConfigurationClient/Configuration.swift index 13f49ba..3bd349a 100644 --- a/Sources/ConfigurationClient/Configuration.swift +++ b/Sources/ConfigurationClient/Configuration.swift @@ -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? { diff --git a/Sources/ConfigurationClient/ConfigurationClient.swift b/Sources/ConfigurationClient/ConfigurationClient.swift index f84db98..247765f 100644 --- a/Sources/ConfigurationClient/ConfigurationClient.swift +++ b/Sources/ConfigurationClient/ConfigurationClient.swift @@ -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 { - throw ConfigurationClientError.configurationNotFound + if strict { + throw ConfigurationClientError.configurationNotFound + } + return .default } - return (try? await load(url)) ?? .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( 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)) } diff --git a/Tests/CliVersionTests/CliClientTests.swift b/Tests/CliVersionTests/CliClientTests.swift index 7cc91b8..bf9f8f5 100644 --- a/Tests/CliVersionTests/CliClientTests.swift +++ b/Tests/CliVersionTests/CliClientTests.swift @@ -138,11 +138,12 @@ extension CliClient.SharedOptions { ) -> Self { return .init( dryRun: dryRun, - gitDirectory: gitDirectory, + projectDirectory: gitDirectory, loggingOptions: .init(command: "test", verbose: 2), - target: .init(module: .init(target)), - branch: versionStrategy.branch, - semvar: versionStrategy.semvar + configurationToMerge: .init( + target: .init(module: .init(target)), + strategy: versionStrategy + ) ) } } diff --git a/Tests/CliVersionTests/CliVersionTests.swift b/Tests/CliVersionTests/CliVersionTests.swift deleted file mode 100644 index 192d5b3..0000000 --- a/Tests/CliVersionTests/CliVersionTests.swift +++ /dev/null @@ -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") - } - } -} diff --git a/Tests/ConfigurationClientTests/ConfigurationClientTests.swift b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift index 935cc88..347419a 100644 --- a/Tests/ConfigurationClientTests/ConfigurationClientTests.swift +++ b/Tests/ConfigurationClientTests/ConfigurationClientTests.swift @@ -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)