diff --git a/Sources/CLI/cmd/agent/skills/AgentSkillsGetCommand.swift b/Sources/CLI/cmd/agent/skills/AgentSkillsGetCommand.swift index c775319..f8662d8 100644 --- a/Sources/CLI/cmd/agent/skills/AgentSkillsGetCommand.swift +++ b/Sources/CLI/cmd/agent/skills/AgentSkillsGetCommand.swift @@ -18,7 +18,7 @@ struct AgentSkillsGetCommand: ParsableCommand, GlobalOptionsProviding { var name: String mutating func run() throws { - try run(deps: Dependencies.shared) + try run(deps: AppleDocs.dependencies) } func run(deps: Deps) throws { diff --git a/Sources/CLI/cmd/agent/skills/AgentSkillsInstallCommand.swift b/Sources/CLI/cmd/agent/skills/AgentSkillsInstallCommand.swift index 33f75a6..37b9bd2 100644 --- a/Sources/CLI/cmd/agent/skills/AgentSkillsInstallCommand.swift +++ b/Sources/CLI/cmd/agent/skills/AgentSkillsInstallCommand.swift @@ -35,7 +35,7 @@ struct AgentSkillsInstallCommand: ParsableCommand, GlobalOptionsProviding { } mutating func run() throws { - try run(deps: Dependencies.shared) + try run(deps: AppleDocs.dependencies) } func run(deps: Deps) throws { diff --git a/Sources/CLI/cmd/agent/skills/AgentSkillsListCommand.swift b/Sources/CLI/cmd/agent/skills/AgentSkillsListCommand.swift index d487a64..6e276cf 100644 --- a/Sources/CLI/cmd/agent/skills/AgentSkillsListCommand.swift +++ b/Sources/CLI/cmd/agent/skills/AgentSkillsListCommand.swift @@ -15,7 +15,7 @@ struct AgentSkillsListCommand: ParsableCommand, GlobalOptionsProviding { ) mutating func run() throws { - run(deps: Dependencies.shared) + run(deps: AppleDocs.dependencies) } func run(deps: Deps) { diff --git a/Sources/CLI/cmd/agent/skills/AgentSkillsUninstallCommand.swift b/Sources/CLI/cmd/agent/skills/AgentSkillsUninstallCommand.swift index b908e60..2054f71 100644 --- a/Sources/CLI/cmd/agent/skills/AgentSkillsUninstallCommand.swift +++ b/Sources/CLI/cmd/agent/skills/AgentSkillsUninstallCommand.swift @@ -38,7 +38,7 @@ struct AgentSkillsUninstallCommand: ParsableCommand, GlobalOptionsProviding { } mutating func run() throws { - try run(deps: Dependencies.shared) + try run(deps: AppleDocs.dependencies) } func run(deps: Deps) throws { diff --git a/Sources/CLI/cmd/cache/CacheCleanCommand.swift b/Sources/CLI/cmd/cache/CacheCleanCommand.swift index 92cd2e9..511d0de 100644 --- a/Sources/CLI/cmd/cache/CacheCleanCommand.swift +++ b/Sources/CLI/cmd/cache/CacheCleanCommand.swift @@ -15,7 +15,7 @@ struct CacheCleanCommand: ParsableCommand, GlobalOptionsProviding { ) mutating func run() throws { - run(deps: Dependencies.shared) + run(deps: AppleDocs.dependencies) } func run(deps: Deps) { diff --git a/Sources/CLI/cmd/technologies/TechnologiesListCommand.swift b/Sources/CLI/cmd/technologies/TechnologiesListCommand.swift index c0ace84..389a634 100644 --- a/Sources/CLI/cmd/technologies/TechnologiesListCommand.swift +++ b/Sources/CLI/cmd/technologies/TechnologiesListCommand.swift @@ -19,7 +19,7 @@ struct TechnologiesListCommand: AsyncParsableCommand, GlobalOptionsProviding { ) mutating func run() async throws { - try await run(deps: Dependencies.shared) + try await run(deps: AppleDocs.dependencies) } func run(deps: Deps) async throws { diff --git a/Sources/CLI/cmd/types/TypesListCommand.swift b/Sources/CLI/cmd/types/TypesListCommand.swift index 138fc5b..4a5f6b8 100644 --- a/Sources/CLI/cmd/types/TypesListCommand.swift +++ b/Sources/CLI/cmd/types/TypesListCommand.swift @@ -22,7 +22,7 @@ struct TypesListCommand: AsyncParsableCommand, GlobalOptionsProviding { var technology: String mutating func run() async throws { - try await run(deps: Dependencies.shared) + try await run(deps: AppleDocs.dependencies) } func run(deps: Deps) async throws { diff --git a/Sources/CLI/cmd/types/TypesSearchCommand.swift b/Sources/CLI/cmd/types/TypesSearchCommand.swift index 8e355eb..2fa0edd 100644 --- a/Sources/CLI/cmd/types/TypesSearchCommand.swift +++ b/Sources/CLI/cmd/types/TypesSearchCommand.swift @@ -26,7 +26,7 @@ struct TypesSearchCommand: AsyncParsableCommand, GlobalOptionsProviding { var technology: String mutating func run() async throws { - try await run(deps: Dependencies.shared) + try await run(deps: AppleDocs.dependencies) } func run(deps: Deps) async throws { diff --git a/Sources/CLI/cmd/types/TypesViewCommand.swift b/Sources/CLI/cmd/types/TypesViewCommand.swift index 1f7d121..48be690 100644 --- a/Sources/CLI/cmd/types/TypesViewCommand.swift +++ b/Sources/CLI/cmd/types/TypesViewCommand.swift @@ -25,7 +25,7 @@ struct TypesViewCommand: AsyncParsableCommand, GlobalOptionsProviding { var technology: String mutating func run() async throws { - try await run(deps: Dependencies.shared) + try await run(deps: AppleDocs.dependencies) } func run(deps: Deps) async throws { diff --git a/Sources/CLI/main/AppleDocs.swift b/Sources/CLI/main/AppleDocs.swift index 103c342..3c41706 100644 --- a/Sources/CLI/main/AppleDocs.swift +++ b/Sources/CLI/main/AppleDocs.swift @@ -1,16 +1,23 @@ import ArgumentParser +import Foundation import Logging @main enum AppleDocs { private static let logger = Logger(label: "com.techprimate.apple-docs") + private static let telemetry = DefaultTelemetry( + // Telemetry starts before SwiftLog is bootstrapped. Resolve its logger only when logging an event. + logger: { Logger(label: "com.techprimate.apple-docs.telemetry") }, + environment: ProcessInfo.processInfo.environment + ) + static let dependencies = Dependencies(telemetry: telemetry) @MainActor static func main() async { - let telemetry = Dependencies.shared.telemetry + let telemetry = Self.telemetry telemetry.start() // Telemetry installs a SIGPIPE handler during start, so configure the terminal afterward. - Dependencies.shared.terminalSetup.configure() + TerminalSetup().configure() var loggingConfigured = false do { @@ -20,6 +27,8 @@ enum AppleDocs { telemetry: telemetry ) loggingConfigured = true + // Construct command dependencies only after their log handlers are installed. + _ = Self.dependencies Self.logger.debug("CLI command parsed") if var asyncCommand = command as? any AsyncParsableCommand { try await asyncCommand.run() diff --git a/Sources/CLI/main/Dependencies.swift b/Sources/CLI/main/Dependencies.swift index 5f90f29..db418f0 100644 --- a/Sources/CLI/main/Dependencies.swift +++ b/Sources/CLI/main/Dependencies.swift @@ -6,28 +6,15 @@ import Logging #endif struct Dependencies: Sendable { - static let shared = Dependencies() - let terminalCapabilities = DefaultTerminalCapabilities() let commandOutputWriter = DefaultCommandOutputWriter() - let terminalSetup = TerminalSetup() - let telemetry = DefaultTelemetry( - // Telemetry starts before SwiftLog is bootstrapped. Resolve its logger only when logging an event. - logger: { Logger(label: "com.techprimate.apple-docs.telemetry") }, - environment: ProcessInfo.processInfo.environment - ) + let telemetry: DefaultTelemetry let httpCache: URLCache? let httpDataTransport: URLSession + let documentationRepository: DefaultAppleDocumentationRepository> - // Commands resolve the repository after logging is bootstrapped. - var documentationRepository: DefaultAppleDocumentationRepository> { - DefaultAppleDocumentationRepository( - logger: Logger(label: "com.techprimate.apple-docs.repository"), - dependencies: DefaultAppleDocumentationClient( - logger: Logger(label: "com.techprimate.apple-docs.client"), dependencies: httpDataTransport)) - } - - init() { + init(telemetry: DefaultTelemetry) { + self.telemetry = telemetry if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first { let cacheDirectory = cachesDirectory.appendingPathComponent("com.techprimate.apple-docs", isDirectory: true) #if canImport(FoundationNetworking) @@ -46,6 +33,10 @@ struct Dependencies: Sendable { configuration.urlCache = httpCache } httpDataTransport = URLSession(configuration: configuration) + documentationRepository = DefaultAppleDocumentationRepository( + logger: Logger(label: "com.techprimate.apple-docs.repository"), + dependencies: DefaultAppleDocumentationClient( + logger: Logger(label: "com.techprimate.apple-docs.client"), dependencies: httpDataTransport)) } var documentationCache: URLCache? { diff --git a/Tests/CLITests/main/DependenciesTests.swift b/Tests/CLITests/main/DependenciesTests.swift index 68ab2d1..5765402 100644 --- a/Tests/CLITests/main/DependenciesTests.swift +++ b/Tests/CLITests/main/DependenciesTests.swift @@ -1,4 +1,5 @@ import Foundation +import Logging import Testing @testable import CLI @@ -12,8 +13,8 @@ struct DependenciesTests { @Test("separate dependency instances own separate transports and caches") func isolatesInstances() throws { // -- Arrange -- - let first = Dependencies() - let second = Dependencies() + let first = makeDependencies() + let second = makeDependencies() // -- Act -- let firstCache = try #require(first.documentationCache) @@ -29,7 +30,7 @@ struct DependenciesTests { @Test("uses a dedicated large documentation cache") func usesDedicatedDocumentationCache() throws { // -- Arrange -- - let dependencies = Dependencies.shared + let dependencies = makeDependencies() let session = dependencies.httpDataTransport // -- Act -- @@ -41,4 +42,13 @@ struct DependenciesTests { #expect(cache === documentationCache) #expect(cache.diskCapacity == 1_000_000_000) } + + private func makeDependencies() -> Dependencies { + Dependencies( + telemetry: DefaultTelemetry( + logger: { Logger(label: "test") { _ in SwiftLogNoOpLogHandler() } }, + environment: ["TELEMETRY_DISABLED": "true"] + ) + ) + } }