feat: Preparing to move items out of cli-client module into playbook-client
This commit is contained in:
@@ -47,7 +47,7 @@ public extension CliClient {
|
||||
)
|
||||
|
||||
let configuration = try await configurationClient.findAndLoad()
|
||||
try await playbookClient.installPlaybook(configuration)
|
||||
try await playbookClient.repository.install(configuration)
|
||||
}
|
||||
|
||||
func runPlaybookCommand(
|
||||
@@ -62,7 +62,7 @@ public extension CliClient {
|
||||
let configuration = try await configurationClient.ensuredConfiguration(options.configuration)
|
||||
logger.trace("Configuration: \(configuration)")
|
||||
|
||||
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ extension CliClient.RunPlaybook {
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
@@ -111,7 +111,7 @@ extension CliClient.PlaybookOptions.Route {
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Constants
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
import LoggingExtensions
|
||||
import ShellClient
|
||||
|
||||
public extension DependencyValues {
|
||||
@@ -18,37 +20,93 @@ public struct CommandClient: Sendable {
|
||||
/// Runs a shell command.
|
||||
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
||||
|
||||
/// Runs a shell command.
|
||||
/// Runs a shell command and sets up logging.
|
||||
public func run(
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell,
|
||||
_ arguments: [String]
|
||||
logging logginOptions: LoggingOptions,
|
||||
quiet: Bool = false,
|
||||
shell: String? = nil,
|
||||
in workingDirectory: String? = nil,
|
||||
makeArguments: @Sendable @escaping () async throws -> [String]
|
||||
) async throws {
|
||||
try await runCommand(.init(arguments: arguments, quiet: quiet, shell: shell))
|
||||
try await logginOptions.withLogger {
|
||||
let arguments = try await makeArguments()
|
||||
try await self.run(
|
||||
quiet: quiet,
|
||||
shell: shell,
|
||||
arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a shell command.
|
||||
public func run(
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell,
|
||||
quiet: Bool = false,
|
||||
shell: String? = nil,
|
||||
in workingDirectory: String? = nil,
|
||||
_ arguments: [String]
|
||||
) async throws {
|
||||
try await runCommand(.init(
|
||||
arguments: arguments,
|
||||
quiet: quiet,
|
||||
shell: shell,
|
||||
workingDirectory: workingDirectory
|
||||
))
|
||||
}
|
||||
|
||||
/// Runs a shell command.
|
||||
public func run(
|
||||
quiet: Bool = false,
|
||||
shell: String? = nil,
|
||||
in workingDirectory: String? = nil,
|
||||
_ arguments: String...
|
||||
) async throws {
|
||||
try await run(quiet: quiet, shell: shell, arguments)
|
||||
try await run(
|
||||
quiet: quiet,
|
||||
shell: shell,
|
||||
in: workingDirectory,
|
||||
arguments
|
||||
)
|
||||
}
|
||||
|
||||
public struct RunCommandOptions: Sendable, Equatable {
|
||||
public let arguments: [String]
|
||||
public let quiet: Bool
|
||||
public let shell: ShellCommand.Shell
|
||||
public let shell: String?
|
||||
public let workingDirectory: String?
|
||||
|
||||
public init(
|
||||
arguments: [String],
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell
|
||||
shell: String? = nil,
|
||||
workingDirectory: String? = nil
|
||||
) {
|
||||
self.arguments = arguments
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
self.workingDirectory = workingDirectory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,26 +115,32 @@ extension CommandClient: DependencyKey {
|
||||
|
||||
public static let testValue: CommandClient = Self()
|
||||
|
||||
public static var liveValue: CommandClient {
|
||||
public static func live(
|
||||
environment: [String: String]
|
||||
) -> 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,
|
||||
shell: .init(options.shell),
|
||||
environment: environment,
|
||||
in: options.workingDirectory,
|
||||
options.arguments
|
||||
))
|
||||
} else {
|
||||
try await shellClient.background(.init(
|
||||
shell: options.shell,
|
||||
environment: ProcessInfo.processInfo.environment,
|
||||
in: nil,
|
||||
shell: .init(options.shell),
|
||||
environment: environment,
|
||||
in: options.workingDirectory,
|
||||
options.arguments
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var liveValue: CommandClient {
|
||||
.live(environment: ProcessInfo.processInfo.environment)
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
@@ -93,21 +157,32 @@ public extension CommandClient {
|
||||
|
||||
/// Captures the arguments / options passed into the command client's run commands.
|
||||
///
|
||||
@dynamicMemberLookup
|
||||
actor CapturingClient: Sendable {
|
||||
public private(set) var quiet: Bool?
|
||||
public private(set) var shell: ShellCommand.Shell?
|
||||
public private(set) var arguments: [String]?
|
||||
public private(set) var options: RunCommandOptions?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func set(
|
||||
_ options: RunCommandOptions
|
||||
) {
|
||||
quiet = options.quiet
|
||||
shell = options.shell
|
||||
arguments = options.arguments
|
||||
self.options = options
|
||||
}
|
||||
|
||||
public subscript<T>(dynamicMember keyPath: KeyPath<RunCommandOptions, T>) -> T? {
|
||||
options?[keyPath: keyPath]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ShellCommand.Shell: @retroactive @unchecked Sendable {}
|
||||
|
||||
extension ShellCommand.Shell {
|
||||
init(_ path: String?) {
|
||||
if let path {
|
||||
self = .custom(path: path, useDashC: true)
|
||||
} else {
|
||||
self = .zsh(useDashC: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Sources/PlaybookClient/PlaybookClient+Repository.swift
Normal file
79
Sources/PlaybookClient/PlaybookClient+Repository.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
import ShellClient
|
||||
|
||||
extension PlaybookClient.Repository {
|
||||
|
||||
static func findDirectory(
|
||||
configuration: Configuration? = nil
|
||||
) async throws -> String {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
|
||||
var configuration: Configuration! = configuration
|
||||
if configuration == nil {
|
||||
configuration = try await configurationClient.findAndLoad()
|
||||
}
|
||||
|
||||
return configuration.playbook?.directory
|
||||
?? PlaybookClient.Constants.defaultInstallationPath
|
||||
}
|
||||
|
||||
static func installPlaybook(
|
||||
configuration: Configuration?
|
||||
) async throws {
|
||||
@Dependency(\.commandClient) var commandClient
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
var configuration: Configuration! = configuration
|
||||
|
||||
if configuration == nil {
|
||||
configuration = try await configurationClient.findAndLoad()
|
||||
}
|
||||
|
||||
let (path, version) = parsePlaybookPathAndVerion(
|
||||
configuration?.playbook
|
||||
)
|
||||
|
||||
let parentDirectory = URL(filePath: path)
|
||||
.deletingLastPathComponent()
|
||||
|
||||
let parentExists = try await fileClient.isDirectory(parentDirectory)
|
||||
if !parentExists {
|
||||
try await fileClient.createDirectory(parentDirectory)
|
||||
}
|
||||
|
||||
let playbookExists = try await fileClient.isDirectory(URL(filePath: path))
|
||||
|
||||
if !playbookExists {
|
||||
logger.debug("Cloning playbook to: \(path)")
|
||||
try await commandClient.run(
|
||||
"git", "clone", "--branch", version,
|
||||
PlaybookClient.Constants.playbookRepoUrl, path
|
||||
)
|
||||
} else {
|
||||
logger.debug("Playbook exists, ensuring it's up to date.")
|
||||
try await commandClient.run(
|
||||
in: path,
|
||||
"git", "pull", "--tags"
|
||||
)
|
||||
try await commandClient.run(
|
||||
in: path,
|
||||
"git", "checkout", version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func parsePlaybookPathAndVerion(
|
||||
_ configuration: Configuration.Playbook?
|
||||
) -> (path: String, version: String) {
|
||||
return (
|
||||
path: configuration?.directory ?? PlaybookClient.Constants.defaultInstallationPath,
|
||||
version: configuration?.version ?? PlaybookClient.Constants.playbookRepoVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,64 +16,40 @@ public extension DependencyValues {
|
||||
|
||||
@DependencyClient
|
||||
public struct PlaybookClient: Sendable {
|
||||
|
||||
// TODO: Remove the configuration and have it passed in.
|
||||
public var installPlaybook: @Sendable (Configuration) async throws -> Void
|
||||
public var playbookDirectory: @Sendable (Configuration) async throws -> String
|
||||
|
||||
public var repository: Repository
|
||||
}
|
||||
|
||||
extension PlaybookClient: DependencyKey {
|
||||
public static let testValue: PlaybookClient = Self()
|
||||
public extension PlaybookClient {
|
||||
|
||||
public static var liveValue: PlaybookClient {
|
||||
.init {
|
||||
try await install(config: $0.playbook)
|
||||
} playbookDirectory: {
|
||||
$0.playbook?.directory ?? Constants.defaultInstallationPath
|
||||
@DependencyClient
|
||||
struct Repository: Sendable {
|
||||
public var install: @Sendable (Configuration?) async throws -> Void
|
||||
public var directory: @Sendable (Configuration?) async throws -> String
|
||||
|
||||
public func install() async throws {
|
||||
try await install(nil)
|
||||
}
|
||||
|
||||
public func directory() async throws -> String {
|
||||
try await directory(nil)
|
||||
}
|
||||
|
||||
public static var liveValue: Self {
|
||||
.init {
|
||||
try await installPlaybook(configuration: $0)
|
||||
} directory: {
|
||||
try await findDirectory(configuration: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func install(config: Configuration.Playbook?) async throws {
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.asyncShellClient) var shell
|
||||
extension PlaybookClient: DependencyKey {
|
||||
public static let testValue: PlaybookClient = Self(repository: Repository())
|
||||
|
||||
let (path, version) = parsePlaybookPathAndVerion(config)
|
||||
|
||||
let parentDirectory = URL(filePath: path)
|
||||
.deletingLastPathComponent()
|
||||
|
||||
let parentExists = try await fileClient.isDirectory(parentDirectory)
|
||||
if !parentExists {
|
||||
try await fileClient.createDirectory(parentDirectory)
|
||||
}
|
||||
|
||||
let playbookExists = try await fileClient.isDirectory(URL(filePath: path))
|
||||
|
||||
if !playbookExists {
|
||||
try await shell.foreground(.init([
|
||||
"git", "clone",
|
||||
"--branch", version,
|
||||
PlaybookClient.Constants.playbookRepoUrl, path
|
||||
]))
|
||||
} else {
|
||||
logger.debug("Playbook exists, ensuring it's up to date.")
|
||||
try await shell.foreground(.init(
|
||||
in: path,
|
||||
["git", "pull", "--tags"]
|
||||
))
|
||||
try await shell.foreground(.init(
|
||||
in: path,
|
||||
["git", "checkout", version]
|
||||
))
|
||||
public static var liveValue: PlaybookClient {
|
||||
.init(
|
||||
repository: .liveValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func parsePlaybookPathAndVerion(_ configuration: Configuration.Playbook?) -> (path: String, version: String) {
|
||||
return (
|
||||
path: configuration?.directory ?? PlaybookClient.Constants.defaultInstallationPath,
|
||||
version: configuration?.version ?? PlaybookClient.Constants.playbookRepoVersion
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user