feat: Beginning to refactor cli-client
This commit is contained in:
@@ -2,6 +2,7 @@ disabled_rules:
|
|||||||
- closing_brace
|
- closing_brace
|
||||||
- fuction_body_length
|
- fuction_body_length
|
||||||
- opening_brace
|
- opening_brace
|
||||||
|
- nesting
|
||||||
|
|
||||||
included:
|
included:
|
||||||
- Sources
|
- Sources
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ let package = Package(
|
|||||||
.executable(name: "hpa", targets: ["hpa"]),
|
.executable(name: "hpa", targets: ["hpa"]),
|
||||||
.library(name: "CliClient", targets: ["CliClient"]),
|
.library(name: "CliClient", targets: ["CliClient"]),
|
||||||
.library(name: "CodersClient", targets: ["CodersClient"]),
|
.library(name: "CodersClient", targets: ["CodersClient"]),
|
||||||
|
.library(name: "CommandClient", targets: ["CommandClient"]),
|
||||||
.library(name: "ConfigurationClient", targets: ["ConfigurationClient"]),
|
.library(name: "ConfigurationClient", targets: ["ConfigurationClient"]),
|
||||||
.library(name: "FileClient", targets: ["FileClient"]),
|
.library(name: "FileClient", targets: ["FileClient"]),
|
||||||
.library(name: "PlaybookClient", targets: ["PlaybookClient"])
|
.library(name: "PlaybookClient", targets: ["PlaybookClient"])
|
||||||
@@ -60,6 +61,15 @@ let package = Package(
|
|||||||
.product(name: "TOMLKit", package: "TOMLKit")
|
.product(name: "TOMLKit", package: "TOMLKit")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "CommandClient",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
.product(name: "ShellClient", package: "swift-shell-client")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(name: "Constants"),
|
||||||
.target(
|
.target(
|
||||||
name: "TestSupport",
|
name: "TestSupport",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -94,6 +104,13 @@ let package = Package(
|
|||||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "LoggingExtensions",
|
||||||
|
dependencies: [
|
||||||
|
"Constants",
|
||||||
|
.product(name: "ShellClient", package: "swift-shell-client")
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "PlaybookClient",
|
name: "PlaybookClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|||||||
@@ -149,31 +149,6 @@ public extension ConfigurationClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @_spi(Internal)
|
|
||||||
// public extension Configuration {
|
|
||||||
|
|
||||||
// func defaultPlaybookDirectory() throws -> String {
|
|
||||||
// let playbookDirectory = Bundle.module.url(
|
|
||||||
// forResource: Constants.playbookBundleDirectoryName,
|
|
||||||
// withExtension: nil
|
|
||||||
// )
|
|
||||||
// guard let playbookDirectory else {
|
|
||||||
// throw CliClientError.playbookDirectoryNotFound
|
|
||||||
// }
|
|
||||||
// return playbookDirectory.cleanFilePath
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ensuredPlaybookDirectory(_ optionalDirectory: String?) throws -> String {
|
|
||||||
// guard let directory = optionalDirectory else {
|
|
||||||
// guard let directory = playbook?.directory else {
|
|
||||||
// return try defaultPlaybookDirectory()
|
|
||||||
// }
|
|
||||||
// return directory
|
|
||||||
// }
|
|
||||||
// return directory
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
public extension Optional where Wrapped == String {
|
public extension Optional where Wrapped == String {
|
||||||
var orDefault: ShellCommand.Shell {
|
var orDefault: ShellCommand.Shell {
|
||||||
|
|||||||
216
Sources/CliClient/CliClient+RunPlaybook.swift
Normal file
216
Sources/CliClient/CliClient+RunPlaybook.swift
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import ConfigurationClient
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import PlaybookClient
|
||||||
|
|
||||||
|
extension CliClient.RunPlaybook {
|
||||||
|
|
||||||
|
static func makeCommonArguments(
|
||||||
|
configuration: Configuration,
|
||||||
|
inventoryFilePath: String?
|
||||||
|
) async throws -> [String] {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
|
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
||||||
|
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||||
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
|
let inventoryPath = ensuredInventoryPath(
|
||||||
|
inventoryFilePath,
|
||||||
|
configuration: configuration,
|
||||||
|
playbookDirectory: playbookDirectory
|
||||||
|
)
|
||||||
|
logger.trace("Inventory path: \(inventoryPath)")
|
||||||
|
|
||||||
|
var arguments = [
|
||||||
|
Constants.playbookCommand, playbookPath,
|
||||||
|
"--inventory", inventoryPath
|
||||||
|
]
|
||||||
|
|
||||||
|
if let defaultArgs = configuration.args {
|
||||||
|
arguments.append(contentsOf: defaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||||
|
arguments.append(contentsOf: vaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Common arguments: \(inventoryPath)")
|
||||||
|
return arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CliClient.RunPlaybook.BuildOptions {
|
||||||
|
private func applyArguments(
|
||||||
|
to arguments: inout [String],
|
||||||
|
configuration: Configuration
|
||||||
|
) throws {
|
||||||
|
let projectDirectory = projectDirectory
|
||||||
|
?? ProcessInfo.processInfo.environment["PWD"]
|
||||||
|
|
||||||
|
guard let projectDirectory else {
|
||||||
|
throw CliClientError.projectDirectoryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.append(contentsOf: [
|
||||||
|
"--tags", "build-project",
|
||||||
|
"--extra-vars", "project_dir=\(projectDirectory)"
|
||||||
|
])
|
||||||
|
|
||||||
|
if let extraOptions = extraOptions {
|
||||||
|
arguments.append(contentsOf: extraOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CliClient.RunPlaybook.CreateOptions {
|
||||||
|
private func applyArguments(
|
||||||
|
to arguments: inout [String],
|
||||||
|
configuration: Configuration
|
||||||
|
) throws {
|
||||||
|
let json = try createJSONData(configuration: configuration)
|
||||||
|
|
||||||
|
arguments.append(contentsOf: [
|
||||||
|
"--tags", "setup-project",
|
||||||
|
"--extra-vars", "project_dir=\(projectDirectory)",
|
||||||
|
"--extra-vars", "'\(json)'"
|
||||||
|
])
|
||||||
|
|
||||||
|
if let extraOptions {
|
||||||
|
arguments.append(contentsOf: extraOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CliClient.PlaybookOptions.Route {
|
||||||
|
|
||||||
|
private func parseInventoryPath(
|
||||||
|
_ configuration: Configuration,
|
||||||
|
_ playbookDirectory: String
|
||||||
|
) -> String {
|
||||||
|
let inventoryFilePath: String?
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case let .build(options):
|
||||||
|
inventoryFilePath = options.inventoryFilePath
|
||||||
|
case let .create(options):
|
||||||
|
inventoryFilePath = options.inventoryFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
return ensuredInventoryPath(
|
||||||
|
inventoryFilePath,
|
||||||
|
configuration: configuration,
|
||||||
|
playbookDirectory: playbookDirectory
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeArguments(configuration: Configuration) async throws -> [String] {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
|
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
||||||
|
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||||
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
|
let inventoryPath = parseInventoryPath(configuration, playbookDirectory)
|
||||||
|
logger.trace("Inventory path: \(inventoryPath)")
|
||||||
|
|
||||||
|
var arguments = [
|
||||||
|
Constants.playbookCommand, playbookPath,
|
||||||
|
"--inventory", inventoryPath
|
||||||
|
]
|
||||||
|
|
||||||
|
if let defaultArgs = configuration.args {
|
||||||
|
arguments.append(contentsOf: defaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||||
|
arguments.append(contentsOf: vaultArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try applyArguments(to: &arguments, configuration: configuration)
|
||||||
|
|
||||||
|
return arguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We're not using the `Coders` client because we generally do not
|
||||||
|
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||||
|
// tests, so we use a supplied json encoder.
|
||||||
|
|
||||||
|
extension CliClient.RunPlaybook.CreateOptions {
|
||||||
|
|
||||||
|
func createJSONData(
|
||||||
|
configuration: Configuration,
|
||||||
|
encoder: JSONEncoder = .init()
|
||||||
|
) throws -> Data {
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let templateDir = template.directory ?? configuration.template.directory
|
||||||
|
let templateRepo = template.url ?? configuration.template.url
|
||||||
|
let version = template.version ?? configuration.template.version
|
||||||
|
|
||||||
|
logger.debug("""
|
||||||
|
(\(useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
||||||
|
""")
|
||||||
|
|
||||||
|
switch (useLocalTemplateDirectory, templateDir, templateRepo) {
|
||||||
|
case (true, .none, _):
|
||||||
|
// User supplied they wanted to use a local template directory, but we could not find
|
||||||
|
// the path set from command line or in configuration.
|
||||||
|
throw CliClientError.templateDirectoryNotFound
|
||||||
|
case let (false, _, .some(repo)):
|
||||||
|
// User did not supply they wanted to use a local template directory, and we found a repo url that was
|
||||||
|
// either set by the command line or found in the configuration.
|
||||||
|
logger.debug("Using repo.")
|
||||||
|
return try encoder.encode(TemplateRepo(repo: repo, version: version))
|
||||||
|
case let (true, .some(templateDir), _):
|
||||||
|
// User supplied they wanted to use a local template directory, and we found the template directory
|
||||||
|
// either set by the command line or in the configuration.
|
||||||
|
logger.debug("Using template directory.")
|
||||||
|
return try encoder.encode(TemplateDirJson(path: templateDir))
|
||||||
|
case let (false, .some(templateDir), _):
|
||||||
|
// User supplied they did not wanted to use a local template directory, and we found the template directory
|
||||||
|
// either set by the command line or in the configuration, and no repo was found / handled previously.
|
||||||
|
logger.debug("Using template directory.")
|
||||||
|
return try encoder.encode(TemplateDirJson(path: templateDir))
|
||||||
|
case (_, .none, .none):
|
||||||
|
// We could not find a repo or template directory.
|
||||||
|
throw CliClientError.templateDirectoryOrRepoNotSpecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TemplateDirJson: Encodable {
|
||||||
|
|
||||||
|
let template: Template
|
||||||
|
|
||||||
|
init(path: String) {
|
||||||
|
self.template = .init(path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Template: Encodable {
|
||||||
|
let path: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TemplateRepo: Encodable {
|
||||||
|
|
||||||
|
let template: Template
|
||||||
|
|
||||||
|
init(repo: String, version: String?) {
|
||||||
|
self.template = .init(repo: .init(url: repo, version: version ?? "main"))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Template: Encodable {
|
||||||
|
let repo: Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Repo: Encodable {
|
||||||
|
let url: String
|
||||||
|
let version: String
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ public enum CliClientError: Error {
|
|||||||
case brewfileNotFound
|
case brewfileNotFound
|
||||||
case encodingError
|
case encodingError
|
||||||
case playbookDirectoryNotFound
|
case playbookDirectoryNotFound
|
||||||
|
case projectDirectoryNotFound
|
||||||
case generate(GenerateError)
|
case generate(GenerateError)
|
||||||
case templateDirectoryNotFound
|
case templateDirectoryNotFound
|
||||||
case templateDirectoryOrRepoNotSpecified
|
case templateDirectoryOrRepoNotSpecified
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Foundation
|
|||||||
// want the output to be `prettyPrinted` or anything, unless we're running
|
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||||
// tests, so we use a supplied json encoder.
|
// tests, so we use a supplied json encoder.
|
||||||
|
|
||||||
|
// TODO: Remove.
|
||||||
func createJSONData(
|
func createJSONData(
|
||||||
_ options: CliClient.GenerateJsonOptions,
|
_ options: CliClient.GenerateJsonOptions,
|
||||||
logging loggingOptions: CliClient.LoggingOptions,
|
logging loggingOptions: CliClient.LoggingOptions,
|
||||||
|
|||||||
@@ -25,6 +25,60 @@ public struct CliClient: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension CliClient {
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
struct RunPlaybook: Sendable {
|
||||||
|
public var buildProject: @Sendable (RunOptions, BuildOptions) async throws -> Void
|
||||||
|
public var createProject: @Sendable (RunOptions, CreateOptions) async throws -> String
|
||||||
|
|
||||||
|
public struct RunOptions: Equatable, Sendable {
|
||||||
|
public let loggingOptions: CliClient.LoggingOptions
|
||||||
|
public let quiet: Bool
|
||||||
|
public let shell: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct BuildOptions: Equatable, Sendable {
|
||||||
|
public let extraOptions: [String]?
|
||||||
|
public let inventoryFilePath: String?
|
||||||
|
public let projectDirectory: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
projectDirectory: String
|
||||||
|
) {
|
||||||
|
self.extraOptions = extraOptions
|
||||||
|
self.inventoryFilePath = inventoryFilePath
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CreateOptions: Equatable, Sendable {
|
||||||
|
public let extraOptions: [String]?
|
||||||
|
public let inventoryFilePath: String?
|
||||||
|
public let projectDirectory: String
|
||||||
|
public let template: Configuration.Template
|
||||||
|
public let useLocalTemplateDirectory: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
projectDirectory: String,
|
||||||
|
template: Configuration.Template,
|
||||||
|
useLocalTemplateDirectory: Bool
|
||||||
|
) {
|
||||||
|
self.extraOptions = extraOptions
|
||||||
|
self.inventoryFilePath = inventoryFilePath
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.template = template
|
||||||
|
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public extension CliClient {
|
public extension CliClient {
|
||||||
|
|
||||||
struct PandocOptions: Equatable, Sendable {
|
struct PandocOptions: Equatable, Sendable {
|
||||||
@@ -125,6 +179,50 @@ public extension CliClient {
|
|||||||
self.quiet = quiet
|
self.quiet = quiet
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Route {
|
||||||
|
case build(BuildOption)
|
||||||
|
case create(CreateOption)
|
||||||
|
|
||||||
|
public struct BuildOption: Equatable, Sendable {
|
||||||
|
public let extraOptions: [String]?
|
||||||
|
public let inventoryFilePath: String?
|
||||||
|
public let projectDirectory: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
projectDirectory: String
|
||||||
|
) {
|
||||||
|
self.extraOptions = extraOptions
|
||||||
|
self.inventoryFilePath = inventoryFilePath
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CreateOption: Equatable, Sendable {
|
||||||
|
public let extraOptions: [String]?
|
||||||
|
public let inventoryFilePath: String?
|
||||||
|
public let projectDirectory: String
|
||||||
|
public let template: Configuration.Template
|
||||||
|
public let useLocalTemplateDirectory: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
extraOptions: [String]?,
|
||||||
|
inventoryFilePath: String?,
|
||||||
|
projectDirectory: String,
|
||||||
|
template: Configuration.Template,
|
||||||
|
useLocalTemplateDirectory: Bool
|
||||||
|
) {
|
||||||
|
self.extraOptions = extraOptions
|
||||||
|
self.inventoryFilePath = inventoryFilePath
|
||||||
|
self.projectDirectory = projectDirectory
|
||||||
|
self.template = template
|
||||||
|
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RunCommandOptions: Sendable, Equatable {
|
struct RunCommandOptions: Sendable, Equatable {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Dependencies
|
|||||||
import Logging
|
import Logging
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
|
// TODO: Remove.
|
||||||
public extension CliClient {
|
public extension CliClient {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|||||||
113
Sources/CommandClient/CommandClient.swift
Normal file
113
Sources/CommandClient/CommandClient.swift
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import Foundation
|
||||||
|
import ShellClient
|
||||||
|
|
||||||
|
public extension DependencyValues {
|
||||||
|
|
||||||
|
/// Runs shell commands.
|
||||||
|
var commandClient: CommandClient {
|
||||||
|
get { self[CommandClient.self] }
|
||||||
|
set { self[CommandClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct CommandClient: Sendable {
|
||||||
|
|
||||||
|
/// Runs a shell command.
|
||||||
|
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
||||||
|
|
||||||
|
/// Runs a shell command.
|
||||||
|
public func run(
|
||||||
|
quiet: Bool,
|
||||||
|
shell: ShellCommand.Shell,
|
||||||
|
_ arguments: [String]
|
||||||
|
) async throws {
|
||||||
|
try await runCommand(.init(arguments: arguments, quiet: quiet, shell: shell))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs a shell command.
|
||||||
|
public func run(
|
||||||
|
quiet: Bool,
|
||||||
|
shell: ShellCommand.Shell,
|
||||||
|
_ arguments: String...
|
||||||
|
) async throws {
|
||||||
|
try await run(quiet: quiet, shell: shell, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RunCommandOptions: Sendable, Equatable {
|
||||||
|
public let arguments: [String]
|
||||||
|
public let quiet: Bool
|
||||||
|
public let shell: ShellCommand.Shell
|
||||||
|
|
||||||
|
public init(
|
||||||
|
arguments: [String],
|
||||||
|
quiet: Bool,
|
||||||
|
shell: ShellCommand.Shell
|
||||||
|
) {
|
||||||
|
self.arguments = arguments
|
||||||
|
self.quiet = quiet
|
||||||
|
self.shell = shell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CommandClient: DependencyKey {
|
||||||
|
|
||||||
|
public static let testValue: CommandClient = Self()
|
||||||
|
|
||||||
|
public static var liveValue: CommandClient {
|
||||||
|
.init { options in
|
||||||
|
@Dependency(\.asyncShellClient) var shellClient
|
||||||
|
if !options.quiet {
|
||||||
|
try await shellClient.foreground(.init(
|
||||||
|
shell: options.shell,
|
||||||
|
environment: ProcessInfo.processInfo.environment,
|
||||||
|
in: nil,
|
||||||
|
options.arguments
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
try await shellClient.background(.init(
|
||||||
|
shell: options.shell,
|
||||||
|
environment: ProcessInfo.processInfo.environment,
|
||||||
|
in: nil,
|
||||||
|
options.arguments
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension CommandClient {
|
||||||
|
|
||||||
|
/// Create a command client that can capture the arguments / options.
|
||||||
|
///
|
||||||
|
/// This is used for testing.
|
||||||
|
static func capturing(_ client: CapturingClient) -> Self {
|
||||||
|
.init { options in
|
||||||
|
await client.set(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Captures the arguments / options passed into the command client's run commands.
|
||||||
|
///
|
||||||
|
actor CapturingClient: Sendable {
|
||||||
|
public private(set) var quiet: Bool?
|
||||||
|
public private(set) var shell: ShellCommand.Shell?
|
||||||
|
public private(set) var arguments: [String]?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func set(
|
||||||
|
_ options: RunCommandOptions
|
||||||
|
) {
|
||||||
|
quiet = options.quiet
|
||||||
|
shell = options.shell
|
||||||
|
arguments = options.arguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShellCommand.Shell: @retroactive @unchecked Sendable {}
|
||||||
3
Sources/Constants/Constants.swift
Normal file
3
Sources/Constants/Constants.swift
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
public enum Constants {
|
||||||
|
public static let executableName = "hpa"
|
||||||
|
}
|
||||||
27
Sources/LoggingExtensions/LoggingExtensions.swift
Normal file
27
Sources/LoggingExtensions/LoggingExtensions.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import Constants
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import ShellClient
|
||||||
|
|
||||||
|
public struct LoggingOptions: Equatable, Sendable {
|
||||||
|
public let commandName: String
|
||||||
|
public let logLevel: Logger.Level
|
||||||
|
|
||||||
|
public init(commandName: String, logLevel: Logger.Level) {
|
||||||
|
self.commandName = commandName
|
||||||
|
self.logLevel = logLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func withLogger<T>(
|
||||||
|
operation: @Sendable @escaping () async throws -> T
|
||||||
|
) async rethrows -> T {
|
||||||
|
try await withDependencies {
|
||||||
|
$0.logger = .init(label: "\(Constants.executableName)")
|
||||||
|
$0.logger.logLevel = logLevel
|
||||||
|
$0.logger[metadataKey: "command"] = "\(commandName.blue)"
|
||||||
|
} operation: {
|
||||||
|
try await operation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ public extension DependencyValues {
|
|||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct PlaybookClient: Sendable {
|
public struct PlaybookClient: Sendable {
|
||||||
|
|
||||||
|
// TODO: Remove the configuration and have it passed in.
|
||||||
public var installPlaybook: @Sendable (Configuration) async throws -> Void
|
public var installPlaybook: @Sendable (Configuration) async throws -> Void
|
||||||
public var playbookDirectory: @Sendable (Configuration) async throws -> String
|
public var playbookDirectory: @Sendable (Configuration) async throws -> String
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user