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 {}