feat: Working on command-line documentation.
Some checks failed
CI / Ubuntu (push) Has been cancelled
Some checks failed
CI / Ubuntu (push) Has been cancelled
This commit is contained in:
19
Sources/BumpVersion/Application.swift
Normal file
19
Sources/BumpVersion/Application.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct Application: AsyncParsableCommand {
|
||||
static let commandName = "bump-version"
|
||||
|
||||
static let configuration: CommandConfiguration = .init(
|
||||
commandName: commandName,
|
||||
version: VERSION ?? "0.0.0",
|
||||
subcommands: [
|
||||
BuildCommand.self,
|
||||
BumpCommand.self,
|
||||
GenerateCommand.self,
|
||||
ConfigCommand.self
|
||||
],
|
||||
defaultSubcommand: BumpCommand.self
|
||||
)
|
||||
}
|
||||
25
Sources/BumpVersion/Commands/BuildCommand.swift
Normal file
25
Sources/BumpVersion/Commands/BuildCommand.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import Foundation
|
||||
import ShellClient
|
||||
|
||||
// NOTE: This command is only used with the build with version plugin.
|
||||
struct BuildCommand: AsyncParsableCommand {
|
||||
static let commandName = "build"
|
||||
|
||||
static let configuration: CommandConfiguration = .init(
|
||||
commandName: Self.commandName,
|
||||
abstract: Abstract.default("Used for the build with version plugin.").render(),
|
||||
discussion: 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, command: Self.commandName)
|
||||
}
|
||||
}
|
||||
40
Sources/BumpVersion/Commands/BumpCommand.swift
Normal file
40
Sources/BumpVersion/Commands/BumpCommand.swift
Normal file
@@ -0,0 +1,40 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import Dependencies
|
||||
|
||||
struct BumpCommand: CommandRepresentable {
|
||||
|
||||
static let commandName = "bump"
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: Self.commandName,
|
||||
abstract: Abstract.default("Bump version of a command-line tool."),
|
||||
usage: Usage.default(commandName: nil),
|
||||
discussion: Discussion.default(examples: [
|
||||
makeExample(
|
||||
label: "Basic usage, bump the minor version.",
|
||||
example: "--minor"
|
||||
),
|
||||
makeExample(
|
||||
label: "Dry run, just show what the bumped version would be.",
|
||||
example: "--minor --dry-run"
|
||||
)
|
||||
])
|
||||
)
|
||||
|
||||
@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, command: Self.commandName, args: bumpOption)
|
||||
}
|
||||
}
|
||||
|
||||
extension CliClient.BumpOption: EnumerableFlag {}
|
||||
233
Sources/BumpVersion/Commands/ConfigCommand.swift
Normal file
233
Sources/BumpVersion/Commands/ConfigCommand.swift
Normal file
@@ -0,0 +1,233 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import ConfigurationClient
|
||||
import CustomDump
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
|
||||
struct ConfigCommand: AsyncParsableCommand {
|
||||
static let commandName = "config"
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: commandName,
|
||||
abstract: Abstract.default("Configuration commands").render(),
|
||||
subcommands: [
|
||||
DumpConfig.self,
|
||||
GenerateConfig.self
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
extension ConfigCommand {
|
||||
|
||||
struct DumpConfig: CommandRepresentable {
|
||||
static let commandName = "dump"
|
||||
static let parentCommand = ConfigCommand.commandName
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: Self.commandName,
|
||||
abstract: Abstract.default("Inspect the parsed configuration."),
|
||||
usage: Usage.default(parentCommand: ConfigCommand.commandName, commandName: Self.commandName),
|
||||
discussion: Discussion.default(
|
||||
notes: [
|
||||
"""
|
||||
The default style is to print the output in `json`, however you can use the `--swift` flag to
|
||||
print the output in `swift`.
|
||||
"""
|
||||
],
|
||||
examples: [
|
||||
makeExample(label: "Show the project configuration.", example: ""),
|
||||
makeExample(
|
||||
label: "Update a configuration file with the dumped output",
|
||||
example: "--disable-pre-release > .bump-version.prod.json"
|
||||
)
|
||||
]
|
||||
) {
|
||||
"""
|
||||
Loads the project configuration file (if applicable) and merges the options passed in,
|
||||
then prints the configuration to stdout.
|
||||
"""
|
||||
},
|
||||
aliases: ["d"]
|
||||
)
|
||||
@Flag(
|
||||
help: "Change the style of what get's printed."
|
||||
)
|
||||
fileprivate var printStyle: PrintStyle = .json
|
||||
|
||||
@OptionGroup var globals: ConfigCommandOptions
|
||||
|
||||
func run() async throws {
|
||||
let configuration = try await globals
|
||||
.shared(command: Self.commandName)
|
||||
.runClient(\.parsedConfiguration)
|
||||
|
||||
try globals.printConfiguration(configuration, style: printStyle)
|
||||
}
|
||||
}
|
||||
|
||||
struct GenerateConfig: CommandRepresentable {
|
||||
static let commandName = "generate"
|
||||
static let parentCommand = ConfigCommand.commandName
|
||||
|
||||
static let configuration: CommandConfiguration = .init(
|
||||
commandName: commandName,
|
||||
abstract: Abstract.default("Generate a configuration file, based on the given options.").render(),
|
||||
usage: Usage.default(parentCommand: ConfigCommand.commandName, commandName: commandName),
|
||||
discussion: Discussion.default(examples: [
|
||||
makeExample(
|
||||
label: "Generate a configuration file for the 'foo' target.",
|
||||
example: "-m foo"
|
||||
),
|
||||
makeExample(
|
||||
label: "Show the output and don't write to a file.",
|
||||
example: "-m foo --print"
|
||||
)
|
||||
]),
|
||||
aliases: ["g"]
|
||||
)
|
||||
|
||||
@Flag(
|
||||
help: "The style of the configuration."
|
||||
)
|
||||
var style: ConfigCommand.Style = .semvar
|
||||
|
||||
@Flag(
|
||||
name: .customLong("print"),
|
||||
help: "Print json to stdout."
|
||||
)
|
||||
var printJson: Bool = false
|
||||
|
||||
@OptionGroup var globals: ConfigCommandOptions
|
||||
|
||||
func run() async throws {
|
||||
try await withSetupDependencies {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
|
||||
let configuration = try style.parseConfiguration(
|
||||
configOptions: globals.configOptions,
|
||||
extraOptions: globals.extraOptions
|
||||
)
|
||||
|
||||
switch printJson {
|
||||
case true:
|
||||
try globals.handlePrintJson(configuration)
|
||||
case false:
|
||||
let url = globals.configFileUrl
|
||||
try await configurationClient.write(configuration, url)
|
||||
print(url.cleanFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigCommand {
|
||||
enum Style: EnumerableFlag {
|
||||
case branch, semvar
|
||||
|
||||
func parseConfiguration(
|
||||
configOptions: ConfigurationOptions,
|
||||
extraOptions: [String]
|
||||
) throws -> Configuration {
|
||||
let strategy: Configuration.VersionStrategy
|
||||
|
||||
switch self {
|
||||
case .branch:
|
||||
strategy = .branch(includeCommitSha: configOptions.commitSha)
|
||||
case .semvar:
|
||||
strategy = try .semvar(configOptions.semvarOptions(extraOptions: extraOptions))
|
||||
}
|
||||
|
||||
return try Configuration(
|
||||
target: configOptions.target(),
|
||||
strategy: strategy
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add verbose.
|
||||
// TODO: Need to be able to generate a branch style config file.
|
||||
@dynamicMemberLookup
|
||||
struct ConfigCommandOptions: ParsableArguments {
|
||||
|
||||
@OptionGroup var configOptions: ConfigurationOptions
|
||||
|
||||
@Flag(
|
||||
name: .shortAndLong,
|
||||
help: "Increase logging level, can be passed multiple times (example: -vvv)."
|
||||
)
|
||||
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-command` or `--custom-pre-release` flag is not set.
|
||||
"""
|
||||
)
|
||||
var extraOptions: [String] = []
|
||||
|
||||
subscript<T>(dynamicMember keyPath: KeyPath<ConfigurationOptions, T>) -> T {
|
||||
configOptions[keyPath: keyPath]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConfigCommand.DumpConfig {
|
||||
enum PrintStyle: EnumerableFlag {
|
||||
case json, swift
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConfigCommand.ConfigCommandOptions {
|
||||
|
||||
func shared(command: String) throws -> CliClient.SharedOptions {
|
||||
try configOptions.shared(command: command, extraOptions: extraOptions, verbose: verbose)
|
||||
}
|
||||
|
||||
func handlePrintJson(_ configuration: Configuration) throws {
|
||||
@Dependency(\.coders) var coders
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let data = try coders.jsonEncoder().encode(configuration)
|
||||
guard let string = String(bytes: data, encoding: .utf8) else {
|
||||
logger.error("Error encoding configuration to json.")
|
||||
throw ConfigurationEncodingError()
|
||||
}
|
||||
print(string)
|
||||
}
|
||||
|
||||
func printConfiguration(
|
||||
_ configuration: Configuration,
|
||||
style: ConfigCommand.DumpConfig.PrintStyle
|
||||
) throws {
|
||||
switch style {
|
||||
case .json:
|
||||
try handlePrintJson(configuration)
|
||||
case .swift:
|
||||
customDump(configuration)
|
||||
}
|
||||
|
||||
// guard printJson else {
|
||||
// customDump(configuration)
|
||||
// return
|
||||
// }
|
||||
// try handlePrintJson(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConfigurationOptions {
|
||||
var configFileUrl: URL {
|
||||
switch configurationFile {
|
||||
case let .some(path):
|
||||
return URL(filePath: path)
|
||||
case .none:
|
||||
return URL(filePath: ".bump-version.json")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConfigurationEncodingError: Error {}
|
||||
29
Sources/BumpVersion/Commands/GenerateCommand.swift
Normal file
29
Sources/BumpVersion/Commands/GenerateCommand.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import ShellClient
|
||||
|
||||
struct GenerateCommand: CommandRepresentable {
|
||||
static let commandName = "generate"
|
||||
|
||||
static let configuration: CommandConfiguration = .init(
|
||||
commandName: Self.commandName,
|
||||
abstract: Abstract.default("Generates a version file in your project."),
|
||||
usage: Usage.default(commandName: Self.commandName),
|
||||
discussion: Discussion.default(
|
||||
examples: [
|
||||
makeExample(label: "Basic usage.", example: "")
|
||||
]
|
||||
) {
|
||||
"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, command: Self.commandName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
# Basic Configuration
|
||||
|
||||
Basic configuration examples.
|
||||
|
||||
## Overview
|
||||
|
||||
Generating a configuration file for your application is the easiest way to use the command-line
|
||||
tool. The configuration specifies the location of the version file, either by a path to the file or
|
||||
by the module that a `Version.swift` file resides in. It also declares the strategy for generating
|
||||
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.
|
||||
|
||||
```bash
|
||||
bump-version config generate --target-module my-tool
|
||||
```
|
||||
|
||||
The above command produces the following in a file named `.bump-version.json` with the generated
|
||||
default settings. This will generate semvar style version (example: `1.0.0`).
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"strategy": {
|
||||
"gitTag": {
|
||||
"exactMatch": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"module": {
|
||||
"name": "my-tool"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: The above does not add a pre-release strategy although it "allows" it if you pass an option
|
||||
> to command later, if you set "allowPreRelease" to false it will ignore any attempts to add a
|
||||
> pre-release strategy when bumping the version.
|
||||
|
||||
Most commands accept the same options for configuration as the above `config generate` command.
|
||||
Those get merged with your project configuration when calling a command, that allows you to override
|
||||
any of your defaults depending on your use case. You can also generate several configuration files
|
||||
and specify them by passing the `-f | --configuration-file` to the command.
|
||||
|
||||
## Inspecting parsed configuration.
|
||||
|
||||
You can inspect the configuration that get's parsed by using the `config dump` command. The dump
|
||||
command will print the parsed `json` to `stdout`, which can be helpful in confirming that your
|
||||
configuration is valid and does not work unexpectedly.
|
||||
|
||||
```bash
|
||||
bump-version config dump <options / overrides>
|
||||
```
|
||||
|
||||
The dump command can also be used to generate a different configuration that is merged with your
|
||||
default.
|
||||
|
||||
```bash
|
||||
bump-version config dump --pre-release-git-tag-style > .bump-version.prerelease.json
|
||||
```
|
||||
|
||||
Which would produce the following in `.bump-version.prerelease.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"preRelease": {
|
||||
"strategy": {
|
||||
"gitTag": {}
|
||||
}
|
||||
},
|
||||
"strategy": {
|
||||
"gitTag": {
|
||||
"exactMatch": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"module": {
|
||||
"name": "my-tool"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You could then use this file when bumping your version.
|
||||
|
||||
```bash
|
||||
bump-version bump -f .bump-version.prerelease.json
|
||||
```
|
||||
|
||||
> See Also: <doc:ConfigurationReference>
|
||||
@@ -0,0 +1,108 @@
|
||||
# Command Reference
|
||||
|
||||
Learn about the provided commands.
|
||||
|
||||
## Overview
|
||||
|
||||
The command-line tool provides the following commands.
|
||||
|
||||
> See Also:
|
||||
>
|
||||
> 1. <doc:BasicConfiguration>
|
||||
> 1. <doc:ConfigurationReference>
|
||||
|
||||
All commands output the path of the file they generate or write to, to allow them to be piped into
|
||||
other commands, however this will not work if you specify `--verbose` because then other output is
|
||||
also written to `stdout`.
|
||||
|
||||
### Bump Command
|
||||
|
||||
This bumps the version to the next version based on the project configuration or passed in options.
|
||||
This is the default command when calling the `bump-version` tool, so specifying the `bump` command
|
||||
is not required, but will be shown in examples below for clarity.
|
||||
|
||||
> See Also: <doc:OptionsReference>
|
||||
|
||||
The following options are used to declare which part of a semvar to bump to the next version, they
|
||||
are ignored if your configuration or options specify to use a `branch` strategy.
|
||||
|
||||
| Long | Description |
|
||||
| ------------- | --------------------------------------- |
|
||||
| --major | Bump the major portion of the semvar |
|
||||
| --minor | Bump the minor portion of the semvar |
|
||||
| --patch | Bump the patch portion of the semvar |
|
||||
| --pre-release | Bump the pre-release suffix of a semvar |
|
||||
|
||||
#### Bump Command Usage Examples
|
||||
|
||||
```bash
|
||||
bump-version bump --minor
|
||||
```
|
||||
|
||||
Show the output, but don't update the version file.
|
||||
|
||||
```bash
|
||||
bump-version bump --major --dry-run
|
||||
```
|
||||
|
||||
### Generate Command
|
||||
|
||||
This generates a version file based on your configuration, setting it's initial value based on your
|
||||
projects configuration strategy. This is generally only ran once after setting up a project.
|
||||
|
||||
```bash
|
||||
bump-version generate
|
||||
```
|
||||
|
||||
### Configuration Commands
|
||||
|
||||
The following commands are used to work with project configuration.
|
||||
|
||||
#### Generate Command
|
||||
|
||||
Generates a configuration file based on the passed in options.
|
||||
|
||||
> See Also: <doc:OptionsReference>
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
bump-version config generate -m my-tool
|
||||
```
|
||||
|
||||
The above generates a configuration file using the default version strategy for a target module
|
||||
named 'my-tool'.
|
||||
|
||||
#### Dump Command
|
||||
|
||||
Dumps the parsed configuration to `stdout`.
|
||||
|
||||
> See Also: <doc:OptionsReference>
|
||||
|
||||
The following options are used to declare what output gets printed.
|
||||
|
||||
| Long | Description |
|
||||
| ------- | -------------------- |
|
||||
| --json | Print json (default) |
|
||||
| --swift | Print swift struct |
|
||||
|
||||
##### Dump Configuration Example
|
||||
|
||||
```bash
|
||||
bump-version config dump --disable-pre-release
|
||||
```
|
||||
|
||||
This command can also be used to extend a configuration file with new configuration by sending the
|
||||
output to a new file.
|
||||
|
||||
```bash
|
||||
bump-version config dump --disable-pre-release > .bump-version.prod.json
|
||||
```
|
||||
@@ -0,0 +1,225 @@
|
||||
# Configuration Reference
|
||||
|
||||
Learn about the configuration.
|
||||
|
||||
## Target
|
||||
|
||||
The target declares where a version file lives that can be bumped by the command-line tool.
|
||||
|
||||
A target can be specified either as a path from the root of the project to a file that contains the
|
||||
version or as a module that contains the version file.
|
||||
|
||||
> Note: A version file should not contain any other code aside from the version as the entire file
|
||||
> contents get over written when bumping the version.
|
||||
|
||||
### Target - Path Example
|
||||
|
||||
```json
|
||||
{
|
||||
"target": {
|
||||
"path": "Sources/my-tool/Version.swift"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Target - Module Example
|
||||
|
||||
When using the module style a file name is not required if you use the default file name of
|
||||
`Version.swift`, however it can be customized in your target specification.
|
||||
|
||||
```json
|
||||
{
|
||||
"target": {
|
||||
"module": {
|
||||
"fileName": "CustomVersion.swift",
|
||||
"name": "my-tool"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above will parse the path to the file as `Sources/my-tool/CustomVersion.swift`.
|
||||
|
||||
## Strategy
|
||||
|
||||
The strategy declares how to generate the next version of your project. There are currently two
|
||||
strategies, `branch` and `semvar`, that we will discuss.
|
||||
|
||||
### Branch Strategy
|
||||
|
||||
This is the most basic strategy, which will derive the version via the git branch and optionally the
|
||||
short version of the commit sha.
|
||||
|
||||
An example of this style may look like: `main-8d73287a60`.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"branch": {
|
||||
"includeCommitSha": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you set `'"includeCommitSha" : false'` then only the branch name will be used.
|
||||
|
||||
### Semvar Strategy
|
||||
|
||||
This is the most common strategy to use. It has support for generating the next version using either
|
||||
`gitTag` or a custom `command` strategy.
|
||||
|
||||
#### Git Tag Strategy
|
||||
|
||||
The `gitTag` strategy derives the next version using the output of `git describe --tags` command.
|
||||
This requires a commit to have a semvar style tag in it's history, otherwise we will use `0.0.0` as
|
||||
the tag until a commit is tagged.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"strategy": {
|
||||
"gitTag": {
|
||||
"exactMatch": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you set `'"exactMatch": true'` then bumping the version will fail on commits that are not
|
||||
specifically tagged.
|
||||
|
||||
#### Custom Command Strategy
|
||||
|
||||
The custom `command` strategy allows you to call an external command to derive the next version. The
|
||||
external command should return something that can be parsed as a semvar.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"strategy": {
|
||||
"command": {
|
||||
"arguments": ["my-command", "--some-option", "foo"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: All arguments to custom commands need to be a separate string in the arguments array
|
||||
> otherwise they may not get passed appropriately to the command, so
|
||||
> `"my-command --some-option foo"` will likely not work as expected.
|
||||
|
||||
#### Pre-Release
|
||||
|
||||
Semvar strategies can also include a pre-release strategy that adds a suffix to the semvar version
|
||||
that can be used. In order for pre-release suffixes to be allowed the `'"allowPreRelease": true'`
|
||||
must be set on the semvar strategy, you must also supply a pre-release strategy either when calling
|
||||
the bump-version command or in your configuration.
|
||||
|
||||
Currently there are three pre-release strategies, `branch`, `gitTag`, and custom `command`, which we
|
||||
will discuss.
|
||||
|
||||
A pre-release semvar example: `1.0.0-1-8d73287a60`
|
||||
|
||||
##### Branch
|
||||
|
||||
This will use the branch and optionally short version of the commit sha in order to derive the
|
||||
pre-release suffix.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"preRelease": {
|
||||
"strategy": {
|
||||
"branch": {
|
||||
"includeCommitSha": true
|
||||
}
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would produce something similar to: `1.0.0-main-8d73287a60`
|
||||
|
||||
##### Git Tag
|
||||
|
||||
This will use the full output of `git describe --tags` to include the pre-release suffix.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy" : {
|
||||
"semvar" : {
|
||||
"allowPreRelease" : true,
|
||||
"preRelease" : {
|
||||
"strategy" : {
|
||||
"gitTag" : {}
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would produce something similar to: `1.0.0-10-8d73287a60`
|
||||
|
||||
##### Custom Command
|
||||
|
||||
This allows you to call an external command to generate the pre-release suffix. We will use whatever
|
||||
the output is as the suffix.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": {
|
||||
"semvar": {
|
||||
"allowPreRelease": true,
|
||||
"preRelease": {
|
||||
"strategy": {
|
||||
"command": {
|
||||
"arguments": ["my-command", "--some-option", "foo"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: All arguments to custom commands need to be a separate string in the arguments array
|
||||
> otherwise they may not get passed appropriately to the command, so
|
||||
> `"my-command --some-option foo"` will likely not work as expected.
|
||||
|
||||
##### Pre-Release Prefixes
|
||||
|
||||
All pre-release strategies can also accept a `prefix` that will appended prior to the generated
|
||||
pre-release suffix. This can also be used without providing a pre-release strategy to only append
|
||||
the `prefix` to the semvar.
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy" : {
|
||||
"semvar" : {
|
||||
"allowPreRelease" : true,
|
||||
"preRelease" : {
|
||||
"prefix": "rc"
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would produce something similar to: `1.0.0-rc`
|
||||
@@ -0,0 +1,47 @@
|
||||
# Options Reference
|
||||
|
||||
Common options used for the commands.
|
||||
|
||||
## Overview
|
||||
|
||||
The commands mostly all accept similar options, below is a list of those options and a description
|
||||
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 |
|
||||
| -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
|
||||
|
||||
| Short | Long | Argument | Description |
|
||||
| ----- | ---------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
|
||||
| -f | --configuration-file | <path> | The path to the configuration to use. |
|
||||
| -m | --target-module | <name> | The target module name inside your project |
|
||||
| -n | --target-file-name | <name> | The file name for the version to be found inside the module |
|
||||
| -p | --target-file-path | <path> | Path to a version file in your project |
|
||||
| N/A | --enable-git-tag/--disable-git-tag | N/A | Use the git-tag version strategy |
|
||||
| N/A | --require-exact-match | N/A | Fail if a tag is not specifically set on the commit |
|
||||
| N/A | --require-existing-semvar | N/A | Fail if an existing semvar is not found in the version file. |
|
||||
| -c | --custom-command | <arguments> | Use a custom command strategy for the version (any options need to proceed a '--') |
|
||||
| N/A | --commit-sha/--no-commit-sha | N/A | Use the commit sha with branch version or pre-release strategy |
|
||||
|
||||
#### Pre-Release Options
|
||||
|
||||
| Short | Long | Argument | Description |
|
||||
| ----- | ---------------------------- | ----------- | ------------------------------------------------------------ |
|
||||
| -d | --disable-pre-release | N/A | Disable pre-relase suffixes from being used |
|
||||
| -b | --pre-release-branch-style | N/A | Use the branch and commit sha style for pre-release suffixes |
|
||||
| N/A | --commit-sha/--no-commit-sha | N/A | Use the commit sha with branch pre-release strategy |
|
||||
| -g | --pre-release-git-tag-style | N/A | Use the git tag style for pre-release suffixes |
|
||||
| N/A | --pre-release-prefix | <prefix> | A prefix to use before a pre-release suffix |
|
||||
| N/A | --custom-pre-release | <arguments> | Use custom command strategy for pre-release suffix |
|
||||
|
||||
> Note: When using one of the `--custom-*` options then any arguments passed will be used for
|
||||
> arguments when calling your custom strategy, if the external tool you use requires options they
|
||||
> must proceed a '--' otherwise you will get an error that an 'unexpected option' is being used.
|
||||
@@ -0,0 +1,77 @@
|
||||
# Plugins Reference
|
||||
|
||||
Learn about using the provided package plugins.
|
||||
|
||||
## Overview
|
||||
|
||||
There are two provided plugins that can be used, this describes their usage.
|
||||
|
||||
### Build with Version
|
||||
|
||||
The `BuildWithVersion` plugin uses your project configuration to automatically generate a version
|
||||
file when swift builds your project. You can use the plugin by declaring it as dependency in your
|
||||
project.
|
||||
|
||||
```swift
|
||||
// swift-tools-version: 5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
platforms:[.macOS(.v13)],
|
||||
dependencies: [
|
||||
...,
|
||||
.package(url: "https://github.com/m-housh/swift-bump-version.git", from: "0.2.0")
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "<target name>",
|
||||
dependencies: [...],
|
||||
plugins: [
|
||||
.plugin(name: "BuildWithVersionPlugin", package: "swift-bump-version")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
### Manual Plugin
|
||||
|
||||
There is also a `BumpVersionPlugin` that allows you to run the `bump-version` tool without
|
||||
installing the command-line tool on your system, however it does make the usage much more verbose.
|
||||
|
||||
Include as dependency in your project.
|
||||
|
||||
```swift
|
||||
// swift-tools-version: 5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
platforms:[.macOS(.v13)],
|
||||
dependencies: [
|
||||
...,
|
||||
.package(url: "https://github.com/m-housh/swift-bump-version.git", from: "0.2.0")
|
||||
],
|
||||
targets: [
|
||||
...
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Then you can use the manual plugin.
|
||||
|
||||
```
|
||||
swift package \
|
||||
--disable-sandbox \
|
||||
--allow-writing-to-package-directory \
|
||||
bump-version \
|
||||
bump \
|
||||
--minor
|
||||
```
|
||||
|
||||
> Note: Anything after the `'bump-version'` in the above get's passed directly to the bump-version
|
||||
> command-line tool, so you can use this to run any of the provided commands, the above shows
|
||||
> bumping the minor semvar as a reference example.
|
||||
>
|
||||
> See Also: <doc:CommandReference>
|
||||
62
Sources/BumpVersion/Documentation.docc/BumpVersion.md
Normal file
62
Sources/BumpVersion/Documentation.docc/BumpVersion.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# ``BumpVersion``
|
||||
|
||||
@Metadata {
|
||||
@DisplayName("bump-version")
|
||||
@DocumentationExtension(mergeBehavior: override)
|
||||
}
|
||||
|
||||
A command-line tool for managing swift application versions.
|
||||
|
||||
## Overview
|
||||
|
||||
This tool aims to provide a way to manage application versions in your build
|
||||
pipeline. It can be used as a stand-alone command-line tool or by using one of
|
||||
the provided swift package plugins.
|
||||
|
||||
## Installation
|
||||
|
||||
The command-line tool can be installed via homebrew.
|
||||
|
||||
```bash
|
||||
brew tap m-housh/formula
|
||||
brew install bump-version
|
||||
```
|
||||
|
||||
## Package Plugins
|
||||
|
||||
Package plugins can be used in a swift package manager project.
|
||||
|
||||
```swift
|
||||
// swift-tools-version: 5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
platforms:[.macOS(.v13)],
|
||||
dependencies: [
|
||||
...,
|
||||
.package(url: "https://github.com/m-housh/swift-bump-version.git", from: "0.2.0")
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "<target name>",
|
||||
dependencies: [...],
|
||||
plugins: [
|
||||
.plugin(name: "BuildWithVersionPlugin", package: "swift-bump-version")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
> See Also: <doc:PluginsReference>
|
||||
|
||||
## Topics
|
||||
|
||||
### Articles
|
||||
|
||||
- <doc:BasicConfiguration>
|
||||
- <doc:ConfigurationReference>
|
||||
- <doc:CommandReference>
|
||||
- <doc:OptionsReference>
|
||||
- <doc:PluginsReference>
|
||||
169
Sources/BumpVersion/GlobalOptions.swift
Normal file
169
Sources/BumpVersion/GlobalOptions.swift
Normal file
@@ -0,0 +1,169 @@
|
||||
import ArgumentParser
|
||||
@_spi(Internal) import CliClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import Rainbow
|
||||
|
||||
// TODO: Add an option to not load project configuration.
|
||||
|
||||
struct GlobalOptions: ParsableArguments {
|
||||
|
||||
@OptionGroup
|
||||
var configOptions: ConfigurationOptions
|
||||
|
||||
@Option(
|
||||
name: .customLong("git-directory"),
|
||||
help: "The git directory for the version (default: current directory)"
|
||||
)
|
||||
var gitDirectory: String?
|
||||
|
||||
@Flag(
|
||||
name: .customLong("dry-run"),
|
||||
help: "Print's what would be written to a target version file."
|
||||
)
|
||||
var dryRun: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: .shortAndLong,
|
||||
help: "Increase logging level, can be passed multiple times (example: -vvv)."
|
||||
)
|
||||
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` or `--custom-pre-release` flag is not set.
|
||||
"""
|
||||
)
|
||||
var extraOptions: [String] = []
|
||||
|
||||
}
|
||||
|
||||
struct ConfigurationOptions: ParsableArguments {
|
||||
@Option(
|
||||
name: [.customShort("f"), .long],
|
||||
help: "Specify the path to a configuration file. (default: .bump-version.json)",
|
||||
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 {
|
||||
@Option(
|
||||
name: [.customShort("p"), .long],
|
||||
help: "Path to the version file, not required if module is set."
|
||||
)
|
||||
var targetFilePath: String?
|
||||
|
||||
@Option(
|
||||
name: [.customShort("m"), .long],
|
||||
help: "The target module name or directory path, not required if path is set."
|
||||
)
|
||||
var targetModule: String?
|
||||
|
||||
@Option(
|
||||
name: [.customShort("n"), .long],
|
||||
help: "The file name inside the target module. (defaults to: \"Version.swift\")."
|
||||
)
|
||||
var targetFileName: String?
|
||||
|
||||
}
|
||||
|
||||
struct PreReleaseOptions: ParsableArguments {
|
||||
|
||||
@Flag(
|
||||
name: .shortAndLong,
|
||||
help: ""
|
||||
)
|
||||
var disablePreRelease: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: [.customShort("b"), .customLong("pre-release-branch-style")],
|
||||
help: """
|
||||
Use branch name and commit sha for pre-release suffix, ignored if branch is set.
|
||||
"""
|
||||
)
|
||||
var useBranchAsPreRelease: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: [.customShort("g"), .customLong("pre-release-git-tag-style")],
|
||||
help: """
|
||||
Use `git describe --tags` for pre-release suffix, ignored if branch is set.
|
||||
"""
|
||||
)
|
||||
var useTagAsPreRelease: Bool = false
|
||||
|
||||
@Option(
|
||||
name: .long,
|
||||
help: """
|
||||
Add / use a pre-release prefix string.
|
||||
"""
|
||||
)
|
||||
var preReleasePrefix: String?
|
||||
|
||||
@Flag(
|
||||
name: .long,
|
||||
help: """
|
||||
Apply custom pre-release suffix, using extra options / arguments passed in after a '--'.
|
||||
"""
|
||||
)
|
||||
var customPreRelease: Bool = false
|
||||
|
||||
}
|
||||
|
||||
// TODO: Add custom command strategy.
|
||||
struct SemVarOptions: ParsableArguments {
|
||||
|
||||
@Flag(
|
||||
name: .long,
|
||||
inversion: .prefixedEnableDisable,
|
||||
help: "Use git-tag strategy for semvar."
|
||||
)
|
||||
var gitTag: Bool = true
|
||||
|
||||
@Flag(
|
||||
name: .long,
|
||||
help: "Require exact match for git tag strategy."
|
||||
)
|
||||
var requireExactMatch: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: .long,
|
||||
help: """
|
||||
Fail if an existing version file does not exist, \("ignored if:".yellow.bold) \("branch is set".italic).
|
||||
"""
|
||||
)
|
||||
var requireExistingFile: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: .long,
|
||||
help: "Fail if a semvar is not parsed from existing file or git tag, used if branch is not set."
|
||||
)
|
||||
var requireExistingSemvar: Bool = false
|
||||
|
||||
@Flag(
|
||||
name: .shortAndLong,
|
||||
help: """
|
||||
Custom command strategy, uses extra-options to call an external command.
|
||||
The external command should return a semvar that is used.
|
||||
"""
|
||||
)
|
||||
var customCommand: Bool = false
|
||||
|
||||
@OptionGroup var preRelease: PreReleaseOptions
|
||||
}
|
||||
311
Sources/BumpVersion/Helpers/DocHelpers.swift
Normal file
311
Sources/BumpVersion/Helpers/DocHelpers.swift
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
This file contains helpers for generating the documentation for the commands.
|
||||
|
||||
*/
|
||||
import ArgumentParser
|
||||
import CliDoc
|
||||
import Rainbow
|
||||
|
||||
protocol CommandRepresentable: AsyncParsableCommand {
|
||||
static var commandName: String { get }
|
||||
static var parentCommand: String? { get }
|
||||
}
|
||||
|
||||
extension CommandRepresentable {
|
||||
|
||||
static var parentCommand: String? { nil }
|
||||
|
||||
static func makeExample(
|
||||
label: String,
|
||||
example: String,
|
||||
includesAppName: Bool = true
|
||||
) -> AppExample {
|
||||
.init(
|
||||
label: label,
|
||||
parentCommand: parentCommand,
|
||||
commandName: commandName,
|
||||
includesAppName: includesAppName,
|
||||
example: example
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Abstract where Content == String {
|
||||
static func `default`(_ string: String) -> Self {
|
||||
.init { string.blue }
|
||||
}
|
||||
}
|
||||
|
||||
struct Note<Content: TextNode>: TextNode {
|
||||
let content: Content
|
||||
|
||||
init(
|
||||
@TextBuilder _ content: () -> Content
|
||||
) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: some TextNode {
|
||||
LabeledContent {
|
||||
content.italic()
|
||||
} label: {
|
||||
"Note:".yellow.bold
|
||||
}
|
||||
.style(.vertical())
|
||||
}
|
||||
}
|
||||
|
||||
extension Note where Content == AnyTextNode {
|
||||
|
||||
static func `default`(
|
||||
notes: [String],
|
||||
usesConfigurationFileNote: Bool = true,
|
||||
usesConfigurationMergingNote: Bool = true
|
||||
) -> Self {
|
||||
var notes = notes
|
||||
|
||||
if usesConfigurationFileNote {
|
||||
notes.insert(
|
||||
"Most options are not required when a configuration file is setup for your project.",
|
||||
at: 0
|
||||
)
|
||||
}
|
||||
|
||||
if usesConfigurationMergingNote {
|
||||
if usesConfigurationFileNote {
|
||||
notes.insert(
|
||||
"Any configuration options get merged with the loaded project configuration file.",
|
||||
at: 1
|
||||
)
|
||||
} else {
|
||||
notes.insert(
|
||||
"Any configuration options get merged with the loaded project configuration file.",
|
||||
at: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return .init {
|
||||
VStack {
|
||||
notes.enumerated().map { "\($0 + 1). \($1)" }
|
||||
}
|
||||
.eraseToAnyTextNode()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Discussion where Content == AnyTextNode {
|
||||
static func `default`<Preamble: TextNode, Trailing: TextNode>(
|
||||
notes: [String] = [],
|
||||
examples: [AppExample]? = nil,
|
||||
usesExtraOptions: Bool = true,
|
||||
usesConfigurationFileNote: Bool = true,
|
||||
usesConfigurationMergingNote: Bool = true,
|
||||
@TextBuilder preamble: () -> Preamble,
|
||||
@TextBuilder trailing: () -> Trailing
|
||||
) -> Self {
|
||||
Discussion {
|
||||
VStack {
|
||||
preamble().italic()
|
||||
|
||||
Note.default(
|
||||
notes: notes,
|
||||
usesConfigurationFileNote: usesConfigurationFileNote,
|
||||
usesConfigurationMergingNote: usesConfigurationMergingNote
|
||||
)
|
||||
|
||||
if let examples {
|
||||
ExampleSection.default(examples: examples, usesExtraOptions: usesExtraOptions)
|
||||
}
|
||||
|
||||
trailing()
|
||||
}
|
||||
.separator(.newLine(count: 2))
|
||||
.eraseToAnyTextNode()
|
||||
}
|
||||
}
|
||||
|
||||
static func `default`<Preamble: TextNode>(
|
||||
notes: [String] = [],
|
||||
examples: [AppExample]? = nil,
|
||||
usesExtraOptions: Bool = true,
|
||||
usesConfigurationFileNote: Bool = true,
|
||||
usesConfigurationMergingNote: Bool = true,
|
||||
@TextBuilder preamble: () -> Preamble
|
||||
) -> Self {
|
||||
.default(
|
||||
notes: notes,
|
||||
examples: examples,
|
||||
usesExtraOptions: usesExtraOptions,
|
||||
usesConfigurationFileNote: usesConfigurationFileNote,
|
||||
usesConfigurationMergingNote: usesConfigurationMergingNote,
|
||||
preamble: preamble,
|
||||
trailing: {
|
||||
if usesExtraOptions {
|
||||
ImportantNote.extraOptionsNote
|
||||
} else {
|
||||
Empty()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
static func `default`(
|
||||
notes: [String] = [],
|
||||
examples: [AppExample]? = nil,
|
||||
usesExtraOptions: Bool = true,
|
||||
usesConfigurationFileNote: Bool = true,
|
||||
usesConfigurationMergingNote: Bool = true
|
||||
) -> Self {
|
||||
.default(
|
||||
notes: notes,
|
||||
examples: examples,
|
||||
usesExtraOptions: usesExtraOptions,
|
||||
usesConfigurationFileNote: usesConfigurationFileNote,
|
||||
usesConfigurationMergingNote: usesConfigurationMergingNote,
|
||||
preamble: { Empty() },
|
||||
trailing: { Empty() }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ExampleSection where Header == String, Label == String {
|
||||
static func `default`(
|
||||
examples: [AppExample] = [],
|
||||
usesExtraOptions: Bool = true
|
||||
) -> some TextNode {
|
||||
var examples: [AppExample] = examples
|
||||
if usesExtraOptions {
|
||||
examples = examples.appendingExtraOptionsExample()
|
||||
}
|
||||
|
||||
return Self(
|
||||
"Examples:",
|
||||
label: "A few common usage examples.",
|
||||
examples: examples.map(\.example)
|
||||
)
|
||||
.style(AppExampleSectionStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct AppExampleSectionStyle: ExampleSectionStyle {
|
||||
|
||||
func render(content: ExampleSectionConfiguration) -> some TextNode {
|
||||
Section {
|
||||
VStack {
|
||||
content.examples.map { example in
|
||||
VStack {
|
||||
example.label.color(.green).bold()
|
||||
ShellCommand(example.example).style(.default)
|
||||
}
|
||||
}
|
||||
}
|
||||
.separator(.newLine(count: 2))
|
||||
} header: {
|
||||
HStack {
|
||||
content.title.color(.blue).bold()
|
||||
content.label.italic()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppExample {
|
||||
let label: String
|
||||
let parentCommand: String?
|
||||
let commandName: String
|
||||
let includesAppName: Bool
|
||||
let exampleText: String
|
||||
|
||||
init(
|
||||
label: String,
|
||||
parentCommand: String? = nil,
|
||||
commandName: String,
|
||||
includesAppName: Bool = true,
|
||||
example exampleText: String
|
||||
) {
|
||||
self.label = label
|
||||
self.parentCommand = parentCommand
|
||||
self.commandName = commandName
|
||||
self.includesAppName = includesAppName
|
||||
self.exampleText = exampleText
|
||||
}
|
||||
|
||||
var example: Example {
|
||||
var exampleString = "\(commandName) \(exampleText)"
|
||||
if let parentCommand {
|
||||
exampleString = "\(parentCommand) \(exampleString)"
|
||||
}
|
||||
if includesAppName {
|
||||
exampleString = "\(Application.commandName) \(exampleString)"
|
||||
}
|
||||
return (label: label, example: exampleString)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == AppExample {
|
||||
|
||||
func appendingExtraOptionsExample() -> Self {
|
||||
guard let first = first else { return self }
|
||||
var output = self
|
||||
output.append(.init(
|
||||
label: "Passing extra options to custom strategy.",
|
||||
parentCommand: first.parentCommand,
|
||||
commandName: first.commandName,
|
||||
includesAppName: first.includesAppName,
|
||||
example: "--custom-command -- git describe --tags --exact-match"
|
||||
))
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportantNote<Content: TextNode>: TextNode {
|
||||
let content: Content
|
||||
|
||||
init(
|
||||
@TextBuilder _ content: () -> Content
|
||||
) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: some TextNode {
|
||||
LabeledContent {
|
||||
content.italic()
|
||||
} label: {
|
||||
"Important Note:".red.bold
|
||||
}
|
||||
.style(.vertical())
|
||||
}
|
||||
}
|
||||
|
||||
extension ImportantNote where Content == String {
|
||||
static var extraOptionsNote: Self {
|
||||
.init {
|
||||
"""
|
||||
Extra options / flags for custom strategies must proceed a `--` or you may get an undefined option error.
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Usage where Content == AnyTextNode {
|
||||
static func `default`(parentCommand: String? = nil, commandName: String?) -> Self {
|
||||
var commandString = commandName == nil ? "" : "\(commandName!)"
|
||||
if let parentCommand {
|
||||
commandString = "\(parentCommand) \(commandString)"
|
||||
}
|
||||
commandString = commandString == "" ? "\(Application.commandName)" : "\(Application.commandName) \(commandString)"
|
||||
|
||||
return .init {
|
||||
HStack {
|
||||
commandString.blue
|
||||
"[<options>]".green
|
||||
"--"
|
||||
"[<extra-options> ...]".cyan
|
||||
}
|
||||
.eraseToAnyTextNode()
|
||||
}
|
||||
}
|
||||
}
|
||||
190
Sources/BumpVersion/Helpers/GlobalOptions+run.swift
Normal file
190
Sources/BumpVersion/Helpers/GlobalOptions+run.swift
Normal file
@@ -0,0 +1,190 @@
|
||||
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 CliClient.SharedOptions {
|
||||
func runClient<T>(
|
||||
_ keyPath: KeyPath<CliClient, @Sendable (Self) async throws -> T>
|
||||
) async throws -> T {
|
||||
try await withSetupDependencies {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
return try await cliClient[keyPath: keyPath](self)
|
||||
}
|
||||
}
|
||||
|
||||
func runClient<A, T>(
|
||||
_ keyPath: KeyPath<CliClient, @Sendable (A, Self) async throws -> T>,
|
||||
args: A
|
||||
) async throws -> T {
|
||||
try await withSetupDependencies {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
return try await cliClient[keyPath: keyPath](args, self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension GlobalOptions {
|
||||
|
||||
func run(
|
||||
_ keyPath: KeyPath<CliClient, @Sendable (CliClient.SharedOptions) async throws -> String>,
|
||||
command: String
|
||||
) async throws {
|
||||
let output = try await shared(command: command).runClient(keyPath)
|
||||
print(output)
|
||||
}
|
||||
|
||||
func run<T>(
|
||||
_ keyPath: KeyPath<CliClient, @Sendable (T, CliClient.SharedOptions) async throws -> String>,
|
||||
command: String,
|
||||
args: T
|
||||
) async throws {
|
||||
let output = try await shared(command: command).runClient(keyPath, args: args)
|
||||
print(output)
|
||||
}
|
||||
|
||||
func shared(command: String) throws -> CliClient.SharedOptions {
|
||||
try configOptions.shared(
|
||||
command: command,
|
||||
dryRun: dryRun,
|
||||
extraOptions: extraOptions,
|
||||
gitDirectory: gitDirectory,
|
||||
verbose: verbose
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension TargetOptions {
|
||||
func configTarget() throws -> Configuration.Target? {
|
||||
guard let targetFilePath else {
|
||||
guard let targetModule else {
|
||||
return nil
|
||||
}
|
||||
return .init(module: .init(targetModule, fileName: targetFileName))
|
||||
}
|
||||
return .init(path: targetFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
} else if let preReleasePrefix {
|
||||
return .init(prefix: preReleasePrefix, strategy: nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension SemVarOptions {
|
||||
|
||||
func parseStrategy(extraOptions: [String]) throws -> Configuration.SemVar.Strategy? {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
guard customCommand else {
|
||||
guard gitTag else { return nil }
|
||||
return .gitTag(exactMatch: requireExactMatch)
|
||||
}
|
||||
guard extraOptions.count > 0 else {
|
||||
logger.error("""
|
||||
Extra options are empty, this does not make sense when using a custom command
|
||||
strategy.
|
||||
""")
|
||||
throw ExtraOptionsEmpty()
|
||||
}
|
||||
return .command(arguments: extraOptions)
|
||||
}
|
||||
|
||||
func configSemVarOptions(
|
||||
includeCommitSha: Bool,
|
||||
extraOptions: [String]
|
||||
) 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.
|
||||
Ignoring pre-release...
|
||||
""")
|
||||
}
|
||||
|
||||
return try .init(
|
||||
allowPreRelease: !preRelease.disablePreRelease,
|
||||
preRelease: customCommand ? nil : preRelease.configPreReleaseStrategy(
|
||||
includeCommitSha: includeCommitSha,
|
||||
extraOptions: extraOptions
|
||||
),
|
||||
// Use nil here if false, which makes them not get used in json / file output, which makes
|
||||
// user config smaller.
|
||||
requireExistingFile: requireExistingFile ? true : nil,
|
||||
requireExistingSemVar: requireExistingSemvar ? true : nil,
|
||||
strategy: parseStrategy(extraOptions: extraOptions)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigurationOptions {
|
||||
|
||||
func target() throws -> Configuration.Target? {
|
||||
try targetOptions.configTarget()
|
||||
}
|
||||
|
||||
func semvarOptions(
|
||||
extraOptions: [String]
|
||||
) throws -> Configuration.SemVar {
|
||||
try semvarOptions.configSemVarOptions(
|
||||
includeCommitSha: commitSha,
|
||||
extraOptions: extraOptions
|
||||
)
|
||||
}
|
||||
|
||||
func shared(
|
||||
command: String,
|
||||
dryRun: Bool = true,
|
||||
extraOptions: [String] = [],
|
||||
gitDirectory: String? = nil,
|
||||
verbose: Int = 0
|
||||
) throws -> CliClient.SharedOptions {
|
||||
try .init(
|
||||
allowPreReleaseTag: !semvarOptions.preRelease.disablePreRelease,
|
||||
dryRun: dryRun,
|
||||
gitDirectory: gitDirectory,
|
||||
loggingOptions: .init(command: command, verbose: verbose),
|
||||
target: target(),
|
||||
branch: semvarOptions.gitTag ? nil : .init(includeCommitSha: commitSha),
|
||||
semvar: semvarOptions(extraOptions: extraOptions),
|
||||
configurationFile: configurationFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtraOptionsEmpty: Error {}
|
||||
2
Sources/BumpVersion/Version.swift
Normal file
2
Sources/BumpVersion/Version.swift
Normal file
@@ -0,0 +1,2 @@
|
||||
// Do not set this variable, it is set during the build process.
|
||||
let VERSION: String? = "0.2.1"
|
||||
Reference in New Issue
Block a user