diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 8eabc1e2..ce8c8997 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -87,8 +87,6 @@ 03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF72E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; - 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; - 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; 03FE39E72E81682800B7B5AC /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E52E81682800B7B5AC /* AppIcon.icon */; }; 03FE39E82E81682800B7B5AC /* AppIconEAP.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */; }; 03FE39EA2E81694500B7B5AC /* AppIconUD.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E92E81694500B7B5AC /* AppIconUD.icon */; }; @@ -383,7 +381,6 @@ C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150A2C46D81E0069AAE7 /* NVAlert */; }; C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150C2C46D83E0069AAE7 /* NVAlert */; }; C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; - C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* StartupTest.swift */; }; @@ -393,12 +390,8 @@ C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; }; C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; - C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; - C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; - C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; }; - C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; }; C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; }; @@ -410,9 +403,7 @@ C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; - C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* RealCommand.swift */; }; - C471E7E228F9BAAB0021E251 /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; C471E7E328F9BAC20021E251 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; }; C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C471E7E528F9BAC20021E251 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; @@ -794,7 +785,6 @@ C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; }; C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; - C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; }; C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; }; @@ -880,8 +870,6 @@ C4E2E86A28FC3002003B070C /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; - C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; - C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE628F764050026AC4E /* ActiveCommand.swift */; }; C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; }; C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DEC28F764A00026AC4E /* TestableCommand.swift */; }; @@ -1007,7 +995,6 @@ 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = ""; }; 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallHomebrew.swift; sourceTree = ""; }; 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZshRunCommand.swift; sourceTree = ""; }; - 03E36FE628D9219000636F7F /* ActiveShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveShell.swift; sourceTree = ""; }; 03FE39E52E81682800B7B5AC /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconEAP.icon; sourceTree = ""; }; 03FE39E92E81694500B7B5AC /* AppIconUD.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconUD.icon; sourceTree = ""; }; @@ -1214,7 +1201,6 @@ C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = ""; }; C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = ""; }; C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = ""; }; - C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = ""; }; C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = ""; }; C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = ""; }; C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = ""; }; @@ -1248,7 +1234,6 @@ C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfiguration.swift; sourceTree = ""; }; C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCPMApplication.swift; sourceTree = ""; }; C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; - C4E49DE628F764050026AC4E /* ActiveCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommand.swift; sourceTree = ""; }; C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = ""; }; C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = ""; }; C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = ""; }; @@ -2229,7 +2214,6 @@ C4C8900128F0E27900CE5E97 /* Filesystem */ = { isa = PBXGroup; children = ( - C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */, C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */, C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */, ); @@ -2297,7 +2281,6 @@ C4E49DE528F763E20026AC4E /* Command */ = { isa = PBXGroup; children = ( - C4E49DE628F764050026AC4E /* ActiveCommand.swift */, C4B5853D2770FE3900DA4FBE /* RealCommand.swift */, C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */, ); @@ -2356,7 +2339,6 @@ C4F787A628EF811000790735 /* Shell */ = { isa = PBXGroup; children = ( - 03E36FE628D9219000636F7F /* ActiveShell.swift */, C46EBC4628DB9644007ACC74 /* RealShell.swift */, C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */, ); @@ -2702,7 +2684,6 @@ C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */, C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */, C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */, - C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C43931CA29C4C03F0069165B /* Brew.swift in Sources */, @@ -2728,7 +2709,6 @@ C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, - C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */, C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */, 032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, @@ -2815,7 +2795,6 @@ C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */, 03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */, - 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C4D5857C2A7038DB00DDBB63 /* ByteLimitView.swift in Sources */, @@ -3059,7 +3038,6 @@ C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */, C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */, - C471E7E228F9BAAB0021E251 /* ActiveCommand.swift in Sources */, 03263A392E86D5EC00BD0415 /* UpdateScheduler.swift in Sources */, C471E80A28F9BADC0021E251 /* CreatedFromFile.swift in Sources */, C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, @@ -3092,7 +3070,6 @@ C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */, - C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */, C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */, C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */, @@ -3110,7 +3087,6 @@ C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */, C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, - C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */, 032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */, C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */, @@ -3273,7 +3249,6 @@ 03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */, C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */, - C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */, C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */, 039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */, @@ -3288,7 +3263,6 @@ C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, - C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */, C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */, C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */, C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, @@ -3312,7 +3286,6 @@ C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */, C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */, C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */, - C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */, C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */, C471E7F528F9BAC80021E251 /* PhpEnvironments.swift in Sources */, C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, @@ -3486,13 +3459,10 @@ C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */, - C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, - 03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */, 0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, - C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */, C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, 036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, diff --git a/packages/container-macro/Sources/ContainerMacroPlugin/ContainerAccessMacro.swift b/packages/container-macro/Sources/ContainerMacroPlugin/ContainerAccessMacro.swift index 7286baad..d9f970b3 100644 --- a/packages/container-macro/Sources/ContainerMacroPlugin/ContainerAccessMacro.swift +++ b/packages/container-macro/Sources/ContainerMacroPlugin/ContainerAccessMacro.swift @@ -13,6 +13,8 @@ public struct ContainerAccessMacro: MemberMacro { // This should be kept in sync with the Container class let allContainerServices: [(name: String, type: String)] = [ ("shell", "ShellProtocol"), + ("filesystem", "FileSystemProtocol"), + ("command", "CommandProtocol"), ("favorites", "Favorites"), ("warningManager", "WarningManager") ] diff --git a/phpmon/Common/Command/ActiveCommand.swift b/phpmon/Common/Command/ActiveCommand.swift deleted file mode 100644 index 79ae17b2..00000000 --- a/phpmon/Common/Command/ActiveCommand.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// ActiveCommand.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 12/10/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation - -var Command: CommandProtocol { - return ActiveCommand.shared -} - -class ActiveCommand { - static var shared: CommandProtocol = RealCommand() - - public static func useTestable(_ output: [String: String]) { - Self.shared = TestableCommand(commands: output) - } - - public static func useSystem() { - Self.shared = RealCommand() - } -} diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index ddc866ad..2dd1df34 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -112,7 +112,7 @@ class Actions { // MARK: - Other Actions public func createTempPhpInfoFile() async -> URL { - try! FileSystem.writeAtomicallyToFile("/tmp/phpmon_phpinfo.php", content: " /tmp/phpmon_phpinfo.html") diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index 64ea1655..38ede66e 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -13,32 +13,38 @@ import Foundation /** Runs a `brew` command. Can run as superuser. */ -func brew(_ command: String, sudo: Bool = false) async { - await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") +func brew(_ command: String, sudo: Bool = false, shell: ShellProtocol = App.shared.container.shell) async { + await shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") } /** Runs `sed` in order to replace all occurrences of a string in a specific file with another. */ -func sed(file: String, original: String, replacement: String) async { +func sed( + file: String, + original: String, + replacement: String, + filesystem: FileSystemProtocol = App.shared.container.filesystem, + shell: ShellProtocol = App.shared.container.shell +) async { // Escape slashes (or `sed` won't work) let e_original = original.replacingOccurrences(of: "/", with: "\\/") let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/") // Check if gsed exists; it is able to follow symlinks, // which we want to do to toggle the extension - if FileSystem.fileExists("\(Paths.binPath)/gsed") { - await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") + if filesystem.fileExists("\(Paths.binPath)/gsed") { + await shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") } else { - await Shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") + await shell.quiet("sed -i '' 's/\(e_original)/\(e_replacement)/g' \(file)") } } /** Uses `grep` to determine whether a particular query string can be found in a particular file. */ -func grepContains(file: String, query: String) async -> Bool { - return await Shell.pipe(""" +func grepContains(file: String, query: String, shell: ShellProtocol = App.shared.container.shell) async -> Bool { + return await shell.pipe(""" grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO" """).out .trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/phpmon/Common/Core/Homebrew.swift b/phpmon/Common/Core/Homebrew.swift index 57add148..0cd1cc85 100644 --- a/phpmon/Common/Core/Homebrew.swift +++ b/phpmon/Common/Core/Homebrew.swift @@ -22,7 +22,7 @@ struct HomebrewFormulae { } static var nginx: HomebrewFormula { - return BrewDiagnostics.usesNginxFullFormula + return BrewDiagnostics.shared.usesNginxFullFormula ? HomebrewFormula("nginx-full", elevated: true) : HomebrewFormula("nginx", elevated: true) } diff --git a/phpmon/Common/Core/Logger.swift b/phpmon/Common/Core/Logger.swift index c7f331f1..fbfa87bf 100644 --- a/phpmon/Common/Core/Logger.swift +++ b/phpmon/Common/Core/Logger.swift @@ -33,7 +33,7 @@ class Log { system_quiet("mkdir -p ~/.config/phpmon 2> /dev/null") system_quiet("rm ~/.config/phpmon/last_session.log 2> /dev/null") system_quiet("touch ~/.config/phpmon/last_session.log 2> /dev/null") - self.logExists = FileSystem.fileExists(self.logFilePath) + self.logExists = App.shared.container.filesystem.fileExists(self.logFilePath) } } diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 56d23a7a..20a499ac 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -25,8 +25,8 @@ public class Paths { // Ensure that if a different location is used, it takes precendence if baseDir == .usr - && FileSystem.directoryExists("/usr/local/homebrew") - && !FileSystem.directoryExists("/usr/local/Cellar") { + && App.shared.container.filesystem.directoryExists("/usr/local/homebrew") + && !App.shared.container.filesystem.directoryExists("/usr/local/Cellar") { Log.warn("Using /usr/local/homebrew as base directory!") baseDir = .usr_hb } @@ -74,12 +74,12 @@ public class Paths { } public static var homePath: String { - if FileSystem is RealFileSystem { + if App.shared.container.filesystem is RealFileSystem { return NSHomeDirectory() } - if FileSystem is TestableFileSystem { - let fs = FileSystem as! TestableFileSystem + if App.shared.container.filesystem is TestableFileSystem { + let fs = App.shared.container.filesystem as! TestableFileSystem return fs.homeDirectory } @@ -123,11 +123,11 @@ public class Paths { // (PHP Monitor will not use the user's own PATH) private func detectComposerBinary() { - if FileSystem.fileExists("/usr/local/bin/composer") { + if App.shared.container.filesystem.fileExists("/usr/local/bin/composer") { Paths.composer = "/usr/local/bin/composer" - } else if FileSystem.fileExists("/opt/homebrew/bin/composer") { + } else if App.shared.container.filesystem.fileExists("/opt/homebrew/bin/composer") { Paths.composer = "/opt/homebrew/bin/composer" - } else if FileSystem.fileExists("/usr/local/homebrew/bin/composer") { + } else if App.shared.container.filesystem.fileExists("/usr/local/homebrew/bin/composer") { Paths.composer = "/usr/local/homebrew/bin/composer" } else { Paths.composer = nil diff --git a/phpmon/Common/Filesystem/ActiveFileSystem.swift b/phpmon/Common/Filesystem/ActiveFileSystem.swift deleted file mode 100644 index f1e55b82..00000000 --- a/phpmon/Common/Filesystem/ActiveFileSystem.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// FS.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 08/10/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation - -@available(*, deprecated, message: "Use an injected `Container` instance to access this instead.") -var FileSystem: FileSystemProtocol { - return App.shared.container.filesystem -} diff --git a/phpmon/Common/Helpers/Application.swift b/phpmon/Common/Helpers/Application.swift index 37a88589..f774d666 100644 --- a/phpmon/Common/Helpers/Application.swift +++ b/phpmon/Common/Helpers/Application.swift @@ -7,10 +7,12 @@ // import Foundation +import ContainerMacro /// An application that is capable of opening a particular directory (usually of a PHP project). /// In most cases this is going to be a code editor, but it could also be another application /// that supports opening those directories, like a visual Git client or a terminal app. +@ContainerAccess class Application { enum AppType { @@ -34,19 +36,19 @@ class Application { (This will open the app if it isn't open yet.) */ @objc public func openDirectory(file: String) { - Task { await Shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") } + Task { await shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") } } /** Checks if the app is installed. */ func isInstalled() async -> Bool { - let (process, output) = try! await Shell.attach( + let (process, output) = try! await shell.attach( "/usr/bin/open -Ra \"\(name)\"", didReceiveOutput: { _, _ in }, withTimeout: 2.0 ) - if Shell is TestableShell { + if shell is TestableShell { // When testing, check the error output (must not be empty) return !output.hasError } else { diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 649c5a4d..7ac1edd9 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -41,7 +41,7 @@ class ActivePhpInstallation { // MARK: - Initializer public static func load() -> ActivePhpInstallation? { - if !FileSystem.fileExists(Paths.phpConfig) { + if !App.shared.container.filesystem.fileExists(Paths.phpConfig) { return nil } @@ -92,7 +92,7 @@ class ActivePhpInstallation { _or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case. */ private func determineVersion() throws { - let output = Command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) + let output = command.execute(path: Paths.phpConfig, arguments: ["--version"], trimNewlines: true) self.hasErrorState = (output == "" || output.contains("Warning") || output.contains("Error")) @@ -115,7 +115,7 @@ class ActivePhpInstallation { - Parameter key: The key of the `ini` value that needs to be retrieved. For example, you can use `memory_limit`. */ private func getByteCount(key: String) -> String { - let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"], trimNewlines: false) + let value = command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"], trimNewlines: false) // Check if the value is unlimited if value == "-1" { diff --git a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift index 768635af..70e68792 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift @@ -44,8 +44,8 @@ class PhpEnvironments { // Check if that version actually corresponds to an older version let phpConfigExecutablePath = "\(Paths.optPath)/php/bin/php-config" - if FileSystem.fileExists(phpConfigExecutablePath) { - let longVersionString = Command.execute( + if container.filesystem.fileExists(phpConfigExecutablePath) { + let longVersionString = container.command.execute( path: phpConfigExecutablePath, arguments: ["--version"], trimNewlines: false @@ -115,7 +115,7 @@ class PhpEnvironments { static var homebrewBrewPhpAlias: String { if PhpEnvironments.shared.homebrewPackage == nil { // For UI testing and as a fallback, determine this version by using (fake) php-config - let version = Command.execute(path: "/opt/homebrew/bin/php-config", + let version = App.shared.container.command.execute(path: "/opt/homebrew/bin/php-config", arguments: ["--version"], trimNewlines: true) return try! VersionNumber.parse(version).short @@ -184,7 +184,7 @@ class PhpEnvironments { let phpAlias = homebrewPackage.version // Avoid inserting a duplicate - if !supportedVersions.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") { + if !supportedVersions.contains(phpAlias) && container.filesystem.fileExists("\(Paths.optPath)/php/bin/php") { let phpAliasInstall = PhpInstallation(phpAlias) // Before inserting, ensure that the actual output matches the alias // if that isn't the case, our formula remains out-of-date @@ -237,7 +237,7 @@ class PhpEnvironments { // is supported and where the binary exists (avoids broken installs) if !output.contains(version) && supported.contains(version) - && (checkBinaries ? FileSystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { + && (checkBinaries ? container.filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) { output.insert(version) } } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index dc692320..b896abd7 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -26,20 +26,20 @@ class PhpHelper { let inPath = container.shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") // Check if we can create symlinks (`/usr/local/bin` must be writable) - let canWriteSymlinks = FileSystem.isWriteableFile("/usr/local/bin/") + let canWriteSymlinks = App.shared.container.filesystem.isWriteableFile("/usr/local/bin/") Task { // Create the appropriate folders and check if the files exist do { - if !FileSystem.directoryExists("~/.config/phpmon/bin") { + if !App.shared.container.filesystem.directoryExists("~/.config/phpmon/bin") { Task { @MainActor in - try FileSystem.createDirectory( + try App.shared.container.filesystem.createDirectory( "~/.config/phpmon/bin", withIntermediateDirectories: true ) } } - if FileSystem.fileExists(destination) { + if App.shared.container.filesystem.fileExists(destination) { let contents = try String(contentsOfFile: destination) if !contents.contains(keyPhrase) { Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " @@ -58,10 +58,10 @@ class PhpHelper { : zshScript(path, keyPhrase, version, dotless) Task { @MainActor in - try FileSystem.writeAtomicallyToFile(destination, content: script) + try App.shared.container.filesystem.writeAtomicallyToFile(destination, content: script) - if !FileSystem.isExecutableFile(destination) { - try FileSystem.makeExecutable(destination) + if !App.shared.container.filesystem.isExecutableFile(destination) { + try App.shared.container.filesystem.makeExecutable(destination) } } @@ -121,13 +121,13 @@ class PhpHelper { let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" let destination = "/usr/local/bin/pm\(dotless)" - if !FileSystem.fileExists(destination) { + if !App.shared.container.filesystem.fileExists(destination) { Log.info("Creating new symlink: \(destination)") await container.shell.quiet("ln -s \(source) \(destination)") return } - if !FileSystem.isSymlink(destination) { + if !App.shared.container.filesystem.isSymlink(destination) { Log.info("Overwriting existing file with new symlink: \(destination)") await container.shell.quiet("ln -fs \(source) \(destination)") return diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index 1bfe7433..494f6106 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class PhpInstallation { var versionNumber: VersionNumber @@ -54,8 +56,8 @@ class PhpInstallation { } private func determineVersion(_ phpConfigExecutablePath: String, _ phpExecutablePath: String) { - if FileSystem.fileExists(phpConfigExecutablePath) { - let longVersionString = Command.execute( + if filesystem.fileExists(phpConfigExecutablePath) { + let longVersionString = command.execute( path: phpConfigExecutablePath, arguments: ["--version"], trimNewlines: false @@ -76,8 +78,8 @@ class PhpInstallation { } private func determineHealth(_ phpExecutablePath: String) { - if FileSystem.fileExists(phpExecutablePath) { - let testCommand = Command.execute( + if filesystem.fileExists(phpExecutablePath) { + let testCommand = command.execute( path: phpExecutablePath, arguments: ["-v"], trimNewlines: false, @@ -94,7 +96,7 @@ class PhpInstallation { } private func determineIniFiles(_ phpExecutablePath: String) { - let paths = Shell + let paths = shell .sync("\(phpExecutablePath) --ini | grep -E -o '(/[^ ]+\\.ini)'").out .split(separator: "\n") .map { String($0) } diff --git a/phpmon/Common/Shell/ActiveShell.swift b/phpmon/Common/Shell/ActiveShell.swift deleted file mode 100644 index a77040c0..00000000 --- a/phpmon/Common/Shell/ActiveShell.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Shell.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 20/09/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation - -@available(*, deprecated, message: "Use an injected `Container` instance to access this instead.") -var Shell: ShellProtocol { - return App.shared.container.shell -} diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index f4772cd6..47527e0b 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -124,8 +124,6 @@ public struct TestableConfiguration: Codable { Log.separator() Log.info("Applying to container...") App.shared.container.overrideWith(config: self) - Log.info("Applying fake commands...") - ActiveCommand.useTestable(commandOutput) Log.info("Applying temporary preference overrides...") preferenceOverrides.forEach { (key: PreferenceName, value: Any?) in Preferences.shared.cachedPreferences[key] = value diff --git a/phpmon/Container.swift b/phpmon/Container.swift index 14c892bd..15c7de70 100644 --- a/phpmon/Container.swift +++ b/phpmon/Container.swift @@ -9,6 +9,7 @@ class Container { var shell: ShellProtocol! var filesystem: FileSystemProtocol! + var command: CommandProtocol! var favorites: Favorites! var warningManager: WarningManager! @@ -18,6 +19,7 @@ class Container { public func prepare() { self.shell = RealShell(container: self) self.filesystem = RealFileSystem(container: self) + self.command = RealCommand() self.favorites = Favorites() self.warningManager = WarningManager(container: self) @@ -26,5 +28,6 @@ class Container { public func overrideWith(config: TestableConfiguration) { self.shell = TestableShell(expectations: config.shellOutput) self.filesystem = TestableFileSystem(files: config.filesystem) + self.command = TestableCommand(commands: config.commandOutput) } } diff --git a/phpmon/Domain/App/AppUpdater.swift b/phpmon/Domain/App/AppUpdater.swift index f567500f..1d59fcf2 100644 --- a/phpmon/Domain/App/AppUpdater.swift +++ b/phpmon/Domain/App/AppUpdater.swift @@ -75,7 +75,7 @@ class AppUpdater { .localized(latestVersionOnline.humanReadable), subtitle: "updater.alerts.newer_version_available.subtitle" .localized, - description: BrewDiagnostics.customCaskInstalled + description: BrewDiagnostics.shared.customCaskInstalled ? "updater.installation_source.brew".localized(command) : "updater.installation_source.direct".localized ) @@ -152,7 +152,7 @@ class AppUpdater { system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/PHP Monitor Self-Updater.app\"") - try! FileSystem.writeAtomicallyToFile( + try! App.shared.container.filesystem.writeAtomicallyToFile( "\(updaterDirectory)/update.json", content: "{ \"url\": \"\(caskFile.url)\", \"sha256\": \"\(caskFile.sha256)\" }" ) @@ -168,10 +168,10 @@ class AppUpdater { private func cleanupCaskroom() { let path = Paths.caskroomPath - if FileSystem.directoryExists(path) { + if App.shared.container.filesystem.directoryExists(path) { Log.info("Removing the Caskroom directory for PHP Monitor...") do { - try FileSystem.remove(path) + try App.shared.container.filesystem.remove(path) Log.info("Removed the Caskroom directory at `\(path)`.") } catch { Log.err("Automatically removing the Caskroom directory at `\(path)` failed.") @@ -183,7 +183,7 @@ class AppUpdater { public static func checkIfUpdateWasPerformed() { // Cleanup the upgrade.success file - if FileSystem.fileExists("~/.config/phpmon/updater/upgrade.success") { + if App.shared.container.filesystem.fileExists("~/.config/phpmon/updater/upgrade.success") { Task { @MainActor in if App.identifier.contains(".phpmon.eap") { LocalNotification.send( @@ -201,13 +201,13 @@ class AppUpdater { } Log.info("The `upgrade.success` file was found! An update was installed. Cleaning up...") - try? FileSystem.remove("~/.config/phpmon/updater/upgrade.success") + try? App.shared.container.filesystem.remove("~/.config/phpmon/updater/upgrade.success") } // Cleanup the previous updater - if FileSystem.anyExists("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") { + if App.shared.container.filesystem.anyExists("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") { Log.info("A remnant of the self-updater must still be removed...") - try? FileSystem.remove("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") + try? App.shared.container.filesystem.remove("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") } } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 081940d7..9ce5eb4e 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -9,7 +9,9 @@ import Foundation import Cocoa import NVAlert +import ContainerMacro +@ContainerAccess class ValetServicesManager: ServicesManager { override init() { super.init() @@ -46,7 +48,7 @@ class ValetServicesManager: ServicesManager { .filter { $0.elevated } .map { $0.name } - let rootJson = await Shell + let rootJson = await App.shared.container.shell .pipe("sudo \(Paths.brew) services info --all --json") .out.data(using: .utf8)! @@ -61,7 +63,7 @@ class ValetServicesManager: ServicesManager { .filter { !$0.elevated } .map { $0.name } - let normalJson = await Shell + let normalJson = await App.shared.container.shell .pipe("\(Paths.brew) services info --all --json") .out.data(using: .utf8)! diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 95fbb6db..6e1d5de8 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -101,7 +101,7 @@ class Startup { // The Homebrew binary must exist. // ================================================================================= EnvironmentCheck( - command: { return !FileSystem.fileExists(Paths.brew) }, + command: { return !App.shared.container.filesystem.fileExists(Paths.brew) }, name: "`\(Paths.brew)` exists", titleText: "alert.homebrew_missing.title".localized, subtitleText: "alert.homebrew_missing.subtitle".localized, @@ -119,7 +119,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return await !Shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") + return await !App.shared.container.shell.pipe("ls \(Paths.optPath) | grep php").out.contains("php") }, name: "`ls \(Paths.optPath) | grep php` returned php result", titleText: "startup.errors.php_opt.title".localized, @@ -134,7 +134,7 @@ class Startup { // The PHP binary must exist. // ================================================================================= EnvironmentCheck( - command: { return !FileSystem.fileExists(Paths.php) }, + command: { return !App.shared.container.filesystem.fileExists(Paths.php) }, name: "`\(Paths.php)` exists", titleText: "startup.errors.php_binary.title".localized, subtitleText: "startup.errors.php_binary.subtitle".localized, @@ -145,7 +145,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return await Shell.pipe("\(Paths.binPath)/php -v").err + return await App.shared.container.shell.pipe("\(Paths.binPath)/php -v").err .contains("Library not loaded") }, name: "no `dyld` issue (`Library not loaded`) detected", @@ -160,8 +160,8 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return !(FileSystem.fileExists(Paths.valet) - || FileSystem.fileExists("~/.composer/vendor/bin/valet")) + return !(App.shared.container.filesystem.fileExists(Paths.valet) + || App.shared.container.filesystem.fileExists("~/.composer/vendor/bin/valet")) }, name: "`valet` binary exists", titleText: "startup.errors.valet_executable.title".localized, @@ -176,14 +176,14 @@ class Startup { // functioning correctly. Let the user know that they need to run `valet trust`. // ================================================================================= EnvironmentCheck( - command: { return await !Shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, + command: { return await !App.shared.container.shell.pipe("cat /private/etc/sudoers.d/brew").out.contains(Paths.brew) }, name: "`/private/etc/sudoers.d/brew` contains brew", titleText: "startup.errors.sudoers_brew.title".localized, subtitleText: "startup.errors.sudoers_brew.subtitle".localized, descriptionText: "startup.errors.sudoers_brew.desc".localized ), EnvironmentCheck( - command: { return await !Shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) }, + command: { return await !App.shared.container.shell.pipe("cat /private/etc/sudoers.d/valet").out.contains(Paths.valet) }, name: "`/private/etc/sudoers.d/valet` contains valet", titleText: "startup.errors.sudoers_valet.title".localized, subtitleText: "startup.errors.sudoers_valet.subtitle".localized, @@ -194,7 +194,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return !FileSystem.directoryExists("~/.config/valet") + return !App.shared.container.filesystem.directoryExists("~/.config/valet") }, name: "`.config/valet` not empty (Valet installed)", titleText: "startup.errors.valet_not_installed.title".localized, @@ -223,8 +223,8 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - await BrewDiagnostics.loadInstalledTaps() - return await BrewDiagnostics.cannotLoadService("dnsmasq") + await BrewDiagnostics.shared.loadInstalledTaps() + return await BrewDiagnostics.shared.cannotLoadService("dnsmasq") }, name: "`sudo \(Paths.brew) services info` JSON loaded", titleText: "startup.errors.services_json_error.title".localized, @@ -236,9 +236,9 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - let nodePath = await Shell.pipe("which node").out + let nodePath = await App.shared.container.shell.pipe("which node").out return App.architecture == "x86_64" - && FileSystem.fileExists("/usr/local/bin/which") + && App.shared.container.filesystem.fileExists("/usr/local/bin/which") && nodePath.contains("env: node: No such file or directory") }, name: "`env: node` issue does not apply", @@ -265,7 +265,7 @@ class Startup { // ================================================================================= EnvironmentCheck( command: { - return await Shell.pipe("valet --version").out + return await App.shared.container.shell.pipe("valet --version").out .contains("Composer detected issues in your platform") }, name: "no global composer issues", diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index 032da88c..abce25c4 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -8,8 +8,9 @@ import Foundation import NVAlert +import ContainerMacro -@MainActor class ComposerWindow { +@MainActor @ContainerAccess class ComposerWindow { private var shouldNotify: Bool! = nil private var completion: ((Bool) -> Void)! = nil private var window: TerminalProgressWindowController? @@ -55,7 +56,7 @@ import NVAlert self.window?.addToConsole("\(command)\n") - let (process, _) = try await Shell.attach( + let (process, _) = try await shell.attach( command, didReceiveOutput: { [weak self] (incoming, _) in guard let window = self?.window else { return } diff --git a/phpmon/Domain/Integrations/Composer/ProjectTypeDetection.swift b/phpmon/Domain/Integrations/Composer/ProjectTypeDetection.swift index a9eb5ede..5f2dafab 100644 --- a/phpmon/Domain/Integrations/Composer/ProjectTypeDetection.swift +++ b/phpmon/Domain/Integrations/Composer/ProjectTypeDetection.swift @@ -50,7 +50,7 @@ struct ProjectTypeDetection { public static func detectFallbackDependency(_ basePath: String) -> String? { for entry in Self.FileMapping { let found = entry.value - .map { path in return FileSystem.anyExists(basePath + path) } + .map { path in return App.shared.container.filesystem.anyExists(basePath + path) } .contains(true) if found { diff --git a/phpmon/Domain/Integrations/Homebrew/Brew.swift b/phpmon/Domain/Integrations/Homebrew/Brew.swift index 679904a7..5c2c2a79 100644 --- a/phpmon/Domain/Integrations/Homebrew/Brew.swift +++ b/phpmon/Domain/Integrations/Homebrew/Brew.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class Brew { static let shared = Brew() @@ -19,7 +21,7 @@ class Brew { /// Determine which version of Homebrew is installed. public func determineVersion() async { - let output = await Shell.pipe("\(Paths.brew) --version") + let output = await shell.pipe("\(Paths.brew) --version") self.version = try? VersionNumber.parse(output.out) if let version = version { diff --git a/phpmon/Domain/Integrations/Homebrew/BrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/BrewDiagnostics.swift index 7c695e77..69bb97b8 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewDiagnostics.swift @@ -8,18 +8,22 @@ import Foundation import NVAlert +import ContainerMacro +@ContainerAccess class BrewDiagnostics { + public static let shared = BrewDiagnostics() + /** Determines the Homebrew taps the user has installed. */ - public static var installedTaps: [String] = [] + public var installedTaps: [String] = [] /** Load which taps are installed. */ - public static func loadInstalledTaps() async { - installedTaps = await Shell + public func loadInstalledTaps() async { + installedTaps = await shell .pipe("\(Paths.brew) tap") .out .split(separator: "\n") @@ -31,13 +35,13 @@ class BrewDiagnostics { /** Logs a bunch of useful information during startup. */ - public static func logBootInformation() { - Log.info(BrewDiagnostics.customCaskInstalled + public func logBootInformation() { + Log.info(customCaskInstalled ? "[BREW] The app has been installed via Homebrew Cask." : "[BREW] The app has been installed directly (optimal)." ) - Log.info(BrewDiagnostics.usesNginxFullFormula + Log.info(usesNginxFullFormula ? "[BREW] The app will be using the `nginx-full` formula." : "[BREW] The app will be using the `nginx` formula." ) @@ -46,21 +50,20 @@ class BrewDiagnostics { /** Determines whether the PHP Monitor Cask is installed. */ - public static var customCaskInstalled: Bool = { + public var customCaskInstalled: Bool { return installedTaps.contains("nicoverbruggen/cask") - && FileSystem.directoryExists(Paths.caskroomPath) - }() + && filesystem.directoryExists(Paths.caskroomPath) + } /** Determines whether to use the regular `nginx` or `nginx-full` formula. */ - public static var usesNginxFullFormula: Bool = { - guard let destination = try? FileManager.default - .destinationOfSymbolicLink(atPath: "\(Paths.binPath)/nginx") else { return false } + public var usesNginxFullFormula: Bool { + guard let destination = try? filesystem.getDestinationOfSymlink("\(Paths.binPath)/nginx") else { return false } // Verify that the `nginx` binary is symlinked to a directory that includes `nginx-full`. return destination.contains("/nginx-full/") - }() + } /** It is possible to have outdated symlinks for PHP installations. This can mean that certain PHP installations @@ -68,12 +71,12 @@ class BrewDiagnostics { To ensure this does not cause issues, PHP Monitor will automatically remove all incorrect PHP symlinks. */ - public static func checkForOutdatedPhpInstallationSymlinks() async { + public func checkForOutdatedPhpInstallationSymlinks() async { // Set up a regular expression let regex = try! NSRegularExpression(pattern: "^php@[0-9]+\\.[0-9]+$", options: .caseInsensitive) // Check for incorrect versions - if let contents = try? FileSystem.getShallowContentsOfDirectory("\(Paths.optPath)") + if let contents = try? filesystem.getShallowContentsOfDirectory("\(Paths.optPath)") .filter({ let range = NSRange($0.startIndex..., in: $0) return regex.firstMatch(in: $0, options: [], range: range) != nil @@ -81,12 +84,12 @@ class BrewDiagnostics { for symlink in contents { let version = symlink.replacingOccurrences(of: "php@", with: "") - if let destination = try? FileSystem.getDestinationOfSymlink("\(Paths.optPath)/\(symlink)") { + if let destination = try? filesystem.getDestinationOfSymlink("\(Paths.optPath)/\(symlink)") { if !destination.contains("Cellar/php/\(version)") && !destination.contains("Cellar/php@\(version)") { Log.err("Symlink for \(symlink) is incorrect. Removing...") do { - try FileSystem.remove("\(Paths.optPath)/\(symlink)") + try filesystem.remove("\(Paths.optPath)/\(symlink)") Log.info("Incorrect symlink for \(symlink) has been successfully removed.") } catch { Log.err("Symlink for \(symlink) was incorrect but could not be removed!") @@ -106,7 +109,7 @@ class BrewDiagnostics { This check only needs to be performed if the `shivammathur/php` tap is active. */ - public static func checkForCaskConflict() async { + public func checkForCaskConflict() async { if await hasAliasConflict() { presentAlertAboutConflict() } @@ -116,7 +119,7 @@ class BrewDiagnostics { It is possible to upgrade PHP, but forget running `valet install`. This results in a scenario where a rogue www.conf file exists. */ - public static func checkForValetMisconfiguration() async { + public func checkForValetMisconfiguration() async { Log.info("Checking for PHP-FPM issues with Valet...") guard let install = PhpEnvironments.phpInstall else { @@ -138,7 +141,7 @@ class BrewDiagnostics { } } - public static func verifyThirdPartyTaps() async { + public func verifyThirdPartyTaps() async { let requiredTaps = [ "shivammathur/php", "shivammathur/extensions" @@ -157,8 +160,8 @@ class BrewDiagnostics { /** Check if the alias conflict as documented in `checkForCaskConflict` actually occurred. */ - private static func hasAliasConflict() async -> Bool { - let tapAlias = await Shell.pipe("brew info shivammathur/php/php --json").out + private func hasAliasConflict() async -> Bool { + let tapAlias = await shell.pipe("brew info shivammathur/php/php --json").out if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") || tapAlias.isEmpty { Log.info("The user does not appear to have tapped: shivammathur/php") @@ -198,7 +201,7 @@ class BrewDiagnostics { /** Show this alert in case the tapped Cask does cause issues because of the conflict. */ - private static func presentAlertAboutConflict() { + private func presentAlertAboutConflict() { Task { @MainActor in NVAlert() .withInformation( @@ -214,8 +217,8 @@ class BrewDiagnostics { In order to see if we support the --json syntax, we'll query nginx. If the JSON response cannot be parsed, Homebrew is probably out of date. */ - public static func cannotLoadService(_ name: String) async -> Bool { - let nginxJson = await Shell + public func cannotLoadService(_ name: String) async -> Bool { + let nginxJson = await shell .pipe("sudo \(Paths.brew) services info \(name) --json") .out diff --git a/phpmon/Domain/Integrations/Homebrew/BrewPhpExtension.swift b/phpmon/Domain/Integrations/Homebrew/BrewPhpExtension.swift index af9d57b4..0d051848 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewPhpExtension.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewPhpExtension.swift @@ -59,7 +59,7 @@ struct BrewPhpExtension: Hashable, Comparable { } static func hasInstallationReceipt(for formulaName: String) -> Bool { - return FileSystem.fileExists("\(Paths.optPath)/\(formulaName)/INSTALL_RECEIPT.json") + return App.shared.container.filesystem.fileExists("\(Paths.optPath)/\(formulaName)/INSTALL_RECEIPT.json") } static func < (lhs: BrewPhpExtension, rhs: BrewPhpExtension) -> Bool { @@ -74,7 +74,7 @@ struct BrewPhpExtension: Hashable, Comparable { let regexPattern = #"depends_on "(.*)""# var dependencies: [String] = [] - guard let content = try? FileSystem.getStringFromFile(path) else { + guard let content = try? App.shared.container.filesystem.getStringFromFile(path) else { return [] } diff --git a/phpmon/Domain/Integrations/Homebrew/BrewPhpFormula.swift b/phpmon/Domain/Integrations/Homebrew/BrewPhpFormula.swift index 4579bdd1..3b102c66 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewPhpFormula.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewPhpFormula.swift @@ -104,7 +104,7 @@ struct BrewPhpFormula: Equatable { return false } - return FileSystem.fileExists( + return App.shared.container.filesystem.fileExists( "\(Paths.tapPath)/shivammathur/homebrew-php/Formula/php@\(version).rb" .replacingOccurrences(of: "php@" + PhpEnvironments.brewPhpAlias, with: "php") ) diff --git a/phpmon/Domain/Integrations/Homebrew/BrewPhpFormulaeHandler.swift b/phpmon/Domain/Integrations/Homebrew/BrewPhpFormulaeHandler.swift index ae8adfd4..d4516f94 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewPhpFormulaeHandler.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewPhpFormulaeHandler.swift @@ -7,6 +7,7 @@ // import Foundation +import ContainerMacro protocol HandlesBrewPhpFormulae { func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] @@ -23,6 +24,7 @@ extension HandlesBrewPhpFormulae { } } +@ContainerAccess class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae { public func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] { var outdated: [OutdatedFormula]? @@ -33,7 +35,7 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae { \(Paths.brew) outdated --json --formulae """ - let rawJsonText = await Shell.pipe(command).out + let rawJsonText = await shell.pipe(command).out .data(using: .utf8)! outdated = try? JSONDecoder().decode( OutdatedFormulae.self, diff --git a/phpmon/Domain/Integrations/Homebrew/BrewTapFormulae.swift b/phpmon/Domain/Integrations/Homebrew/BrewTapFormulae.swift index 46cdd471..aa45c4b2 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewTapFormulae.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewTapFormulae.swift @@ -12,7 +12,7 @@ class BrewTapFormulae { public static func from(tap: String) -> [String: [BrewPhpExtension]] { let directory = "\(Paths.tapPath)/\(tap)/Formula" - let files = try? FileSystem.getShallowContentsOfDirectory(directory) + let files = try? App.shared.container.filesystem.getShallowContentsOfDirectory(directory) var availableExtensions = [String: [BrewPhpExtension]]() diff --git a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift index 944bfd9d..4cf9ada6 100644 --- a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift +++ b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift @@ -26,9 +26,9 @@ struct CaskFile { private static func loadFromApi(_ url: URL) async -> String { if App.hasLoadedTestableConfiguration || url.absoluteString.contains("https://raw.githubusercontent.com") { - return await Shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out + return await App.shared.container.shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out } else { - return await Shell.pipe(""" + return await App.shared.container.shell.pipe(""" curl -s --max-time 10 \ -H "User-Agent: phpmon-curl/1.0" \ -H "X-phpmon-version: \(App.shortVersion) (\(App.bundleVersion))" \ diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/BrewCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/BrewCommand.swift index e8a68637..b1d88c01 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/BrewCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/BrewCommand.swift @@ -9,7 +9,7 @@ import Foundation protocol BrewCommand { - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws func getCommandTitle() -> String } @@ -78,10 +78,14 @@ extension BrewCommand { return nil } - internal func run(_ command: String, _ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + internal func run( + shell: ShellProtocol, + _ command: String, + _ onProgress: @escaping (BrewCommandProgress) -> Void + ) async throws { var loggedMessages: [String] = [] - let (process, _) = try! await Shell.attach( + let (process, _) = try! await shell.attach( command, didReceiveOutput: { text, _ in if !text.isEmpty { @@ -104,15 +108,18 @@ extension BrewCommand { } } - internal func checkPhpTap(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { - if !BrewDiagnostics.installedTaps.contains("shivammathur/php") { + internal func checkPhpTap( + shell: ShellProtocol, + _ onProgress: @escaping (BrewCommandProgress) -> Void + ) async throws { + if !BrewDiagnostics.shared.installedTaps.contains("shivammathur/php") { let command = "brew tap shivammathur/php" - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } - if !BrewDiagnostics.installedTaps.contains("shivammathur/extensions") { + if !BrewDiagnostics.shared.installedTaps.contains("shivammathur/extensions") { let command = "brew tap shivammathur/extensions" - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } } } diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/InstallPhpExtensionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/InstallPhpExtensionCommand.swift index 5d8a41cc..daa4eb46 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/InstallPhpExtensionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/InstallPhpExtensionCommand.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class InstallPhpExtensionCommand: BrewCommand { let installing: [BrewPhpExtension] @@ -23,7 +25,7 @@ class InstallPhpExtensionCommand: BrewCommand { self.installing = extensions } - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws { let progressTitle = "phpman.steps.wait".localized onProgress(.create( @@ -33,16 +35,16 @@ class InstallPhpExtensionCommand: BrewCommand { )) // Make sure the tap is installed - try await self.checkPhpTap(onProgress) + try await self.checkPhpTap(shell: shell, onProgress) // Make sure that the extension(s) are installed - try await self.installPackages(onProgress) + try await self.installPackages(shell, onProgress) // Finally, complete all operations await self.completedOperations(onProgress) } - private func installPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + private func installPackages(_ shell: ShellProtocol, _ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { // If no installations are needed, early exit if self.installing.isEmpty { return @@ -55,7 +57,7 @@ class InstallPhpExtensionCommand: BrewCommand { \(Paths.brew) install \(self.installing.map { $0.formulaName }.joined(separator: " ")) --force """ - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async { diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/RemovePhpExtensionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/RemovePhpExtensionCommand.swift index 60e6b5cc..f01f3052 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/RemovePhpExtensionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Extensions/RemovePhpExtensionCommand.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class RemovePhpExtensionCommand: BrewCommand { public let phpExtension: BrewPhpExtension @@ -19,7 +21,7 @@ class RemovePhpExtensionCommand: BrewCommand { return "phpman.steps.removing".localized(phpExtension.name) } - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws { onProgress(.create( value: 0.2, title: getCommandTitle(), @@ -42,7 +44,7 @@ class RemovePhpExtensionCommand: BrewCommand { var loggedMessages: [String] = [] - let (process, _) = try! await Shell.attach( + let (process, _) = try! await shell.attach( command, didReceiveOutput: { text, _ in if !text.isEmpty { @@ -77,7 +79,7 @@ class RemovePhpExtensionCommand: BrewCommand { // The extension's default configuration file can be removed Log.info("The extension was found in a default extension .ini location. Purging that .ini file.") do { - try FileSystem.remove(ext.file) + try filesystem.remove(ext.file) } catch { Log.err("The file `\(ext.file)` could not be removed.") } diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/ModifyPhpVersionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/ModifyPhpVersionCommand.swift index 144432bd..f85b8158 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/ModifyPhpVersionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/ModifyPhpVersionCommand.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class ModifyPhpVersionCommand: BrewCommand { let title: String let installing: [BrewPhpFormula] @@ -32,17 +34,19 @@ class ModifyPhpVersionCommand: BrewCommand { re-installed and linked again. */ public init( + container: Container = App.shared.container, title: String, upgrading: [BrewPhpFormula], installing: [BrewPhpFormula] ) { + self.container = container self.title = title self.installing = installing self.upgrading = upgrading self.phpGuard = PhpGuard() } - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws { let progressTitle = "phpman.steps.wait".localized onProgress(.create( @@ -58,7 +62,7 @@ class ModifyPhpVersionCommand: BrewCommand { }) // Make sure the tap is installed - try await self.checkPhpTap(onProgress) + try await self.checkPhpTap(shell: shell, onProgress) if unavailable == nil { // Try to run all upgrade and installation operations @@ -99,7 +103,7 @@ class ModifyPhpVersionCommand: BrewCommand { """ // Run the upgrade command - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } private func upgradePackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { @@ -115,7 +119,7 @@ class ModifyPhpVersionCommand: BrewCommand { \(Paths.brew) upgrade \(self.upgrading.map { $0.name }.joined(separator: " ")) """ - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } private func installPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { @@ -130,7 +134,7 @@ class ModifyPhpVersionCommand: BrewCommand { \(Paths.brew) install \(self.installing.map { $0.name }.joined(separator: " ")) --force """ - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } private func repairBrokenPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { @@ -162,7 +166,7 @@ class ModifyPhpVersionCommand: BrewCommand { \(Paths.brew) reinstall \(requiringRepair.joined(separator: " ")) --force """ - try await run(command, onProgress) + try await run(shell: shell, command, onProgress) } private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async { @@ -170,7 +174,7 @@ class ModifyPhpVersionCommand: BrewCommand { onProgress(.create(value: 0.95, title: self.title, description: "phpman.steps.reloading".localized)) // Ensure all symlinks are correctly linked - await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks() + await BrewDiagnostics.shared.checkForOutdatedPhpInstallationSymlinks() // Check which version of PHP are now installed await PhpEnvironments.detectPhpVersions() diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/RemovePhpVersionCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/RemovePhpVersionCommand.swift index 2d8bf6ec..1a1bba84 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/RemovePhpVersionCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/PHP Versions/RemovePhpVersionCommand.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class RemovePhpVersionCommand: BrewCommand { let formula: String let version: String @@ -25,7 +27,7 @@ class RemovePhpVersionCommand: BrewCommand { return "phpman.steps.removing".localized("PHP \(version)...") } - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws { onProgress(.create( value: 0.2, title: getCommandTitle(), @@ -47,7 +49,7 @@ class RemovePhpVersionCommand: BrewCommand { var loggedMessages: [String] = [] - let (process, _) = try! await Shell.attach( + let (process, _) = try! await shell.attach( command, didReceiveOutput: { text, _ in if !text.isEmpty { diff --git a/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift b/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift index ff54fadc..f17a3572 100644 --- a/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Fake/FakeCommand.swift @@ -19,7 +19,7 @@ class FakeCommand: BrewCommand { self.version = version } - func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws { + func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws { onProgress(.create(value: 0.2, title: "Hello", description: "Doing the work")) await delay(seconds: 2) onProgress(.create(value: 0.5, title: "Hello", description: "Doing some more work")) diff --git a/phpmon/Domain/Integrations/Packagist/ValetUpgrader.swift b/phpmon/Domain/Integrations/Packagist/ValetUpgrader.swift index 542a987d..4850040c 100644 --- a/phpmon/Domain/Integrations/Packagist/ValetUpgrader.swift +++ b/phpmon/Domain/Integrations/Packagist/ValetUpgrader.swift @@ -14,7 +14,7 @@ class ValetUpgrader { let path = "~/.composer/composer.json".replacingTildeWithHomeDirectory do { - if FileSystem.fileExists(path) { + if App.shared.container.filesystem.fileExists(path) { return try JSONDecoder().decode( ComposerJson.self, from: String( diff --git a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift index b51bc338..85d9f2e6 100644 --- a/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift +++ b/phpmon/Domain/Integrations/Valet/Domains/ValetInteractor.swift @@ -7,12 +7,14 @@ // import Foundation +import ContainerMacro struct ValetInteractionError: Error { /// The command the user should try (and failed). var command: String } +@ContainerAccess class ValetInteractor { static var shared = ValetInteractor() @@ -23,11 +25,11 @@ class ValetInteractor { // MARK: - Managing Domains public func link(path: String, domain: String) async throws { - await Shell.quiet("cd '\(path)' && \(Paths.valet) link '\(domain)' && valet links") + await shell.quiet("cd '\(path)' && \(Paths.valet) link '\(domain)' && valet links") } public func unlink(site: ValetSite) async throws { - await Shell.quiet("valet unlink '\(site.name)'") + await shell.quiet("valet unlink '\(site.name)'") } public func proxy(domain: String, proxy: String, secure: Bool) async throws { @@ -35,12 +37,12 @@ class ValetInteractor { ? "\(Paths.valet) proxy \(domain) \(proxy) --secure" : "\(Paths.valet) proxy \(domain) \(proxy)" - await Shell.quiet(command) + await shell.quiet(command) await Actions.restartNginx() } public func remove(proxy: ValetProxy) async throws { - await Shell.quiet("valet unproxy '\(proxy.domain)'") + await shell.quiet("valet unproxy '\(proxy.domain)'") } // MARK: - Modifying Domains @@ -62,7 +64,7 @@ class ValetInteractor { } // Run the command - await Shell.quiet(command) + await shell.quiet(command) // Check if the secured status has actually changed site.determineSecured() @@ -87,7 +89,7 @@ class ValetInteractor { // Run the commands for command in commands { - await Shell.quiet(command) + await shell.quiet(command) } // Check if the secured status has actually changed @@ -106,7 +108,7 @@ class ValetInteractor { let command = "sudo \(Paths.valet) isolate php@\(version) --site '\(site.name)'" // Run the command - await Shell.quiet(command) + await shell.quiet(command) // Check if the secured status has actually changed site.determineIsolated() @@ -122,7 +124,7 @@ class ValetInteractor { let command = "sudo \(Paths.valet) unisolate --site '\(site.name)'" // Run the command - await Shell.quiet(command) + await shell.quiet(command) // Check if the secured status has actually changed site.determineIsolated() diff --git a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift index 546e7466..85d276c7 100644 --- a/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift +++ b/phpmon/Domain/Integrations/Valet/Proxies/ValetProxy.swift @@ -78,7 +78,7 @@ class ValetProxy: ValetListable { // MARK: - Interactions func determineSecured() { - self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") + self.secured = container.filesystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key") } func toggleFavorite() { diff --git a/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift index b8f55ac3..4fd9028d 100644 --- a/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Scanners/ValetDomainScanner.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class ValetDomainScanner: DomainScanner { // MARK: - Sites @@ -15,7 +17,7 @@ class ValetDomainScanner: DomainScanner { func resolveSiteCount(paths: [String]) -> Int { return paths.map { path in do { - let entries = try FileSystem + let entries = try filesystem .getShallowContentsOfDirectory(path) return entries @@ -35,7 +37,7 @@ class ValetDomainScanner: DomainScanner { paths.forEach { path in do { - let entries = try FileSystem + let entries = try filesystem .getShallowContentsOfDirectory(path) return entries.forEach { @@ -59,7 +61,7 @@ class ValetDomainScanner: DomainScanner { // Get the TLD from the global Valet object let tld = Valet.shared.config.tld - if !FileSystem.anyExists(path) { + if !filesystem.anyExists(path) { Log.warn("Could not parse the site: \(path), skipping!") } @@ -69,9 +71,9 @@ class ValetDomainScanner: DomainScanner { return nil } - if FileSystem.isSymlink(path) { - return ValetSite(aliasPath: path, tld: tld) - } else if FileSystem.isDirectory(path) { + if filesystem.isSymlink(path) { + return ValetSite(filesystem: filesystem, aliasPath: path, tld: tld) + } else if filesystem.isDirectory(path) { return ValetSite(absolutePath: path, tld: tld) } @@ -85,7 +87,7 @@ class ValetDomainScanner: DomainScanner { private func isSite(_ entry: String, forPath path: String) -> Bool { let siteDir = path + "/" + entry - return (FileSystem.isDirectory(siteDir) || FileSystem.isSymlink(siteDir)) + return (App.shared.container.filesystem.isDirectory(siteDir) || App.shared.container.filesystem.isSymlink(siteDir)) } // MARK: - Proxies diff --git a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift index 6db96852..4aa88d52 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/ValetSite.swift @@ -96,9 +96,9 @@ class ValetSite: ValetListable { self.init(name: name, tld: tld, absolutePath: absolutePath) } - convenience init(aliasPath: String, tld: String) { + convenience init(filesystem: FileSystemProtocol, aliasPath: String, tld: String) { let name = URL(fileURLWithPath: aliasPath).lastPathComponent - let absolutePath = try! FileSystem.getDestinationOfSymlink(aliasPath) + let absolutePath = try! filesystem.getDestinationOfSymlink(aliasPath) self.init(name: name, tld: tld, absolutePath: absolutePath, aliasPath: aliasPath) } @@ -106,7 +106,7 @@ class ValetSite: ValetListable { Determine whether a site is isolated. */ public func determineIsolated() { - if let version = ValetSite.isolatedVersion("~/.config/valet/Nginx/\(self.name).\(self.tld)") { + if let version = ValetSite.isolatedVersion(container, "~/.config/valet/Nginx/\(self.name).\(self.tld)") { if !PhpEnvironments.shared.cachedPhpInstallations.keys.contains(version) { Log.err("The PHP version \(version) is isolated for the site \(self.name) " + "but that PHP version is unavailable.") @@ -123,7 +123,7 @@ class ValetSite: ValetListable { - Note: The file is not validated, only its presence is checked. */ public func determineSecured() { - secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key") + secured = filesystem.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key") } /** @@ -185,7 +185,7 @@ class ValetSite: ValetListable { let path = "\(absolutePath)/composer.json" do { - if FileSystem.fileExists(path) { + if filesystem.fileExists(path) { let decoded = try JSONDecoder().decode( ComposerJson.self, from: String( @@ -216,7 +216,7 @@ class ValetSite: ValetListable { for (suffix, source) in files { do { let path = "\(absolutePath)/\(suffix)" - if FileSystem.fileExists(path) { + if filesystem.fileExists(path) { return try self.handleValetFile(path, source) } } catch { @@ -274,8 +274,11 @@ class ValetSite: ValetListable { // MARK: - File Parsing - public static func isolatedVersion(_ filePath: String) -> String? { - if FileSystem.fileExists(filePath) { + public static func isolatedVersion( + _ container: Container = App.shared.container, + _ filePath: String + ) -> String? { + if container.filesystem.fileExists(filePath) { return NginxConfigurationFile .from(filePath: filePath)? .isolatedVersion ?? nil diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 499882ab..bde42260 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -7,12 +7,14 @@ // import Foundation +import ContainerMacro /** This class is responsible for handling the state of Valet throughout PHP Monitor. A singleton instance is created and accessible throughout the lifecycle of the app, unless the user has decided to not use Valet. In that case, only a restricted subset of functionality is available in the app. */ +@ContainerAccess class Valet { enum FeatureFlag { @@ -65,8 +67,8 @@ class Valet { } lazy var installed: Bool = { - return FileSystem.fileExists(Paths.binPath.appending("/valet")) - && FileSystem.anyExists("~/.config/valet") + return filesystem.fileExists(Paths.binPath.appending("/valet")) + && filesystem.anyExists("~/.config/valet") }() /** @@ -89,7 +91,7 @@ class Valet { and the app cannot start. */ public func updateVersionNumber() async { - let output = await Shell.pipe("valet --version").out + let output = await shell.pipe("valet --version").out // Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") { @@ -119,7 +121,7 @@ class Valet { do { config = try JSONDecoder().decode( Valet.Configuration.self, - from: FileSystem.getStringFromFile("~/.config/valet/config.json").data(using: .utf8)! + from: filesystem.getStringFromFile("~/.config/valet/config.json").data(using: .utf8)! ) } catch { Log.err(error) @@ -206,7 +208,7 @@ class Valet { Determine if any platform issues are detected when running `valet --version`. */ public func hasPlatformIssues() async -> Bool { - return await Shell.pipe("valet --version") + return await shell.pipe("valet --version") .out.contains("Composer detected issues in your platform") } @@ -248,12 +250,12 @@ class Valet { if version.short == "5.6" { // The main PHP config file should contain `valet.sock` and then we're probably fine? let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf" - return await Shell.pipe("cat \(fileName)").out + return await shell.pipe("cat \(fileName)").out .contains("valet.sock") } // Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) - return FileSystem.fileExists("\(Paths.etcPath)/php/\(version.short)/php-fpm.d/valet-fpm.conf") + return filesystem.fileExists("\(Paths.etcPath)/php/\(version.short)/php-fpm.d/valet-fpm.conf") } /** diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 2b2de975..f2f567ff 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -34,13 +34,13 @@ extension MainMenu { await PhpEnvironments.shared.determinePhpAlias() // Make sure that broken symlinks are removed ASAP - await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks() + await BrewDiagnostics.shared.checkForOutdatedPhpInstallationSymlinks() // Initialize preferences _ = Preferences.shared // Put some useful diagnostics information in log - BrewDiagnostics.logBootInformation() + BrewDiagnostics.shared.logBootInformation() // Attempt to find out more info about Valet if Valet.shared.version != nil { @@ -58,10 +58,10 @@ extension MainMenu { // Verify third party taps // The missing tap(s) will be actionable later - await BrewDiagnostics.verifyThirdPartyTaps() + await BrewDiagnostics.shared.verifyThirdPartyTaps() // Check for an alias conflict - await BrewDiagnostics.checkForCaskConflict() + await BrewDiagnostics.shared.checkForCaskConflict() // Attempt to find out if PHP-FPM is broken PhpEnvironments.prepare() @@ -92,7 +92,7 @@ extension MainMenu { await Valet.shared.startPreloadingSites() // After preloading sites, check for PHP-FPM pool conflicts - await BrewDiagnostics.checkForValetMisconfiguration() + await BrewDiagnostics.shared.checkForValetMisconfiguration() // Check if PHP-FPM is broken (should be fixed automatically if phpmon >= 6.0) await Valet.shared.notifyAboutBrokenPhpFpm() diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index eaa9c0ff..ef634156 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -45,7 +45,7 @@ struct CustomPrefs: Decodable { extension Preferences { func loadCustomPreferences() async { // Ensure the configuration directory is created if missing - await Shell.quiet("mkdir -p ~/.config/phpmon") + await App.shared.container.shell.quiet("mkdir -p ~/.config/phpmon") // Move the legacy file await moveOutdatedConfigurationFile() @@ -64,7 +64,7 @@ extension Preferences { func moveOutdatedConfigurationFile() async { if FileSystem.fileExists("~/.phpmon.conf.json") && !FileSystem.fileExists("~/.config/phpmon/config.json") { Log.info("An outdated configuration file was found. Moving it...") - await Shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") + await App.shared.container.shell.quiet("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json") Log.info("The configuration file was copied successfully!") } } @@ -88,7 +88,7 @@ extension Preferences { if customPreferences.hasEnvironmentVariables() { Log.info("Configuring the additional exports...") - if let shell = Shell as? RealShell { + if let shell = App.shared.container.shell as? RealShell { shell.exports = customPreferences.exportAsString } } diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index 42342b50..7267ea05 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -106,7 +106,7 @@ class Stats { */ public static func evaluateSponsorMessageShouldBeDisplayed() { - if Shell is TestableShell { + if App.shared.container.shell is TestableShell { return Log.info("A fake shell is in use, skipping sponsor alert.") } diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index 7e128bc2..d0956ff1 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -272,7 +272,7 @@ struct Preset: Codable, Equatable { private func persistRevert() async { let data = try! JSONEncoder().encode(self.revertSnapshot) - await Shell.quiet("mkdir -p ~/.config/phpmon") + await App.shared.container.shell.quiet("mkdir -p ~/.config/phpmon") try! String(data: data, encoding: .utf8)! .write( diff --git a/phpmon/Domain/Provision/InstallHomebrew.swift b/phpmon/Domain/Provision/InstallHomebrew.swift index 50d0b381..52dd4dcb 100644 --- a/phpmon/Domain/Provision/InstallHomebrew.swift +++ b/phpmon/Domain/Provision/InstallHomebrew.swift @@ -6,6 +6,9 @@ // Copyright © 2025 Nico Verbruggen. All rights reserved. // +import ContainerMacro + +@ContainerAccess class InstallHomebrew { public func run() async throws { let script = """ @@ -13,7 +16,7 @@ class InstallHomebrew { "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" """ - _ = try await Shell.attach(script, didReceiveOutput: { (string: String, _: ShellStream) in + _ = try await shell.attach(script, didReceiveOutput: { (string: String, _: ShellStream) in print(string) }, withTimeout: 60 * 10) } diff --git a/phpmon/Domain/Provision/ZshRunCommand.swift b/phpmon/Domain/Provision/ZshRunCommand.swift index a7f7308f..b2bcdc62 100644 --- a/phpmon/Domain/Provision/ZshRunCommand.swift +++ b/phpmon/Domain/Provision/ZshRunCommand.swift @@ -6,12 +6,15 @@ // Copyright © 2025 Nico Verbruggen. All rights reserved. // +import ContainerMacro + +@ContainerAccess class ZshRunCommand { /** Adds a given line to .zshrc, which may be needed to adjust the PATH. */ private func add(_ text: String) async -> Bool { - let outcome = await Shell.pipe(""" + let outcome = await shell.pipe(""" touch ~/.zshrc && \ grep -qxF '\(text)' ~/.zshrc \ || echo '\n\n\(text)\n' >> ~/.zshrc diff --git a/phpmon/Domain/Terminal/Paths.swift b/phpmon/Domain/Terminal/Paths.swift index 1a1cf8db..00542174 100644 --- a/phpmon/Domain/Terminal/Paths.swift +++ b/phpmon/Domain/Terminal/Paths.swift @@ -16,11 +16,14 @@ class Paths { static let shared = Paths() var baseDir: HomebrewDir - var userName = String(Shell.pipe("whoami").split(separator: "\n")[0]) + var userName: String init() { - let optBrewFound = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") - let usrBrewFound = Shell.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew") + let shell = App.shared.container.shell + self.userName = String(shell.sync("whoami").out.split(separator: "\n")[0]) + + let optBrewFound = App.shared.container.filesystem.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") + let usrBrewFound = App.shared.container.filesystem.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew") if optBrewFound { // This is usually the case with Homebrew installed on Apple Silicon diff --git a/phpmon/Domain/Watcher/ConfigWatchManager.swift b/phpmon/Domain/Watcher/ConfigWatchManager.swift index 91d6a6e6..2207bc9e 100644 --- a/phpmon/Domain/Watcher/ConfigWatchManager.swift +++ b/phpmon/Domain/Watcher/ConfigWatchManager.swift @@ -59,7 +59,7 @@ class ConfigWatchManager { eventMask: DispatchSource.FileSystemEvent, behaviour: ConfigFSNotifier.Behaviour = .reloadsMenu ) { - if !FileSystem.anyExists(url.path) { + if !App.shared.container.filesystem.anyExists(url.path) { Log.warn("No watcher was created for \(url.path) because the requested file does not exist.") return } diff --git a/phpmon/Modules/Domain List/UI/AddSiteVC.swift b/phpmon/Modules/Domain List/UI/AddSiteVC.swift index a6f3bb7c..8aeb325d 100644 --- a/phpmon/Modules/Domain List/UI/AddSiteVC.swift +++ b/phpmon/Modules/Domain List/UI/AddSiteVC.swift @@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { let path = pathControl.url!.path let name = inputDomainName.stringValue - if !FileSystem.anyExists(path) { + if !App.shared.container.filesystem.anyExists(path) { Alert.confirm( onWindow: view.window!, messageText: "domain_list.alert.folder_missing.title".localized, diff --git a/phpmon/Modules/Domain List/UI/DomainListVC+Actions.swift b/phpmon/Modules/Domain List/UI/DomainListVC+Actions.swift index 3d15f651..e3bef077 100644 --- a/phpmon/Modules/Domain List/UI/DomainListVC+Actions.swift +++ b/phpmon/Modules/Domain List/UI/DomainListVC+Actions.swift @@ -32,11 +32,11 @@ extension DomainListVC { } @objc func openInFinder() { - Task { return await Shell.quiet("open '\(selectedSite!.absolutePath)'") } + Task { return await App.shared.container.shell.quiet("open '\(selectedSite!.absolutePath)'") } } @objc func openInTerminal() { - Task { await Shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } + Task { await App.shared.container.shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") } } @objc func openWithEditor(sender: EditorMenuItem) { @@ -50,7 +50,7 @@ extension DomainListVC { let rowToReload = tableView.selectedRow waitAndExecute { - await Shell.quiet(command) + await App.shared.container.shell.quiet(command) } completion: { [self] in beforeCellReload() tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) diff --git a/phpmon/Modules/PHP Config Editor/Data/BytePhpPreference.swift b/phpmon/Modules/PHP Config Editor/Data/BytePhpPreference.swift index 5bf4b336..3a7e39a8 100644 --- a/phpmon/Modules/PHP Config Editor/Data/BytePhpPreference.swift +++ b/phpmon/Modules/PHP Config Editor/Data/BytePhpPreference.swift @@ -7,7 +7,9 @@ // import Foundation +import ContainerMacro +@ContainerAccess class BytePhpPreference: PhpPreference { enum UnitOption: String, CaseIterable { case kilobyte = "K" @@ -35,8 +37,8 @@ class BytePhpPreference: PhpPreference { didSet { updatedFieldValue() } } - override init(key: String) { - let value = Command.execute( + init(container: Container = App.shared.container, key: String) { + let value = container.command.execute( path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"], trimNewlines: false ) @@ -47,6 +49,7 @@ class BytePhpPreference: PhpPreference { self.value = value } super.init(key: key) + self.container = container } // MARK: Save Value diff --git a/phpmon/Modules/PHP Doctor/Data/PhpConfigChecker.swift b/phpmon/Modules/PHP Doctor/Data/PhpConfigChecker.swift index 23acdf08..fb3c6dc6 100644 --- a/phpmon/Modules/PHP Doctor/Data/PhpConfigChecker.swift +++ b/phpmon/Modules/PHP Doctor/Data/PhpConfigChecker.swift @@ -36,7 +36,7 @@ class PhpConfigChecker { // Do the check let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file.path)") - if !FileSystem.fileExists(fullFilePath) { + if !App.shared.container.filesystem.fileExists(fullFilePath) { missing.append(fullFilePath) } } diff --git a/phpmon/Modules/PHP Doctor/Data/WarningManager+Evaluations.swift b/phpmon/Modules/PHP Doctor/Data/WarningManager+Evaluations.swift index 05b48360..2e4af906 100644 --- a/phpmon/Modules/PHP Doctor/Data/WarningManager+Evaluations.swift +++ b/phpmon/Modules/PHP Doctor/Data/WarningManager+Evaluations.swift @@ -12,7 +12,7 @@ extension WarningManager { return [ Warning( command: { - return await Shell.pipe("sysctl -n sysctl.proc_translated").out + return await App.shared.container.shell.pipe("sysctl -n sysctl.proc_translated").out .trimmingCharacters(in: .whitespacesAndNewlines) == "1" }, name: "Running PHP Monitor with Rosetta on Apple Silicon", @@ -23,7 +23,7 @@ extension WarningManager { ), Warning( command: { - return !Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && + return !App.shared.container.shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") && !FileSystem.isWriteableFile("/usr/local/bin/") }, name: "Helpers cannot be symlinked and not in PATH", @@ -73,7 +73,7 @@ extension WarningManager { ), Warning( command: { - !BrewDiagnostics.installedTaps.contains("shivammathur/php") + !BrewDiagnostics.shared.installedTaps.contains("shivammathur/php") }, name: "`shivammathur/php` tap is missing", title: "warnings.php_tap_missing.title", @@ -82,14 +82,14 @@ extension WarningManager { ] }, url: "https://github.com/shivammathur/homebrew-php", fix: { - await Shell.quiet("brew tap shivammathur/php") - await BrewDiagnostics.loadInstalledTaps() + await App.shared.container.shell.quiet("brew tap shivammathur/php") + await BrewDiagnostics.shared.loadInstalledTaps() await self.checkEnvironment() } ), Warning( command: { - !BrewDiagnostics.installedTaps.contains("shivammathur/extensions") + !BrewDiagnostics.shared.installedTaps.contains("shivammathur/extensions") }, name: "`shivammathur/extensions` tap is missing", title: "warnings.extensions_tap_missing.title", @@ -98,8 +98,8 @@ extension WarningManager { ] }, url: "https://github.com/shivammathur/homebrew-extensions", fix: { - await Shell.quiet("brew tap shivammathur/extensions") - await BrewDiagnostics.loadInstalledTaps() + await App.shared.container.shell.quiet("brew tap shivammathur/extensions") + await BrewDiagnostics.shared.loadInstalledTaps() await self.checkEnvironment() } ), diff --git a/phpmon/Modules/PHP Doctor/Data/WarningManager.swift b/phpmon/Modules/PHP Doctor/Data/WarningManager.swift index b48d4d42..b00014df 100644 --- a/phpmon/Modules/PHP Doctor/Data/WarningManager.swift +++ b/phpmon/Modules/PHP Doctor/Data/WarningManager.swift @@ -61,7 +61,7 @@ class WarningManager: ObservableObject { func checkEnvironment() async { container.shell.reload() - await BrewDiagnostics.loadInstalledTaps() + await BrewDiagnostics.shared.loadInstalledTaps() if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil { self.temporaryWarnings = self.evaluations diff --git a/phpmon/Modules/PHP Extension Manager/UI/PhpExtensionManagerView+Actions.swift b/phpmon/Modules/PHP Extension Manager/UI/PhpExtensionManagerView+Actions.swift index eae06b18..937a35bd 100644 --- a/phpmon/Modules/PHP Extension Manager/UI/PhpExtensionManagerView+Actions.swift +++ b/phpmon/Modules/PHP Extension Manager/UI/PhpExtensionManagerView+Actions.swift @@ -66,7 +66,7 @@ extension PhpExtensionManagerView { do { self.status.busy = true - try await command.execute { progress in + try await command.execute(shell: App.shared.container.shell) { progress in Task { @MainActor in self.status.title = progress.title self.status.description = progress.description diff --git a/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView+Actions.swift b/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView+Actions.swift index 5bd71273..b893647a 100644 --- a/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView+Actions.swift +++ b/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView+Actions.swift @@ -22,7 +22,7 @@ extension PhpVersionManagerView { do { self.setBusyStatus(true) - try await command.execute { progress in + try await command.execute(shell: App.shared.container.shell) { progress in Task { @MainActor in self.status.title = progress.title self.status.description = progress.description @@ -107,7 +107,7 @@ extension PhpVersionManagerView { do { self.setBusyStatus(true) - try await command.execute { progress in + try await command.execute(shell: App.shared.container.shell) { progress in Task { @MainActor in self.status.title = progress.title self.status.description = progress.description