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 decoder: @Sendable () -> JSONDecoder = { .init() } public var encoder: @Sendable () -> JSONEncoder = { .init() } public var loadConfiguration: @Sendable () throws -> Configuration public var runCommand: @Sendable ([String], Bool, ShellCommand.Shell) async throws -> Void public func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: [String] ) async throws { try await runCommand(args, quiet, shell) } public func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: String... ) async throws { try await runCommand(args, quiet, shell) } } extension CliClient: DependencyKey { public static func live( decoder: JSONDecoder = .init(), encoder: JSONEncoder = .init(), env: [String: String] ) -> Self { .init { decoder } encoder: { encoder } loadConfiguration: { @Dependency(\.logger) var logger @Dependency(\.fileClient) var fileClient let urls = try findConfigurationFiles(env: env) var env = env logger.trace("Loading configuration from: \(urls)") for url in urls { try fileClient.loadFile(url, &env, decoder) } return try .fromEnv(env, encoder: encoder, decoder: decoder) } runCommand: { args, quiet, shell in @Dependency(\.asyncShellClient) var shellClient if !quiet { try await shellClient.foreground(.init( shell: shell, environment: ProcessInfo.processInfo.environment, in: nil, args )) } try await shellClient.background(.init( shell: shell, environment: ProcessInfo.processInfo.environment, in: nil, args )) } } public static var liveValue: CliClient { .live(env: ProcessInfo.processInfo.environment) } public static let testValue: CliClient = Self() }