import ConfigurationClient import Dependencies import DependenciesMacros import Foundation import ShellClient public extension DependencyValues { var cliClient: CliClient { get { self[CliClient.self] } set { self[CliClient.self] = newValue } } } @DependencyClient public struct CliClient: Sendable { public var runCommand: @Sendable (RunCommandOptions) async throws -> Void public var generateJSON: @Sendable (GenerateJsonOptions, LoggingOptions, JSONEncoder) async throws -> String public func generateJSON( _ options: GenerateJsonOptions, logging loggingOptions: LoggingOptions, encoder jsonEncoder: JSONEncoder = .init() ) async throws -> String { try await generateJSON(options, loggingOptions, jsonEncoder) } } public extension CliClient { struct GenerateJsonOptions: Equatable, Sendable { let templateDirectory: String? let templateRepo: String? let version: String? let useLocalTemplateDirectory: Bool public init( templateDirectory: String?, templateRepo: String?, version: String?, useLocalTemplateDirectory: Bool ) { self.templateDirectory = templateDirectory self.templateRepo = templateRepo self.version = version self.useLocalTemplateDirectory = useLocalTemplateDirectory } } struct LoggingOptions: Equatable, Sendable { let commandName: String let logLevel: Logger.Level public init(commandName: String, logLevel: Logger.Level) { self.commandName = commandName self.logLevel = logLevel } } struct PlaybookOptions: Sendable, Equatable { let arguments: [String] let configuration: Configuration? let inventoryFilePath: String? let playbookDirectory: String? let quiet: Bool let shell: String? public init( arguments: [String], configuration: Configuration? = nil, inventoryFilePath: String? = nil, playbookDirectory: String? = nil, quiet: Bool, shell: String? = nil ) { self.arguments = arguments self.configuration = configuration self.inventoryFilePath = inventoryFilePath self.playbookDirectory = playbookDirectory self.quiet = quiet self.shell = shell } } 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 } } struct VaultOptions: Equatable, Sendable { let arguments: [String] let configuration: Configuration? let quiet: Bool let shell: String? let vaultFilePath: String? public init( arguments: [String], configuration: Configuration? = nil, quiet: Bool, shell: String?, vaultFilePath: String? = nil ) { self.arguments = arguments self.configuration = configuration self.quiet = quiet self.shell = shell self.vaultFilePath = vaultFilePath } } } extension CliClient: DependencyKey { public static func live( env: [String: String] ) -> Self { @Dependency(\.logger) var logger return .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 )) } } generateJSON: { options, loggingOptions, encoder in let data = try await createJSONData(options, logging: loggingOptions, encoder: encoder) guard let string = String(data: data, encoding: .utf8) else { throw CliClientError.encodingError } return string } } public static var liveValue: CliClient { .live(env: ProcessInfo.processInfo.environment) } public static let testValue: CliClient = Self() public static func capturing(_ client: CapturingClient) -> Self { .init { options in await client.set(options) } generateJSON: { try await Self().generateJSON($0, $1, $2) } } public 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 } } }