feat: Preparing to move items out of cli-client module into playbook-client
This commit is contained in:
@@ -38,6 +38,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "CliClient",
|
name: "CliClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"CommandClient",
|
||||||
"CodersClient",
|
"CodersClient",
|
||||||
"ConfigurationClient",
|
"ConfigurationClient",
|
||||||
"PlaybookClient",
|
"PlaybookClient",
|
||||||
@@ -64,6 +65,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "CommandClient",
|
name: "CommandClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"Constants",
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
.product(name: "ShellClient", package: "swift-shell-client")
|
.product(name: "ShellClient", package: "swift-shell-client")
|
||||||
@@ -104,16 +106,10 @@ let package = Package(
|
|||||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
|
||||||
name: "LoggingExtensions",
|
|
||||||
dependencies: [
|
|
||||||
"Constants",
|
|
||||||
.product(name: "ShellClient", package: "swift-shell-client")
|
|
||||||
]
|
|
||||||
),
|
|
||||||
.target(
|
.target(
|
||||||
name: "PlaybookClient",
|
name: "PlaybookClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"CommandClient",
|
||||||
"ConfigurationClient",
|
"ConfigurationClient",
|
||||||
"FileClient",
|
"FileClient",
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public extension CliClient {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let configuration = try await configurationClient.findAndLoad()
|
let configuration = try await configurationClient.findAndLoad()
|
||||||
try await playbookClient.installPlaybook(configuration)
|
try await playbookClient.repository.install(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPlaybookCommand(
|
func runPlaybookCommand(
|
||||||
@@ -62,7 +62,7 @@ public extension CliClient {
|
|||||||
let configuration = try await configurationClient.ensuredConfiguration(options.configuration)
|
let configuration = try await configurationClient.ensuredConfiguration(options.configuration)
|
||||||
logger.trace("Configuration: \(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)"
|
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||||
logger.trace("Playbook path: \(playbookPath)")
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ extension CliClient.RunPlaybook {
|
|||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||||
logger.trace("Playbook path: \(playbookPath)")
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ extension CliClient.PlaybookOptions.Route {
|
|||||||
@Dependency(\.logger) var logger
|
@Dependency(\.logger) var logger
|
||||||
@Dependency(\.playbookClient) var playbookClient
|
@Dependency(\.playbookClient) var playbookClient
|
||||||
|
|
||||||
let playbookDirectory = try await playbookClient.playbookDirectory(configuration)
|
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||||
logger.trace("Playbook path: \(playbookPath)")
|
logger.trace("Playbook path: \(playbookPath)")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import Constants
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import LoggingExtensions
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
public extension DependencyValues {
|
public extension DependencyValues {
|
||||||
@@ -18,37 +20,93 @@ public struct CommandClient: Sendable {
|
|||||||
/// Runs a shell command.
|
/// Runs a shell command.
|
||||||
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
||||||
|
|
||||||
/// Runs a shell command.
|
/// Runs a shell command and sets up logging.
|
||||||
public func run(
|
public func run(
|
||||||
quiet: Bool,
|
logging logginOptions: LoggingOptions,
|
||||||
shell: ShellCommand.Shell,
|
quiet: Bool = false,
|
||||||
_ arguments: [String]
|
shell: String? = nil,
|
||||||
|
in workingDirectory: String? = nil,
|
||||||
|
makeArguments: @Sendable @escaping () async throws -> [String]
|
||||||
) async throws {
|
) 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.
|
/// Runs a shell command.
|
||||||
public func run(
|
public func run(
|
||||||
quiet: Bool,
|
quiet: Bool = false,
|
||||||
shell: ShellCommand.Shell,
|
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...
|
_ arguments: String...
|
||||||
) async throws {
|
) 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 struct RunCommandOptions: Sendable, Equatable {
|
||||||
public let arguments: [String]
|
public let arguments: [String]
|
||||||
public let quiet: Bool
|
public let quiet: Bool
|
||||||
public let shell: ShellCommand.Shell
|
public let shell: String?
|
||||||
|
public let workingDirectory: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
arguments: [String],
|
arguments: [String],
|
||||||
quiet: Bool,
|
quiet: Bool,
|
||||||
shell: ShellCommand.Shell
|
shell: String? = nil,
|
||||||
|
workingDirectory: String? = nil
|
||||||
) {
|
) {
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
self.quiet = quiet
|
self.quiet = quiet
|
||||||
self.shell = shell
|
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 let testValue: CommandClient = Self()
|
||||||
|
|
||||||
public static var liveValue: CommandClient {
|
public static func live(
|
||||||
|
environment: [String: String]
|
||||||
|
) -> CommandClient {
|
||||||
.init { options in
|
.init { options in
|
||||||
@Dependency(\.asyncShellClient) var shellClient
|
@Dependency(\.asyncShellClient) var shellClient
|
||||||
if !options.quiet {
|
if !options.quiet {
|
||||||
try await shellClient.foreground(.init(
|
try await shellClient.foreground(.init(
|
||||||
shell: options.shell,
|
shell: .init(options.shell),
|
||||||
environment: ProcessInfo.processInfo.environment,
|
environment: environment,
|
||||||
in: nil,
|
in: options.workingDirectory,
|
||||||
options.arguments
|
options.arguments
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
try await shellClient.background(.init(
|
try await shellClient.background(.init(
|
||||||
shell: options.shell,
|
shell: .init(options.shell),
|
||||||
environment: ProcessInfo.processInfo.environment,
|
environment: environment,
|
||||||
in: nil,
|
in: options.workingDirectory,
|
||||||
options.arguments
|
options.arguments
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static var liveValue: CommandClient {
|
||||||
|
.live(environment: ProcessInfo.processInfo.environment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
@@ -93,21 +157,32 @@ public extension CommandClient {
|
|||||||
|
|
||||||
/// Captures the arguments / options passed into the command client's run commands.
|
/// Captures the arguments / options passed into the command client's run commands.
|
||||||
///
|
///
|
||||||
|
@dynamicMemberLookup
|
||||||
actor CapturingClient: Sendable {
|
actor CapturingClient: Sendable {
|
||||||
public private(set) var quiet: Bool?
|
public private(set) var options: RunCommandOptions?
|
||||||
public private(set) var shell: ShellCommand.Shell?
|
|
||||||
public private(set) var arguments: [String]?
|
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func set(
|
public func set(
|
||||||
_ options: RunCommandOptions
|
_ options: RunCommandOptions
|
||||||
) {
|
) {
|
||||||
quiet = options.quiet
|
self.options = options
|
||||||
shell = options.shell
|
}
|
||||||
arguments = options.arguments
|
|
||||||
|
public subscript<T>(dynamicMember keyPath: KeyPath<RunCommandOptions, T>) -> T? {
|
||||||
|
options?[keyPath: keyPath]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ShellCommand.Shell: @retroactive @unchecked Sendable {}
|
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
|
@DependencyClient
|
||||||
public struct PlaybookClient: Sendable {
|
public struct PlaybookClient: Sendable {
|
||||||
|
public var repository: Repository
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Remove the configuration and have it passed in.
|
public extension PlaybookClient {
|
||||||
public var installPlaybook: @Sendable (Configuration) async throws -> Void
|
|
||||||
public var playbookDirectory: @Sendable (Configuration) async throws -> String
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaybookClient: DependencyKey {
|
extension PlaybookClient: DependencyKey {
|
||||||
public static let testValue: PlaybookClient = Self()
|
public static let testValue: PlaybookClient = Self(repository: Repository())
|
||||||
|
|
||||||
public static var liveValue: PlaybookClient {
|
public static var liveValue: PlaybookClient {
|
||||||
.init {
|
.init(
|
||||||
try await install(config: $0.playbook)
|
repository: .liveValue
|
||||||
} playbookDirectory: {
|
|
||||||
$0.playbook?.directory ?? Constants.defaultInstallationPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func install(config: Configuration.Playbook?) async throws {
|
|
||||||
@Dependency(\.fileClient) var fileClient
|
|
||||||
@Dependency(\.logger) var logger
|
|
||||||
@Dependency(\.asyncShellClient) var shell
|
|
||||||
|
|
||||||
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]
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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