import ConfigurationClient import Dependencies import DependenciesMacros import FileClient import Foundation import PlaybookClient import ShellClient public extension CliClient { func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: [String] ) async throws { try await runCommand(.init(arguments: args, quiet: quiet, shell: shell)) } func runCommand( quiet: Bool, shell: ShellCommand.Shell, _ args: String... ) async throws { try await runCommand(quiet: quiet, shell: shell, args) } func installDependencies( quiet: Bool = false, shell: String? = nil, extraArgs: [String]? = nil ) async throws { @Dependency(\.playbookClient) var playbookClient @Dependency(\.configurationClient) var configurationClient var arguments = [ "brew", "install" ] + Constants.brewPackages if let extraArgs { arguments.append(contentsOf: extraArgs) } try await runCommand( quiet: quiet, shell: shell.orDefault, arguments ) let configuration = try await configurationClient.findAndLoad() try await playbookClient.repository.install(configuration) } func runPlaybookCommand( _ options: PlaybookOptions, logging loggingOptions: LoggingOptions ) async throws { try await withLogger(loggingOptions) { @Dependency(\.configurationClient) var configurationClient @Dependency(\.logger) var logger @Dependency(\.playbookClient) var playbookClient let configuration = try await configurationClient.ensuredConfiguration(options.configuration) logger.trace("Configuration: \(configuration)") let playbookDirectory = try await playbookClient.repository.directory(configuration) let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)" logger.trace("Playbook path: \(playbookPath)") let inventoryPath = ensuredInventoryPath( options.inventoryFilePath, configuration: configuration, playbookDirectory: playbookDirectory ) logger.trace("Inventory path: \(inventoryPath)") var arguments = [ Constants.playbookCommand, playbookPath, "--inventory", inventoryPath ] + options.arguments if let defaultArgs = configuration.args { arguments.append(contentsOf: defaultArgs) } if configuration.useVaultArgs, let vaultArgs = configuration.vault.args { arguments.append(contentsOf: vaultArgs) } logger.trace("Running playbook command with arguments: \(arguments)") try await runCommand( quiet: options.quiet, shell: options.shell.orDefault, arguments ) } } func runVaultCommand( _ options: VaultOptions, logging loggingOptions: LoggingOptions ) async throws { try await withLogger(loggingOptions) { @Dependency(\.configurationClient) var configurationClient @Dependency(\.fileClient) var fileClient @Dependency(\.logger) var logger let configuration = try await configurationClient.ensuredConfiguration(options.configuration) logger.trace("Configuration: \(configuration)") let vaultFilePath = try await fileClient.ensuredVaultFilePath(options.vaultFilePath) logger.trace("Vault file: \(vaultFilePath)") var arguments = [ Constants.vaultCommand ] + options.arguments if let defaultArgs = configuration.vault.args { arguments.append(contentsOf: defaultArgs) } if arguments.contains("encrypt"), !arguments.contains("--encrypt-vault-id"), let id = configuration.vault.encryptId { arguments.append(contentsOf: ["--encrypt-vault-id", id]) } arguments.append(vaultFilePath) logger.trace("Running vault command with arguments: \(arguments)") try await runCommand( quiet: options.quiet, shell: options.shell.orDefault, arguments ) } } } @_spi(Internal) public extension ConfigurationClient { func ensuredConfiguration(_ optionalConfig: Configuration?) async throws -> Configuration { guard let config = optionalConfig else { return try await findAndLoad() } return config } } @_spi(Internal) public extension Optional where Wrapped == String { var orDefault: ShellCommand.Shell { guard let shell = self else { return .zsh(useDashC: true) } return .custom(path: shell, useDashC: true) } } @_spi(Internal) public func ensuredInventoryPath( _ optionalInventoryPath: String?, configuration: Configuration, playbookDirectory: String ) -> String { guard let path = optionalInventoryPath else { guard let path = configuration.playbook?.inventory else { return "\(playbookDirectory)/\(Constants.inventoryFileName)" } return path } return path } @_spi(Internal) public extension FileClient { func ensuredVaultFilePath(_ optionalPath: String?) async throws -> String { guard let path = optionalPath else { guard let url = try await findVaultFileInCurrentDirectory() else { throw CliClientError.vaultFileNotFound } return url.cleanFilePath } return path } } extension ShellCommand.Shell: @retroactive @unchecked Sendable {}