1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-04-05 18:50:08 +02:00

🚧 WIP: Refactoring

This commit is contained in:
2025-10-09 15:09:42 +02:00
parent c62e3a9905
commit a4aa28313d
57 changed files with 260 additions and 282 deletions

View File

@@ -87,8 +87,6 @@
03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; };
03CC1FF62E3D23130050FC18 /* 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 */; }; 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 */; }; 03FE39E72E81682800B7B5AC /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E52E81682800B7B5AC /* AppIcon.icon */; };
03FE39E82E81682800B7B5AC /* AppIconEAP.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */; }; 03FE39E82E81682800B7B5AC /* AppIconEAP.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */; };
03FE39EA2E81694500B7B5AC /* AppIconUD.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E92E81694500B7B5AC /* AppIconUD.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 */; }; C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150A2C46D81E0069AAE7 /* NVAlert */; };
C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150C2C46D83E0069AAE7 /* NVAlert */; }; C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150C2C46D83E0069AAE7 /* NVAlert */; };
C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; 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 */; }; C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C471E79528F9B2420021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471E7BE28F9B90F0021E251 /* StartupTest.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 */; }; C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4928DB966A007ACC74 /* TestableShell.swift */; };
C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; };
C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.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 */; }; C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.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 */; }; C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40F505428ECA64E004AD45B /* TestableConfigurations.swift */; };
C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; }; C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.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 */; }; C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; };
C471E7DE28F9BAA30021E251 /* 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 */; }; 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 */; }; 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 */; }; C471E7E328F9BAC20021E251 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2F27722E8D00DDDCDC /* Logger.swift */; };
C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
C471E7E528F9BAC20021E251 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.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 */; }; C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; }; C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.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 */; }; 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 */; }; C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.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 */; }; C4E2E86A28FC3002003B070C /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; };
C4E4404727C56F4700D225E1 /* 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 */; }; C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */; };
C4E49DEB28F7643D0026AC4E /* 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 */; }; 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 = "<group>"; }; 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = "<group>"; };
03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallHomebrew.swift; sourceTree = "<group>"; }; 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallHomebrew.swift; sourceTree = "<group>"; };
03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZshRunCommand.swift; sourceTree = "<group>"; }; 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZshRunCommand.swift; sourceTree = "<group>"; };
03E36FE628D9219000636F7F /* ActiveShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveShell.swift; sourceTree = "<group>"; };
03FE39E52E81682800B7B5AC /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; }; 03FE39E52E81682800B7B5AC /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
03FE39E62E81682800B7B5AC /* AppIconEAP.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconEAP.icon; sourceTree = "<group>"; }; 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconEAP.icon; sourceTree = "<group>"; };
03FE39E92E81694500B7B5AC /* AppIconUD.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconUD.icon; sourceTree = "<group>"; }; 03FE39E92E81694500B7B5AC /* AppIconUD.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconUD.icon; sourceTree = "<group>"; };
@@ -1214,7 +1201,6 @@
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; }; C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; };
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; }; C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; };
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; }; C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; };
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; };
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; }; C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = "<group>"; }; C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = "<group>"; };
C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = "<group>"; }; C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = "<group>"; };
@@ -1248,7 +1234,6 @@
C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfiguration.swift; sourceTree = "<group>"; }; C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableConfiguration.swift; sourceTree = "<group>"; };
C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCPMApplication.swift; sourceTree = "<group>"; }; C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCPMApplication.swift; sourceTree = "<group>"; };
C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = "<group>"; }; C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = "<group>"; };
C4E49DE628F764050026AC4E /* ActiveCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommand.swift; sourceTree = "<group>"; };
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = "<group>"; }; C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandProtocol.swift; sourceTree = "<group>"; };
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = "<group>"; }; C4E49DEC28F764A00026AC4E /* TestableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableCommand.swift; sourceTree = "<group>"; };
C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = "<group>"; }; C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewTapFormulae.swift; sourceTree = "<group>"; };
@@ -2229,7 +2214,6 @@
C4C8900128F0E27900CE5E97 /* Filesystem */ = { C4C8900128F0E27900CE5E97 /* Filesystem */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */,
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */, C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */,
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */, C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */,
); );
@@ -2297,7 +2281,6 @@
C4E49DE528F763E20026AC4E /* Command */ = { C4E49DE528F763E20026AC4E /* Command */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4E49DE628F764050026AC4E /* ActiveCommand.swift */,
C4B5853D2770FE3900DA4FBE /* RealCommand.swift */, C4B5853D2770FE3900DA4FBE /* RealCommand.swift */,
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */, C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */,
); );
@@ -2356,7 +2339,6 @@
C4F787A628EF811000790735 /* Shell */ = { C4F787A628EF811000790735 /* Shell */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
03E36FE628D9219000636F7F /* ActiveShell.swift */,
C46EBC4628DB9644007ACC74 /* RealShell.swift */, C46EBC4628DB9644007ACC74 /* RealShell.swift */,
C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */, C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */,
); );
@@ -2702,7 +2684,6 @@
C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */, C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */, C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */,
C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */, C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */,
C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */, C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
C43931CA29C4C03F0069165B /* Brew.swift in Sources */, C43931CA29C4C03F0069165B /* Brew.swift in Sources */,
@@ -2728,7 +2709,6 @@
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */,
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */, C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */,
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */, 032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
@@ -2815,7 +2795,6 @@
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */,
03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */, 03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */,
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */,
C4D5857C2A7038DB00DDBB63 /* ByteLimitView.swift in Sources */, C4D5857C2A7038DB00DDBB63 /* ByteLimitView.swift in Sources */,
@@ -3059,7 +3038,6 @@
C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */, C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */,
C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */,
C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */, C471E7E128F9BAAB0021E251 /* RealCommand.swift in Sources */,
C471E7E228F9BAAB0021E251 /* ActiveCommand.swift in Sources */,
03263A392E86D5EC00BD0415 /* UpdateScheduler.swift in Sources */, 03263A392E86D5EC00BD0415 /* UpdateScheduler.swift in Sources */,
C471E80A28F9BADC0021E251 /* CreatedFromFile.swift in Sources */, C471E80A28F9BADC0021E251 /* CreatedFromFile.swift in Sources */,
C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, C471E80528F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */,
@@ -3092,7 +3070,6 @@
C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */,
C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */,
C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */, C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */,
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */, C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */, C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */,
C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */, C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
@@ -3110,7 +3087,6 @@
C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */, C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */,
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
C471E7D428F9BA8F0021E251 /* ActiveFileSystem.swift in Sources */,
032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, 032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */,
C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */, C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */, C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */,
@@ -3273,7 +3249,6 @@
03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */, 03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */, C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */, C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */,
C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */, C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */, C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */,
039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */, 039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
@@ -3288,7 +3263,6 @@
C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */, C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */,
C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */, C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */,
C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */, C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C471E7E028F9BAAB0021E251 /* ActiveCommand.swift in Sources */,
C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */, C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */,
C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */, C471E80928F9BADC0021E251 /* CreatedFromFile.swift in Sources */,
C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */, C471E80128F9BAD40021E251 /* ActivePhpInstallation.swift in Sources */,
@@ -3312,7 +3286,6 @@
C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */, C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */, C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */,
C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */, C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */,
C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */, C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */,
C471E7F528F9BAC80021E251 /* PhpEnvironments.swift in Sources */, C471E7F528F9BAC80021E251 /* PhpEnvironments.swift in Sources */,
C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, C471E7ED28F9BAC30021E251 /* Process.swift in Sources */,
@@ -3486,13 +3459,10 @@
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
5489625928313231004F647A /* CreatedFromFile.swift in Sources */, 5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */, C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */,
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */,
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */, 0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */,
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */,
C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */, 036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,

View File

@@ -13,6 +13,8 @@ public struct ContainerAccessMacro: MemberMacro {
// This should be kept in sync with the Container class // This should be kept in sync with the Container class
let allContainerServices: [(name: String, type: String)] = [ let allContainerServices: [(name: String, type: String)] = [
("shell", "ShellProtocol"), ("shell", "ShellProtocol"),
("filesystem", "FileSystemProtocol"),
("command", "CommandProtocol"),
("favorites", "Favorites"), ("favorites", "Favorites"),
("warningManager", "WarningManager") ("warningManager", "WarningManager")
] ]

View File

@@ -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()
}
}

View File

@@ -112,7 +112,7 @@ class Actions {
// MARK: - Other Actions // MARK: - Other Actions
public func createTempPhpInfoFile() async -> URL { public func createTempPhpInfoFile() async -> URL {
try! FileSystem.writeAtomicallyToFile("/tmp/phpmon_phpinfo.php", content: "<?php phpinfo();") try! container.filesystem.writeAtomicallyToFile("/tmp/phpmon_phpinfo.php", content: "<?php phpinfo();")
// Tell php-cgi to run the PHP and output as an .html file // Tell php-cgi to run the PHP and output as an .html file
await container.shell.quiet("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html") await container.shell.quiet("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")

View File

@@ -13,32 +13,38 @@ import Foundation
/** /**
Runs a `brew` command. Can run as superuser. Runs a `brew` command. Can run as superuser.
*/ */
func brew(_ command: String, sudo: Bool = false) async { func brew(_ command: String, sudo: Bool = false, shell: ShellProtocol = App.shared.container.shell) async {
await Shell.quiet("\(sudo ? "sudo " : "")" + "\(Paths.brew) \(command)") 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. 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) // Escape slashes (or `sed` won't work)
let e_original = original.replacingOccurrences(of: "/", with: "\\/") let e_original = original.replacingOccurrences(of: "/", with: "\\/")
let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/") let e_replacement = replacement.replacingOccurrences(of: "/", with: "\\/")
// Check if gsed exists; it is able to follow symlinks, // Check if gsed exists; it is able to follow symlinks,
// which we want to do to toggle the extension // which we want to do to toggle the extension
if FileSystem.fileExists("\(Paths.binPath)/gsed") { if filesystem.fileExists("\(Paths.binPath)/gsed") {
await Shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)") await shell.quiet("\(Paths.binPath)/gsed -i --follow-symlinks 's/\(e_original)/\(e_replacement)/g' \(file)")
} else { } 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. Uses `grep` to determine whether a particular query string can be found in a particular file.
*/ */
func grepContains(file: String, query: String) async -> Bool { func grepContains(file: String, query: String, shell: ShellProtocol = App.shared.container.shell) async -> Bool {
return await Shell.pipe(""" return await shell.pipe("""
grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO" grep -q '\(query)' \(file); [ $? -eq 0 ] && echo "YES" || echo "NO"
""").out """).out
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)

View File

@@ -22,7 +22,7 @@ struct HomebrewFormulae {
} }
static var nginx: HomebrewFormula { static var nginx: HomebrewFormula {
return BrewDiagnostics.usesNginxFullFormula return BrewDiagnostics.shared.usesNginxFullFormula
? HomebrewFormula("nginx-full", elevated: true) ? HomebrewFormula("nginx-full", elevated: true)
: HomebrewFormula("nginx", elevated: true) : HomebrewFormula("nginx", elevated: true)
} }

View File

@@ -33,7 +33,7 @@ class Log {
system_quiet("mkdir -p ~/.config/phpmon 2> /dev/null") system_quiet("mkdir -p ~/.config/phpmon 2> /dev/null")
system_quiet("rm ~/.config/phpmon/last_session.log 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") 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)
} }
} }

View File

@@ -25,8 +25,8 @@ public class Paths {
// Ensure that if a different location is used, it takes precendence // Ensure that if a different location is used, it takes precendence
if baseDir == .usr if baseDir == .usr
&& FileSystem.directoryExists("/usr/local/homebrew") && App.shared.container.filesystem.directoryExists("/usr/local/homebrew")
&& !FileSystem.directoryExists("/usr/local/Cellar") { && !App.shared.container.filesystem.directoryExists("/usr/local/Cellar") {
Log.warn("Using /usr/local/homebrew as base directory!") Log.warn("Using /usr/local/homebrew as base directory!")
baseDir = .usr_hb baseDir = .usr_hb
} }
@@ -74,12 +74,12 @@ public class Paths {
} }
public static var homePath: String { public static var homePath: String {
if FileSystem is RealFileSystem { if App.shared.container.filesystem is RealFileSystem {
return NSHomeDirectory() return NSHomeDirectory()
} }
if FileSystem is TestableFileSystem { if App.shared.container.filesystem is TestableFileSystem {
let fs = FileSystem as! TestableFileSystem let fs = App.shared.container.filesystem as! TestableFileSystem
return fs.homeDirectory return fs.homeDirectory
} }
@@ -123,11 +123,11 @@ public class Paths {
// (PHP Monitor will not use the user's own PATH) // (PHP Monitor will not use the user's own PATH)
private func detectComposerBinary() { 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" 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" 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" Paths.composer = "/usr/local/homebrew/bin/composer"
} else { } else {
Paths.composer = nil Paths.composer = nil

View File

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

View File

@@ -7,10 +7,12 @@
// //
import Foundation import Foundation
import ContainerMacro
/// An application that is capable of opening a particular directory (usually of a PHP project). /// 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 /// 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. /// that supports opening those directories, like a visual Git client or a terminal app.
@ContainerAccess
class Application { class Application {
enum AppType { enum AppType {
@@ -34,19 +36,19 @@ class Application {
(This will open the app if it isn't open yet.) (This will open the app if it isn't open yet.)
*/ */
@objc public func openDirectory(file: String) { @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. */ /** Checks if the app is installed. */
func isInstalled() async -> Bool { func isInstalled() async -> Bool {
let (process, output) = try! await Shell.attach( let (process, output) = try! await shell.attach(
"/usr/bin/open -Ra \"\(name)\"", "/usr/bin/open -Ra \"\(name)\"",
didReceiveOutput: { _, _ in }, didReceiveOutput: { _, _ in },
withTimeout: 2.0 withTimeout: 2.0
) )
if Shell is TestableShell { if shell is TestableShell {
// When testing, check the error output (must not be empty) // When testing, check the error output (must not be empty)
return !output.hasError return !output.hasError
} else { } else {

View File

@@ -41,7 +41,7 @@ class ActivePhpInstallation {
// MARK: - Initializer // MARK: - Initializer
public static func load() -> ActivePhpInstallation? { public static func load() -> ActivePhpInstallation? {
if !FileSystem.fileExists(Paths.phpConfig) { if !App.shared.container.filesystem.fileExists(Paths.phpConfig) {
return nil 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. _or_ if the output contains the word "Warning" or "Error". In normal situations this should not be the case.
*/ */
private func determineVersion() throws { 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")) 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`. - 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 { 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 // Check if the value is unlimited
if value == "-1" { if value == "-1" {

View File

@@ -44,8 +44,8 @@ class PhpEnvironments {
// Check if that version actually corresponds to an older version // Check if that version actually corresponds to an older version
let phpConfigExecutablePath = "\(Paths.optPath)/php/bin/php-config" let phpConfigExecutablePath = "\(Paths.optPath)/php/bin/php-config"
if FileSystem.fileExists(phpConfigExecutablePath) { if container.filesystem.fileExists(phpConfigExecutablePath) {
let longVersionString = Command.execute( let longVersionString = container.command.execute(
path: phpConfigExecutablePath, path: phpConfigExecutablePath,
arguments: ["--version"], arguments: ["--version"],
trimNewlines: false trimNewlines: false
@@ -115,7 +115,7 @@ class PhpEnvironments {
static var homebrewBrewPhpAlias: String { static var homebrewBrewPhpAlias: String {
if PhpEnvironments.shared.homebrewPackage == nil { if PhpEnvironments.shared.homebrewPackage == nil {
// For UI testing and as a fallback, determine this version by using (fake) php-config // 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"], arguments: ["--version"],
trimNewlines: true) trimNewlines: true)
return try! VersionNumber.parse(version).short return try! VersionNumber.parse(version).short
@@ -184,7 +184,7 @@ class PhpEnvironments {
let phpAlias = homebrewPackage.version let phpAlias = homebrewPackage.version
// Avoid inserting a duplicate // 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) let phpAliasInstall = PhpInstallation(phpAlias)
// Before inserting, ensure that the actual output matches the alias // Before inserting, ensure that the actual output matches the alias
// if that isn't the case, our formula remains out-of-date // 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) // is supported and where the binary exists (avoids broken installs)
if !output.contains(version) if !output.contains(version)
&& supported.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) output.insert(version)
} }
} }

View File

@@ -26,20 +26,20 @@ class PhpHelper {
let inPath = container.shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") let inPath = container.shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
// Check if we can create symlinks (`/usr/local/bin` must be writable) // 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 Task { // Create the appropriate folders and check if the files exist
do { do {
if !FileSystem.directoryExists("~/.config/phpmon/bin") { if !App.shared.container.filesystem.directoryExists("~/.config/phpmon/bin") {
Task { @MainActor in Task { @MainActor in
try FileSystem.createDirectory( try App.shared.container.filesystem.createDirectory(
"~/.config/phpmon/bin", "~/.config/phpmon/bin",
withIntermediateDirectories: true withIntermediateDirectories: true
) )
} }
} }
if FileSystem.fileExists(destination) { if App.shared.container.filesystem.fileExists(destination) {
let contents = try String(contentsOfFile: destination) let contents = try String(contentsOfFile: destination)
if !contents.contains(keyPhrase) { if !contents.contains(keyPhrase) {
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor " 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) : zshScript(path, keyPhrase, version, dotless)
Task { @MainActor in Task { @MainActor in
try FileSystem.writeAtomicallyToFile(destination, content: script) try App.shared.container.filesystem.writeAtomicallyToFile(destination, content: script)
if !FileSystem.isExecutableFile(destination) { if !App.shared.container.filesystem.isExecutableFile(destination) {
try FileSystem.makeExecutable(destination) try App.shared.container.filesystem.makeExecutable(destination)
} }
} }
@@ -121,13 +121,13 @@ class PhpHelper {
let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)" let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
let destination = "/usr/local/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)") Log.info("Creating new symlink: \(destination)")
await container.shell.quiet("ln -s \(source) \(destination)") await container.shell.quiet("ln -s \(source) \(destination)")
return return
} }
if !FileSystem.isSymlink(destination) { if !App.shared.container.filesystem.isSymlink(destination) {
Log.info("Overwriting existing file with new symlink: \(destination)") Log.info("Overwriting existing file with new symlink: \(destination)")
await container.shell.quiet("ln -fs \(source) \(destination)") await container.shell.quiet("ln -fs \(source) \(destination)")
return return

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class PhpInstallation { class PhpInstallation {
var versionNumber: VersionNumber var versionNumber: VersionNumber
@@ -54,8 +56,8 @@ class PhpInstallation {
} }
private func determineVersion(_ phpConfigExecutablePath: String, _ phpExecutablePath: String) { private func determineVersion(_ phpConfigExecutablePath: String, _ phpExecutablePath: String) {
if FileSystem.fileExists(phpConfigExecutablePath) { if filesystem.fileExists(phpConfigExecutablePath) {
let longVersionString = Command.execute( let longVersionString = command.execute(
path: phpConfigExecutablePath, path: phpConfigExecutablePath,
arguments: ["--version"], arguments: ["--version"],
trimNewlines: false trimNewlines: false
@@ -76,8 +78,8 @@ class PhpInstallation {
} }
private func determineHealth(_ phpExecutablePath: String) { private func determineHealth(_ phpExecutablePath: String) {
if FileSystem.fileExists(phpExecutablePath) { if filesystem.fileExists(phpExecutablePath) {
let testCommand = Command.execute( let testCommand = command.execute(
path: phpExecutablePath, path: phpExecutablePath,
arguments: ["-v"], arguments: ["-v"],
trimNewlines: false, trimNewlines: false,
@@ -94,7 +96,7 @@ class PhpInstallation {
} }
private func determineIniFiles(_ phpExecutablePath: String) { private func determineIniFiles(_ phpExecutablePath: String) {
let paths = Shell let paths = shell
.sync("\(phpExecutablePath) --ini | grep -E -o '(/[^ ]+\\.ini)'").out .sync("\(phpExecutablePath) --ini | grep -E -o '(/[^ ]+\\.ini)'").out
.split(separator: "\n") .split(separator: "\n")
.map { String($0) } .map { String($0) }

View File

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

View File

@@ -124,8 +124,6 @@ public struct TestableConfiguration: Codable {
Log.separator() Log.separator()
Log.info("Applying to container...") Log.info("Applying to container...")
App.shared.container.overrideWith(config: self) App.shared.container.overrideWith(config: self)
Log.info("Applying fake commands...")
ActiveCommand.useTestable(commandOutput)
Log.info("Applying temporary preference overrides...") Log.info("Applying temporary preference overrides...")
preferenceOverrides.forEach { (key: PreferenceName, value: Any?) in preferenceOverrides.forEach { (key: PreferenceName, value: Any?) in
Preferences.shared.cachedPreferences[key] = value Preferences.shared.cachedPreferences[key] = value

View File

@@ -9,6 +9,7 @@
class Container { class Container {
var shell: ShellProtocol! var shell: ShellProtocol!
var filesystem: FileSystemProtocol! var filesystem: FileSystemProtocol!
var command: CommandProtocol!
var favorites: Favorites! var favorites: Favorites!
var warningManager: WarningManager! var warningManager: WarningManager!
@@ -18,6 +19,7 @@ class Container {
public func prepare() { public func prepare() {
self.shell = RealShell(container: self) self.shell = RealShell(container: self)
self.filesystem = RealFileSystem(container: self) self.filesystem = RealFileSystem(container: self)
self.command = RealCommand()
self.favorites = Favorites() self.favorites = Favorites()
self.warningManager = WarningManager(container: self) self.warningManager = WarningManager(container: self)
@@ -26,5 +28,6 @@ class Container {
public func overrideWith(config: TestableConfiguration) { public func overrideWith(config: TestableConfiguration) {
self.shell = TestableShell(expectations: config.shellOutput) self.shell = TestableShell(expectations: config.shellOutput)
self.filesystem = TestableFileSystem(files: config.filesystem) self.filesystem = TestableFileSystem(files: config.filesystem)
self.command = TestableCommand(commands: config.commandOutput)
} }
} }

View File

@@ -75,7 +75,7 @@ class AppUpdater {
.localized(latestVersionOnline.humanReadable), .localized(latestVersionOnline.humanReadable),
subtitle: "updater.alerts.newer_version_available.subtitle" subtitle: "updater.alerts.newer_version_available.subtitle"
.localized, .localized,
description: BrewDiagnostics.customCaskInstalled description: BrewDiagnostics.shared.customCaskInstalled
? "updater.installation_source.brew".localized(command) ? "updater.installation_source.brew".localized(command)
: "updater.installation_source.direct".localized : "updater.installation_source.direct".localized
) )
@@ -152,7 +152,7 @@ class AppUpdater {
system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/PHP Monitor Self-Updater.app\"") system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/PHP Monitor Self-Updater.app\"")
try! FileSystem.writeAtomicallyToFile( try! App.shared.container.filesystem.writeAtomicallyToFile(
"\(updaterDirectory)/update.json", "\(updaterDirectory)/update.json",
content: "{ \"url\": \"\(caskFile.url)\", \"sha256\": \"\(caskFile.sha256)\" }" content: "{ \"url\": \"\(caskFile.url)\", \"sha256\": \"\(caskFile.sha256)\" }"
) )
@@ -168,10 +168,10 @@ class AppUpdater {
private func cleanupCaskroom() { private func cleanupCaskroom() {
let path = Paths.caskroomPath let path = Paths.caskroomPath
if FileSystem.directoryExists(path) { if App.shared.container.filesystem.directoryExists(path) {
Log.info("Removing the Caskroom directory for PHP Monitor...") Log.info("Removing the Caskroom directory for PHP Monitor...")
do { do {
try FileSystem.remove(path) try App.shared.container.filesystem.remove(path)
Log.info("Removed the Caskroom directory at `\(path)`.") Log.info("Removed the Caskroom directory at `\(path)`.")
} catch { } catch {
Log.err("Automatically removing the Caskroom directory at `\(path)` failed.") Log.err("Automatically removing the Caskroom directory at `\(path)` failed.")
@@ -183,7 +183,7 @@ class AppUpdater {
public static func checkIfUpdateWasPerformed() { public static func checkIfUpdateWasPerformed() {
// Cleanup the upgrade.success file // 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 Task { @MainActor in
if App.identifier.contains(".phpmon.eap") { if App.identifier.contains(".phpmon.eap") {
LocalNotification.send( LocalNotification.send(
@@ -201,13 +201,13 @@ class AppUpdater {
} }
Log.info("The `upgrade.success` file was found! An update was installed. Cleaning up...") 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 // 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...") 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")
} }
} }
} }

View File

@@ -9,7 +9,9 @@
import Foundation import Foundation
import Cocoa import Cocoa
import NVAlert import NVAlert
import ContainerMacro
@ContainerAccess
class ValetServicesManager: ServicesManager { class ValetServicesManager: ServicesManager {
override init() { override init() {
super.init() super.init()
@@ -46,7 +48,7 @@ class ValetServicesManager: ServicesManager {
.filter { $0.elevated } .filter { $0.elevated }
.map { $0.name } .map { $0.name }
let rootJson = await Shell let rootJson = await App.shared.container.shell
.pipe("sudo \(Paths.brew) services info --all --json") .pipe("sudo \(Paths.brew) services info --all --json")
.out.data(using: .utf8)! .out.data(using: .utf8)!
@@ -61,7 +63,7 @@ class ValetServicesManager: ServicesManager {
.filter { !$0.elevated } .filter { !$0.elevated }
.map { $0.name } .map { $0.name }
let normalJson = await Shell let normalJson = await App.shared.container.shell
.pipe("\(Paths.brew) services info --all --json") .pipe("\(Paths.brew) services info --all --json")
.out.data(using: .utf8)! .out.data(using: .utf8)!

View File

@@ -101,7 +101,7 @@ class Startup {
// The Homebrew binary must exist. // The Homebrew binary must exist.
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { return !FileSystem.fileExists(Paths.brew) }, command: { return !App.shared.container.filesystem.fileExists(Paths.brew) },
name: "`\(Paths.brew)` exists", name: "`\(Paths.brew)` exists",
titleText: "alert.homebrew_missing.title".localized, titleText: "alert.homebrew_missing.title".localized,
subtitleText: "alert.homebrew_missing.subtitle".localized, subtitleText: "alert.homebrew_missing.subtitle".localized,
@@ -119,7 +119,7 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { 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", name: "`ls \(Paths.optPath) | grep php` returned php result",
titleText: "startup.errors.php_opt.title".localized, titleText: "startup.errors.php_opt.title".localized,
@@ -134,7 +134,7 @@ class Startup {
// The PHP binary must exist. // The PHP binary must exist.
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { return !FileSystem.fileExists(Paths.php) }, command: { return !App.shared.container.filesystem.fileExists(Paths.php) },
name: "`\(Paths.php)` exists", name: "`\(Paths.php)` exists",
titleText: "startup.errors.php_binary.title".localized, titleText: "startup.errors.php_binary.title".localized,
subtitleText: "startup.errors.php_binary.subtitle".localized, subtitleText: "startup.errors.php_binary.subtitle".localized,
@@ -145,7 +145,7 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { 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") .contains("Library not loaded")
}, },
name: "no `dyld` issue (`Library not loaded`) detected", name: "no `dyld` issue (`Library not loaded`) detected",
@@ -160,8 +160,8 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { command: {
return !(FileSystem.fileExists(Paths.valet) return !(App.shared.container.filesystem.fileExists(Paths.valet)
|| FileSystem.fileExists("~/.composer/vendor/bin/valet")) || App.shared.container.filesystem.fileExists("~/.composer/vendor/bin/valet"))
}, },
name: "`valet` binary exists", name: "`valet` binary exists",
titleText: "startup.errors.valet_executable.title".localized, 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`. // functioning correctly. Let the user know that they need to run `valet trust`.
// ================================================================================= // =================================================================================
EnvironmentCheck( 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", name: "`/private/etc/sudoers.d/brew` contains brew",
titleText: "startup.errors.sudoers_brew.title".localized, titleText: "startup.errors.sudoers_brew.title".localized,
subtitleText: "startup.errors.sudoers_brew.subtitle".localized, subtitleText: "startup.errors.sudoers_brew.subtitle".localized,
descriptionText: "startup.errors.sudoers_brew.desc".localized descriptionText: "startup.errors.sudoers_brew.desc".localized
), ),
EnvironmentCheck( 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", name: "`/private/etc/sudoers.d/valet` contains valet",
titleText: "startup.errors.sudoers_valet.title".localized, titleText: "startup.errors.sudoers_valet.title".localized,
subtitleText: "startup.errors.sudoers_valet.subtitle".localized, subtitleText: "startup.errors.sudoers_valet.subtitle".localized,
@@ -194,7 +194,7 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { command: {
return !FileSystem.directoryExists("~/.config/valet") return !App.shared.container.filesystem.directoryExists("~/.config/valet")
}, },
name: "`.config/valet` not empty (Valet installed)", name: "`.config/valet` not empty (Valet installed)",
titleText: "startup.errors.valet_not_installed.title".localized, titleText: "startup.errors.valet_not_installed.title".localized,
@@ -223,8 +223,8 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { command: {
await BrewDiagnostics.loadInstalledTaps() await BrewDiagnostics.shared.loadInstalledTaps()
return await BrewDiagnostics.cannotLoadService("dnsmasq") return await BrewDiagnostics.shared.cannotLoadService("dnsmasq")
}, },
name: "`sudo \(Paths.brew) services info` JSON loaded", name: "`sudo \(Paths.brew) services info` JSON loaded",
titleText: "startup.errors.services_json_error.title".localized, titleText: "startup.errors.services_json_error.title".localized,
@@ -236,9 +236,9 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { 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" 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") && nodePath.contains("env: node: No such file or directory")
}, },
name: "`env: node` issue does not apply", name: "`env: node` issue does not apply",
@@ -265,7 +265,7 @@ class Startup {
// ================================================================================= // =================================================================================
EnvironmentCheck( EnvironmentCheck(
command: { 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") .contains("Composer detected issues in your platform")
}, },
name: "no global composer issues", name: "no global composer issues",

View File

@@ -8,8 +8,9 @@
import Foundation import Foundation
import NVAlert import NVAlert
import ContainerMacro
@MainActor class ComposerWindow { @MainActor @ContainerAccess class ComposerWindow {
private var shouldNotify: Bool! = nil private var shouldNotify: Bool! = nil
private var completion: ((Bool) -> Void)! = nil private var completion: ((Bool) -> Void)! = nil
private var window: TerminalProgressWindowController? private var window: TerminalProgressWindowController?
@@ -55,7 +56,7 @@ import NVAlert
self.window?.addToConsole("\(command)\n") self.window?.addToConsole("\(command)\n")
let (process, _) = try await Shell.attach( let (process, _) = try await shell.attach(
command, command,
didReceiveOutput: { [weak self] (incoming, _) in didReceiveOutput: { [weak self] (incoming, _) in
guard let window = self?.window else { return } guard let window = self?.window else { return }

View File

@@ -50,7 +50,7 @@ struct ProjectTypeDetection {
public static func detectFallbackDependency(_ basePath: String) -> String? { public static func detectFallbackDependency(_ basePath: String) -> String? {
for entry in Self.FileMapping { for entry in Self.FileMapping {
let found = entry.value 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) .contains(true)
if found { if found {

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class Brew { class Brew {
static let shared = Brew() static let shared = Brew()
@@ -19,7 +21,7 @@ class Brew {
/// Determine which version of Homebrew is installed. /// Determine which version of Homebrew is installed.
public func determineVersion() async { 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) self.version = try? VersionNumber.parse(output.out)
if let version = version { if let version = version {

View File

@@ -8,18 +8,22 @@
import Foundation import Foundation
import NVAlert import NVAlert
import ContainerMacro
@ContainerAccess
class BrewDiagnostics { class BrewDiagnostics {
public static let shared = BrewDiagnostics()
/** /**
Determines the Homebrew taps the user has installed. Determines the Homebrew taps the user has installed.
*/ */
public static var installedTaps: [String] = [] public var installedTaps: [String] = []
/** /**
Load which taps are installed. Load which taps are installed.
*/ */
public static func loadInstalledTaps() async { public func loadInstalledTaps() async {
installedTaps = await Shell installedTaps = await shell
.pipe("\(Paths.brew) tap") .pipe("\(Paths.brew) tap")
.out .out
.split(separator: "\n") .split(separator: "\n")
@@ -31,13 +35,13 @@ class BrewDiagnostics {
/** /**
Logs a bunch of useful information during startup. Logs a bunch of useful information during startup.
*/ */
public static func logBootInformation() { public func logBootInformation() {
Log.info(BrewDiagnostics.customCaskInstalled Log.info(customCaskInstalled
? "[BREW] The app has been installed via Homebrew Cask." ? "[BREW] The app has been installed via Homebrew Cask."
: "[BREW] The app has been installed directly (optimal)." : "[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-full` formula."
: "[BREW] The app will be using the `nginx` formula." : "[BREW] The app will be using the `nginx` formula."
) )
@@ -46,21 +50,20 @@ class BrewDiagnostics {
/** /**
Determines whether the PHP Monitor Cask is installed. Determines whether the PHP Monitor Cask is installed.
*/ */
public static var customCaskInstalled: Bool = { public var customCaskInstalled: Bool {
return installedTaps.contains("nicoverbruggen/cask") return installedTaps.contains("nicoverbruggen/cask")
&& FileSystem.directoryExists(Paths.caskroomPath) && filesystem.directoryExists(Paths.caskroomPath)
}() }
/** /**
Determines whether to use the regular `nginx` or `nginx-full` formula. Determines whether to use the regular `nginx` or `nginx-full` formula.
*/ */
public static var usesNginxFullFormula: Bool = { public var usesNginxFullFormula: Bool {
guard let destination = try? FileManager.default guard let destination = try? filesystem.getDestinationOfSymlink("\(Paths.binPath)/nginx") else { return false }
.destinationOfSymbolicLink(atPath: "\(Paths.binPath)/nginx") else { return false }
// Verify that the `nginx` binary is symlinked to a directory that includes `nginx-full`. // Verify that the `nginx` binary is symlinked to a directory that includes `nginx-full`.
return destination.contains("/nginx-full/") return destination.contains("/nginx-full/")
}() }
/** /**
It is possible to have outdated symlinks for PHP installations. This can mean that certain PHP installations 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. 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 // Set up a regular expression
let regex = try! NSRegularExpression(pattern: "^php@[0-9]+\\.[0-9]+$", options: .caseInsensitive) let regex = try! NSRegularExpression(pattern: "^php@[0-9]+\\.[0-9]+$", options: .caseInsensitive)
// Check for incorrect versions // Check for incorrect versions
if let contents = try? FileSystem.getShallowContentsOfDirectory("\(Paths.optPath)") if let contents = try? filesystem.getShallowContentsOfDirectory("\(Paths.optPath)")
.filter({ .filter({
let range = NSRange($0.startIndex..., in: $0) let range = NSRange($0.startIndex..., in: $0)
return regex.firstMatch(in: $0, options: [], range: range) != nil return regex.firstMatch(in: $0, options: [], range: range) != nil
@@ -81,12 +84,12 @@ class BrewDiagnostics {
for symlink in contents { for symlink in contents {
let version = symlink.replacingOccurrences(of: "php@", with: "") 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)") if !destination.contains("Cellar/php/\(version)")
&& !destination.contains("Cellar/php@\(version)") { && !destination.contains("Cellar/php@\(version)") {
Log.err("Symlink for \(symlink) is incorrect. Removing...") Log.err("Symlink for \(symlink) is incorrect. Removing...")
do { do {
try FileSystem.remove("\(Paths.optPath)/\(symlink)") try filesystem.remove("\(Paths.optPath)/\(symlink)")
Log.info("Incorrect symlink for \(symlink) has been successfully removed.") Log.info("Incorrect symlink for \(symlink) has been successfully removed.")
} catch { } catch {
Log.err("Symlink for \(symlink) was incorrect but could not be removed!") 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. 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() { if await hasAliasConflict() {
presentAlertAboutConflict() presentAlertAboutConflict()
} }
@@ -116,7 +119,7 @@ class BrewDiagnostics {
It is possible to upgrade PHP, but forget running `valet install`. It is possible to upgrade PHP, but forget running `valet install`.
This results in a scenario where a rogue www.conf file exists. 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...") Log.info("Checking for PHP-FPM issues with Valet...")
guard let install = PhpEnvironments.phpInstall else { guard let install = PhpEnvironments.phpInstall else {
@@ -138,7 +141,7 @@ class BrewDiagnostics {
} }
} }
public static func verifyThirdPartyTaps() async { public func verifyThirdPartyTaps() async {
let requiredTaps = [ let requiredTaps = [
"shivammathur/php", "shivammathur/php",
"shivammathur/extensions" "shivammathur/extensions"
@@ -157,8 +160,8 @@ class BrewDiagnostics {
/** /**
Check if the alias conflict as documented in `checkForCaskConflict` actually occurred. Check if the alias conflict as documented in `checkForCaskConflict` actually occurred.
*/ */
private static func hasAliasConflict() async -> Bool { private func hasAliasConflict() async -> Bool {
let tapAlias = await Shell.pipe("brew info shivammathur/php/php --json").out let tapAlias = await shell.pipe("brew info shivammathur/php/php --json").out
if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") || tapAlias.isEmpty { if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") || tapAlias.isEmpty {
Log.info("The user does not appear to have tapped: shivammathur/php") 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. 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 Task { @MainActor in
NVAlert() NVAlert()
.withInformation( .withInformation(
@@ -214,8 +217,8 @@ class BrewDiagnostics {
In order to see if we support the --json syntax, we'll query nginx. 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. If the JSON response cannot be parsed, Homebrew is probably out of date.
*/ */
public static func cannotLoadService(_ name: String) async -> Bool { public func cannotLoadService(_ name: String) async -> Bool {
let nginxJson = await Shell let nginxJson = await shell
.pipe("sudo \(Paths.brew) services info \(name) --json") .pipe("sudo \(Paths.brew) services info \(name) --json")
.out .out

View File

@@ -59,7 +59,7 @@ struct BrewPhpExtension: Hashable, Comparable {
} }
static func hasInstallationReceipt(for formulaName: String) -> Bool { 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 { static func < (lhs: BrewPhpExtension, rhs: BrewPhpExtension) -> Bool {
@@ -74,7 +74,7 @@ struct BrewPhpExtension: Hashable, Comparable {
let regexPattern = #"depends_on "(.*)""# let regexPattern = #"depends_on "(.*)""#
var dependencies: [String] = [] var dependencies: [String] = []
guard let content = try? FileSystem.getStringFromFile(path) else { guard let content = try? App.shared.container.filesystem.getStringFromFile(path) else {
return [] return []
} }

View File

@@ -104,7 +104,7 @@ struct BrewPhpFormula: Equatable {
return false return false
} }
return FileSystem.fileExists( return App.shared.container.filesystem.fileExists(
"\(Paths.tapPath)/shivammathur/homebrew-php/Formula/php@\(version).rb" "\(Paths.tapPath)/shivammathur/homebrew-php/Formula/php@\(version).rb"
.replacingOccurrences(of: "php@" + PhpEnvironments.brewPhpAlias, with: "php") .replacingOccurrences(of: "php@" + PhpEnvironments.brewPhpAlias, with: "php")
) )

View File

@@ -7,6 +7,7 @@
// //
import Foundation import Foundation
import ContainerMacro
protocol HandlesBrewPhpFormulae { protocol HandlesBrewPhpFormulae {
func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula]
@@ -23,6 +24,7 @@ extension HandlesBrewPhpFormulae {
} }
} }
@ContainerAccess
class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae { class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
public func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] { public func loadPhpVersions(loadOutdated: Bool) async -> [BrewPhpFormula] {
var outdated: [OutdatedFormula]? var outdated: [OutdatedFormula]?
@@ -33,7 +35,7 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
\(Paths.brew) outdated --json --formulae \(Paths.brew) outdated --json --formulae
""" """
let rawJsonText = await Shell.pipe(command).out let rawJsonText = await shell.pipe(command).out
.data(using: .utf8)! .data(using: .utf8)!
outdated = try? JSONDecoder().decode( outdated = try? JSONDecoder().decode(
OutdatedFormulae.self, OutdatedFormulae.self,

View File

@@ -12,7 +12,7 @@ class BrewTapFormulae {
public static func from(tap: String) -> [String: [BrewPhpExtension]] { public static func from(tap: String) -> [String: [BrewPhpExtension]] {
let directory = "\(Paths.tapPath)/\(tap)/Formula" 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]]() var availableExtensions = [String: [BrewPhpExtension]]()

View File

@@ -26,9 +26,9 @@ struct CaskFile {
private static func loadFromApi(_ url: URL) async -> String { private static func loadFromApi(_ url: URL) async -> String {
if App.hasLoadedTestableConfiguration || url.absoluteString.contains("https://raw.githubusercontent.com") { 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 { } else {
return await Shell.pipe(""" return await App.shared.container.shell.pipe("""
curl -s --max-time 10 \ curl -s --max-time 10 \
-H "User-Agent: phpmon-curl/1.0" \ -H "User-Agent: phpmon-curl/1.0" \
-H "X-phpmon-version: \(App.shortVersion) (\(App.bundleVersion))" \ -H "X-phpmon-version: \(App.shortVersion) (\(App.bundleVersion))" \

View File

@@ -9,7 +9,7 @@
import Foundation import Foundation
protocol BrewCommand { protocol BrewCommand {
func execute(onProgress: @escaping (BrewCommandProgress) -> Void) async throws func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws
func getCommandTitle() -> String func getCommandTitle() -> String
} }
@@ -78,10 +78,14 @@ extension BrewCommand {
return nil 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] = [] var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach( let (process, _) = try! await shell.attach(
command, command,
didReceiveOutput: { text, _ in didReceiveOutput: { text, _ in
if !text.isEmpty { if !text.isEmpty {
@@ -104,15 +108,18 @@ extension BrewCommand {
} }
} }
internal func checkPhpTap(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { internal func checkPhpTap(
if !BrewDiagnostics.installedTaps.contains("shivammathur/php") { shell: ShellProtocol,
_ onProgress: @escaping (BrewCommandProgress) -> Void
) async throws {
if !BrewDiagnostics.shared.installedTaps.contains("shivammathur/php") {
let command = "brew tap 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" let command = "brew tap shivammathur/extensions"
try await run(command, onProgress) try await run(shell: shell, command, onProgress)
} }
} }
} }

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class InstallPhpExtensionCommand: BrewCommand { class InstallPhpExtensionCommand: BrewCommand {
let installing: [BrewPhpExtension] let installing: [BrewPhpExtension]
@@ -23,7 +25,7 @@ class InstallPhpExtensionCommand: BrewCommand {
self.installing = extensions 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 let progressTitle = "phpman.steps.wait".localized
onProgress(.create( onProgress(.create(
@@ -33,16 +35,16 @@ class InstallPhpExtensionCommand: BrewCommand {
)) ))
// Make sure the tap is installed // 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 // Make sure that the extension(s) are installed
try await self.installPackages(onProgress) try await self.installPackages(shell, onProgress)
// Finally, complete all operations // Finally, complete all operations
await self.completedOperations(onProgress) 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 no installations are needed, early exit
if self.installing.isEmpty { if self.installing.isEmpty {
return return
@@ -55,7 +57,7 @@ class InstallPhpExtensionCommand: BrewCommand {
\(Paths.brew) install \(self.installing.map { $0.formulaName }.joined(separator: " ")) --force \(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 { private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async {

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class RemovePhpExtensionCommand: BrewCommand { class RemovePhpExtensionCommand: BrewCommand {
public let phpExtension: BrewPhpExtension public let phpExtension: BrewPhpExtension
@@ -19,7 +21,7 @@ class RemovePhpExtensionCommand: BrewCommand {
return "phpman.steps.removing".localized(phpExtension.name) 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( onProgress(.create(
value: 0.2, value: 0.2,
title: getCommandTitle(), title: getCommandTitle(),
@@ -42,7 +44,7 @@ class RemovePhpExtensionCommand: BrewCommand {
var loggedMessages: [String] = [] var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach( let (process, _) = try! await shell.attach(
command, command,
didReceiveOutput: { text, _ in didReceiveOutput: { text, _ in
if !text.isEmpty { if !text.isEmpty {
@@ -77,7 +79,7 @@ class RemovePhpExtensionCommand: BrewCommand {
// The extension's default configuration file can be removed // 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.") Log.info("The extension was found in a default extension .ini location. Purging that .ini file.")
do { do {
try FileSystem.remove(ext.file) try filesystem.remove(ext.file)
} catch { } catch {
Log.err("The file `\(ext.file)` could not be removed.") Log.err("The file `\(ext.file)` could not be removed.")
} }

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class ModifyPhpVersionCommand: BrewCommand { class ModifyPhpVersionCommand: BrewCommand {
let title: String let title: String
let installing: [BrewPhpFormula] let installing: [BrewPhpFormula]
@@ -32,17 +34,19 @@ class ModifyPhpVersionCommand: BrewCommand {
re-installed and linked again. re-installed and linked again.
*/ */
public init( public init(
container: Container = App.shared.container,
title: String, title: String,
upgrading: [BrewPhpFormula], upgrading: [BrewPhpFormula],
installing: [BrewPhpFormula] installing: [BrewPhpFormula]
) { ) {
self.container = container
self.title = title self.title = title
self.installing = installing self.installing = installing
self.upgrading = upgrading self.upgrading = upgrading
self.phpGuard = PhpGuard() 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 let progressTitle = "phpman.steps.wait".localized
onProgress(.create( onProgress(.create(
@@ -58,7 +62,7 @@ class ModifyPhpVersionCommand: BrewCommand {
}) })
// Make sure the tap is installed // Make sure the tap is installed
try await self.checkPhpTap(onProgress) try await self.checkPhpTap(shell: shell, onProgress)
if unavailable == nil { if unavailable == nil {
// Try to run all upgrade and installation operations // Try to run all upgrade and installation operations
@@ -99,7 +103,7 @@ class ModifyPhpVersionCommand: BrewCommand {
""" """
// Run the upgrade command // 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 { 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: " ")) \(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 { 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 \(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 { private func repairBrokenPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
@@ -162,7 +166,7 @@ class ModifyPhpVersionCommand: BrewCommand {
\(Paths.brew) reinstall \(requiringRepair.joined(separator: " ")) --force \(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 { 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)) onProgress(.create(value: 0.95, title: self.title, description: "phpman.steps.reloading".localized))
// Ensure all symlinks are correctly linked // Ensure all symlinks are correctly linked
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks() await BrewDiagnostics.shared.checkForOutdatedPhpInstallationSymlinks()
// Check which version of PHP are now installed // Check which version of PHP are now installed
await PhpEnvironments.detectPhpVersions() await PhpEnvironments.detectPhpVersions()

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class RemovePhpVersionCommand: BrewCommand { class RemovePhpVersionCommand: BrewCommand {
let formula: String let formula: String
let version: String let version: String
@@ -25,7 +27,7 @@ class RemovePhpVersionCommand: BrewCommand {
return "phpman.steps.removing".localized("PHP \(version)...") 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( onProgress(.create(
value: 0.2, value: 0.2,
title: getCommandTitle(), title: getCommandTitle(),
@@ -47,7 +49,7 @@ class RemovePhpVersionCommand: BrewCommand {
var loggedMessages: [String] = [] var loggedMessages: [String] = []
let (process, _) = try! await Shell.attach( let (process, _) = try! await shell.attach(
command, command,
didReceiveOutput: { text, _ in didReceiveOutput: { text, _ in
if !text.isEmpty { if !text.isEmpty {

View File

@@ -19,7 +19,7 @@ class FakeCommand: BrewCommand {
self.version = version 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")) onProgress(.create(value: 0.2, title: "Hello", description: "Doing the work"))
await delay(seconds: 2) await delay(seconds: 2)
onProgress(.create(value: 0.5, title: "Hello", description: "Doing some more work")) onProgress(.create(value: 0.5, title: "Hello", description: "Doing some more work"))

View File

@@ -14,7 +14,7 @@ class ValetUpgrader {
let path = "~/.composer/composer.json".replacingTildeWithHomeDirectory let path = "~/.composer/composer.json".replacingTildeWithHomeDirectory
do { do {
if FileSystem.fileExists(path) { if App.shared.container.filesystem.fileExists(path) {
return try JSONDecoder().decode( return try JSONDecoder().decode(
ComposerJson.self, ComposerJson.self,
from: String( from: String(

View File

@@ -7,12 +7,14 @@
// //
import Foundation import Foundation
import ContainerMacro
struct ValetInteractionError: Error { struct ValetInteractionError: Error {
/// The command the user should try (and failed). /// The command the user should try (and failed).
var command: String var command: String
} }
@ContainerAccess
class ValetInteractor { class ValetInteractor {
static var shared = ValetInteractor() static var shared = ValetInteractor()
@@ -23,11 +25,11 @@ class ValetInteractor {
// MARK: - Managing Domains // MARK: - Managing Domains
public func link(path: String, domain: String) async throws { 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 { 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 { 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) --secure"
: "\(Paths.valet) proxy \(domain) \(proxy)" : "\(Paths.valet) proxy \(domain) \(proxy)"
await Shell.quiet(command) await shell.quiet(command)
await Actions.restartNginx() await Actions.restartNginx()
} }
public func remove(proxy: ValetProxy) async throws { public func remove(proxy: ValetProxy) async throws {
await Shell.quiet("valet unproxy '\(proxy.domain)'") await shell.quiet("valet unproxy '\(proxy.domain)'")
} }
// MARK: - Modifying Domains // MARK: - Modifying Domains
@@ -62,7 +64,7 @@ class ValetInteractor {
} }
// Run the command // Run the command
await Shell.quiet(command) await shell.quiet(command)
// Check if the secured status has actually changed // Check if the secured status has actually changed
site.determineSecured() site.determineSecured()
@@ -87,7 +89,7 @@ class ValetInteractor {
// Run the commands // Run the commands
for command in commands { for command in commands {
await Shell.quiet(command) await shell.quiet(command)
} }
// Check if the secured status has actually changed // 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)'" let command = "sudo \(Paths.valet) isolate php@\(version) --site '\(site.name)'"
// Run the command // Run the command
await Shell.quiet(command) await shell.quiet(command)
// Check if the secured status has actually changed // Check if the secured status has actually changed
site.determineIsolated() site.determineIsolated()
@@ -122,7 +124,7 @@ class ValetInteractor {
let command = "sudo \(Paths.valet) unisolate --site '\(site.name)'" let command = "sudo \(Paths.valet) unisolate --site '\(site.name)'"
// Run the command // Run the command
await Shell.quiet(command) await shell.quiet(command)
// Check if the secured status has actually changed // Check if the secured status has actually changed
site.determineIsolated() site.determineIsolated()

View File

@@ -78,7 +78,7 @@ class ValetProxy: ValetListable {
// MARK: - Interactions // MARK: - Interactions
func determineSecured() { 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() { func toggleFavorite() {

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class ValetDomainScanner: DomainScanner { class ValetDomainScanner: DomainScanner {
// MARK: - Sites // MARK: - Sites
@@ -15,7 +17,7 @@ class ValetDomainScanner: DomainScanner {
func resolveSiteCount(paths: [String]) -> Int { func resolveSiteCount(paths: [String]) -> Int {
return paths.map { path in return paths.map { path in
do { do {
let entries = try FileSystem let entries = try filesystem
.getShallowContentsOfDirectory(path) .getShallowContentsOfDirectory(path)
return entries return entries
@@ -35,7 +37,7 @@ class ValetDomainScanner: DomainScanner {
paths.forEach { path in paths.forEach { path in
do { do {
let entries = try FileSystem let entries = try filesystem
.getShallowContentsOfDirectory(path) .getShallowContentsOfDirectory(path)
return entries.forEach { return entries.forEach {
@@ -59,7 +61,7 @@ class ValetDomainScanner: DomainScanner {
// Get the TLD from the global Valet object // Get the TLD from the global Valet object
let tld = Valet.shared.config.tld let tld = Valet.shared.config.tld
if !FileSystem.anyExists(path) { if !filesystem.anyExists(path) {
Log.warn("Could not parse the site: \(path), skipping!") Log.warn("Could not parse the site: \(path), skipping!")
} }
@@ -69,9 +71,9 @@ class ValetDomainScanner: DomainScanner {
return nil return nil
} }
if FileSystem.isSymlink(path) { if filesystem.isSymlink(path) {
return ValetSite(aliasPath: path, tld: tld) return ValetSite(filesystem: filesystem, aliasPath: path, tld: tld)
} else if FileSystem.isDirectory(path) { } else if filesystem.isDirectory(path) {
return ValetSite(absolutePath: path, tld: tld) return ValetSite(absolutePath: path, tld: tld)
} }
@@ -85,7 +87,7 @@ class ValetDomainScanner: DomainScanner {
private func isSite(_ entry: String, forPath path: String) -> Bool { private func isSite(_ entry: String, forPath path: String) -> Bool {
let siteDir = path + "/" + entry 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 // MARK: - Proxies

View File

@@ -96,9 +96,9 @@ class ValetSite: ValetListable {
self.init(name: name, tld: tld, absolutePath: absolutePath) 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 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) self.init(name: name, tld: tld, absolutePath: absolutePath, aliasPath: aliasPath)
} }
@@ -106,7 +106,7 @@ class ValetSite: ValetListable {
Determine whether a site is isolated. Determine whether a site is isolated.
*/ */
public func determineIsolated() { 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) { if !PhpEnvironments.shared.cachedPhpInstallations.keys.contains(version) {
Log.err("The PHP version \(version) is isolated for the site \(self.name) " Log.err("The PHP version \(version) is isolated for the site \(self.name) "
+ "but that PHP version is unavailable.") + "but that PHP version is unavailable.")
@@ -123,7 +123,7 @@ class ValetSite: ValetListable {
- Note: The file is not validated, only its presence is checked. - Note: The file is not validated, only its presence is checked.
*/ */
public func determineSecured() { 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" let path = "\(absolutePath)/composer.json"
do { do {
if FileSystem.fileExists(path) { if filesystem.fileExists(path) {
let decoded = try JSONDecoder().decode( let decoded = try JSONDecoder().decode(
ComposerJson.self, ComposerJson.self,
from: String( from: String(
@@ -216,7 +216,7 @@ class ValetSite: ValetListable {
for (suffix, source) in files { for (suffix, source) in files {
do { do {
let path = "\(absolutePath)/\(suffix)" let path = "\(absolutePath)/\(suffix)"
if FileSystem.fileExists(path) { if filesystem.fileExists(path) {
return try self.handleValetFile(path, source) return try self.handleValetFile(path, source)
} }
} catch { } catch {
@@ -274,8 +274,11 @@ class ValetSite: ValetListable {
// MARK: - File Parsing // MARK: - File Parsing
public static func isolatedVersion(_ filePath: String) -> String? { public static func isolatedVersion(
if FileSystem.fileExists(filePath) { _ container: Container = App.shared.container,
_ filePath: String
) -> String? {
if container.filesystem.fileExists(filePath) {
return NginxConfigurationFile return NginxConfigurationFile
.from(filePath: filePath)? .from(filePath: filePath)?
.isolatedVersion ?? nil .isolatedVersion ?? nil

View File

@@ -7,12 +7,14 @@
// //
import Foundation import Foundation
import ContainerMacro
/** /**
This class is responsible for handling the state of Valet throughout PHP Monitor. A singleton instance is created 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, 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. only a restricted subset of functionality is available in the app.
*/ */
@ContainerAccess
class Valet { class Valet {
enum FeatureFlag { enum FeatureFlag {
@@ -65,8 +67,8 @@ class Valet {
} }
lazy var installed: Bool = { lazy var installed: Bool = {
return FileSystem.fileExists(Paths.binPath.appending("/valet")) return filesystem.fileExists(Paths.binPath.appending("/valet"))
&& FileSystem.anyExists("~/.config/valet") && filesystem.anyExists("~/.config/valet")
}() }()
/** /**
@@ -89,7 +91,7 @@ class Valet {
and the app cannot start. and the app cannot start.
*/ */
public func updateVersionNumber() async { 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 // Failure condition #1: does not contain Laravel Valet
if !output.contains("Laravel Valet") { if !output.contains("Laravel Valet") {
@@ -119,7 +121,7 @@ class Valet {
do { do {
config = try JSONDecoder().decode( config = try JSONDecoder().decode(
Valet.Configuration.self, Valet.Configuration.self,
from: FileSystem.getStringFromFile("~/.config/valet/config.json").data(using: .utf8)! from: filesystem.getStringFromFile("~/.config/valet/config.json").data(using: .utf8)!
) )
} catch { } catch {
Log.err(error) Log.err(error)
@@ -206,7 +208,7 @@ class Valet {
Determine if any platform issues are detected when running `valet --version`. Determine if any platform issues are detected when running `valet --version`.
*/ */
public func hasPlatformIssues() async -> Bool { 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") .out.contains("Composer detected issues in your platform")
} }
@@ -248,12 +250,12 @@ class Valet {
if version.short == "5.6" { if version.short == "5.6" {
// The main PHP config file should contain `valet.sock` and then we're probably fine? // 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" 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") .contains("valet.sock")
} }
// Make sure to check if valet-fpm.conf exists. If it does, we should be fine :) // 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")
} }
/** /**

View File

@@ -34,13 +34,13 @@ extension MainMenu {
await PhpEnvironments.shared.determinePhpAlias() await PhpEnvironments.shared.determinePhpAlias()
// Make sure that broken symlinks are removed ASAP // Make sure that broken symlinks are removed ASAP
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks() await BrewDiagnostics.shared.checkForOutdatedPhpInstallationSymlinks()
// Initialize preferences // Initialize preferences
_ = Preferences.shared _ = Preferences.shared
// Put some useful diagnostics information in log // Put some useful diagnostics information in log
BrewDiagnostics.logBootInformation() BrewDiagnostics.shared.logBootInformation()
// Attempt to find out more info about Valet // Attempt to find out more info about Valet
if Valet.shared.version != nil { if Valet.shared.version != nil {
@@ -58,10 +58,10 @@ extension MainMenu {
// Verify third party taps // Verify third party taps
// The missing tap(s) will be actionable later // The missing tap(s) will be actionable later
await BrewDiagnostics.verifyThirdPartyTaps() await BrewDiagnostics.shared.verifyThirdPartyTaps()
// Check for an alias conflict // Check for an alias conflict
await BrewDiagnostics.checkForCaskConflict() await BrewDiagnostics.shared.checkForCaskConflict()
// Attempt to find out if PHP-FPM is broken // Attempt to find out if PHP-FPM is broken
PhpEnvironments.prepare() PhpEnvironments.prepare()
@@ -92,7 +92,7 @@ extension MainMenu {
await Valet.shared.startPreloadingSites() await Valet.shared.startPreloadingSites()
// After preloading sites, check for PHP-FPM pool conflicts // 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) // Check if PHP-FPM is broken (should be fixed automatically if phpmon >= 6.0)
await Valet.shared.notifyAboutBrokenPhpFpm() await Valet.shared.notifyAboutBrokenPhpFpm()

View File

@@ -45,7 +45,7 @@ struct CustomPrefs: Decodable {
extension Preferences { extension Preferences {
func loadCustomPreferences() async { func loadCustomPreferences() async {
// Ensure the configuration directory is created if missing // 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 // Move the legacy file
await moveOutdatedConfigurationFile() await moveOutdatedConfigurationFile()
@@ -64,7 +64,7 @@ extension Preferences {
func moveOutdatedConfigurationFile() async { func moveOutdatedConfigurationFile() async {
if FileSystem.fileExists("~/.phpmon.conf.json") && !FileSystem.fileExists("~/.config/phpmon/config.json") { if FileSystem.fileExists("~/.phpmon.conf.json") && !FileSystem.fileExists("~/.config/phpmon/config.json") {
Log.info("An outdated configuration file was found. Moving it...") 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!") Log.info("The configuration file was copied successfully!")
} }
} }
@@ -88,7 +88,7 @@ extension Preferences {
if customPreferences.hasEnvironmentVariables() { if customPreferences.hasEnvironmentVariables() {
Log.info("Configuring the additional exports...") 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 shell.exports = customPreferences.exportAsString
} }
} }

View File

@@ -106,7 +106,7 @@ class Stats {
*/ */
public static func evaluateSponsorMessageShouldBeDisplayed() { 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.") return Log.info("A fake shell is in use, skipping sponsor alert.")
} }

View File

@@ -272,7 +272,7 @@ struct Preset: Codable, Equatable {
private func persistRevert() async { private func persistRevert() async {
let data = try! JSONEncoder().encode(self.revertSnapshot) 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)! try! String(data: data, encoding: .utf8)!
.write( .write(

View File

@@ -6,6 +6,9 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import ContainerMacro
@ContainerAccess
class InstallHomebrew { class InstallHomebrew {
public func run() async throws { public func run() async throws {
let script = """ let script = """
@@ -13,7 +16,7 @@ class InstallHomebrew {
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" "$(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) print(string)
}, withTimeout: 60 * 10) }, withTimeout: 60 * 10)
} }

View File

@@ -6,12 +6,15 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import ContainerMacro
@ContainerAccess
class ZshRunCommand { class ZshRunCommand {
/** /**
Adds a given line to .zshrc, which may be needed to adjust the PATH. Adds a given line to .zshrc, which may be needed to adjust the PATH.
*/ */
private func add(_ text: String) async -> Bool { private func add(_ text: String) async -> Bool {
let outcome = await Shell.pipe(""" let outcome = await shell.pipe("""
touch ~/.zshrc && \ touch ~/.zshrc && \
grep -qxF '\(text)' ~/.zshrc \ grep -qxF '\(text)' ~/.zshrc \
|| echo '\n\n\(text)\n' >> ~/.zshrc || echo '\n\n\(text)\n' >> ~/.zshrc

View File

@@ -16,11 +16,14 @@ class Paths {
static let shared = Paths() static let shared = Paths()
var baseDir: HomebrewDir var baseDir: HomebrewDir
var userName = String(Shell.pipe("whoami").split(separator: "\n")[0]) var userName: String
init() { init() {
let optBrewFound = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew") let shell = App.shared.container.shell
let usrBrewFound = Shell.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew") 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 { if optBrewFound {
// This is usually the case with Homebrew installed on Apple Silicon // This is usually the case with Homebrew installed on Apple Silicon

View File

@@ -59,7 +59,7 @@ class ConfigWatchManager {
eventMask: DispatchSource.FileSystemEvent, eventMask: DispatchSource.FileSystemEvent,
behaviour: ConfigFSNotifier.Behaviour = .reloadsMenu 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.") Log.warn("No watcher was created for \(url.path) because the requested file does not exist.")
return return
} }

View File

@@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
let path = pathControl.url!.path let path = pathControl.url!.path
let name = inputDomainName.stringValue let name = inputDomainName.stringValue
if !FileSystem.anyExists(path) { if !App.shared.container.filesystem.anyExists(path) {
Alert.confirm( Alert.confirm(
onWindow: view.window!, onWindow: view.window!,
messageText: "domain_list.alert.folder_missing.title".localized, messageText: "domain_list.alert.folder_missing.title".localized,

View File

@@ -32,11 +32,11 @@ extension DomainListVC {
} }
@objc func openInFinder() { @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() { @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) { @objc func openWithEditor(sender: EditorMenuItem) {
@@ -50,7 +50,7 @@ extension DomainListVC {
let rowToReload = tableView.selectedRow let rowToReload = tableView.selectedRow
waitAndExecute { waitAndExecute {
await Shell.quiet(command) await App.shared.container.shell.quiet(command)
} completion: { [self] in } completion: { [self] in
beforeCellReload() beforeCellReload()
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4]) tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])

View File

@@ -7,7 +7,9 @@
// //
import Foundation import Foundation
import ContainerMacro
@ContainerAccess
class BytePhpPreference: PhpPreference { class BytePhpPreference: PhpPreference {
enum UnitOption: String, CaseIterable { enum UnitOption: String, CaseIterable {
case kilobyte = "K" case kilobyte = "K"
@@ -35,8 +37,8 @@ class BytePhpPreference: PhpPreference {
didSet { updatedFieldValue() } didSet { updatedFieldValue() }
} }
override init(key: String) { init(container: Container = App.shared.container, key: String) {
let value = Command.execute( let value = container.command.execute(
path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"], path: Paths.php, arguments: ["-r", "echo ini_get('\(key)');"],
trimNewlines: false trimNewlines: false
) )
@@ -47,6 +49,7 @@ class BytePhpPreference: PhpPreference {
self.value = value self.value = value
} }
super.init(key: key) super.init(key: key)
self.container = container
} }
// MARK: Save Value // MARK: Save Value

View File

@@ -36,7 +36,7 @@ class PhpConfigChecker {
// Do the check // Do the check
let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file.path)") let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file.path)")
if !FileSystem.fileExists(fullFilePath) { if !App.shared.container.filesystem.fileExists(fullFilePath) {
missing.append(fullFilePath) missing.append(fullFilePath)
} }
} }

View File

@@ -12,7 +12,7 @@ extension WarningManager {
return [ return [
Warning( Warning(
command: { 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" .trimmingCharacters(in: .whitespacesAndNewlines) == "1"
}, },
name: "Running PHP Monitor with Rosetta on Apple Silicon", name: "Running PHP Monitor with Rosetta on Apple Silicon",
@@ -23,7 +23,7 @@ extension WarningManager {
), ),
Warning( Warning(
command: { 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/") !FileSystem.isWriteableFile("/usr/local/bin/")
}, },
name: "Helpers cannot be symlinked and not in PATH", name: "Helpers cannot be symlinked and not in PATH",
@@ -73,7 +73,7 @@ extension WarningManager {
), ),
Warning( Warning(
command: { command: {
!BrewDiagnostics.installedTaps.contains("shivammathur/php") !BrewDiagnostics.shared.installedTaps.contains("shivammathur/php")
}, },
name: "`shivammathur/php` tap is missing", name: "`shivammathur/php` tap is missing",
title: "warnings.php_tap_missing.title", title: "warnings.php_tap_missing.title",
@@ -82,14 +82,14 @@ extension WarningManager {
] }, ] },
url: "https://github.com/shivammathur/homebrew-php", url: "https://github.com/shivammathur/homebrew-php",
fix: { fix: {
await Shell.quiet("brew tap shivammathur/php") await App.shared.container.shell.quiet("brew tap shivammathur/php")
await BrewDiagnostics.loadInstalledTaps() await BrewDiagnostics.shared.loadInstalledTaps()
await self.checkEnvironment() await self.checkEnvironment()
} }
), ),
Warning( Warning(
command: { command: {
!BrewDiagnostics.installedTaps.contains("shivammathur/extensions") !BrewDiagnostics.shared.installedTaps.contains("shivammathur/extensions")
}, },
name: "`shivammathur/extensions` tap is missing", name: "`shivammathur/extensions` tap is missing",
title: "warnings.extensions_tap_missing.title", title: "warnings.extensions_tap_missing.title",
@@ -98,8 +98,8 @@ extension WarningManager {
] }, ] },
url: "https://github.com/shivammathur/homebrew-extensions", url: "https://github.com/shivammathur/homebrew-extensions",
fix: { fix: {
await Shell.quiet("brew tap shivammathur/extensions") await App.shared.container.shell.quiet("brew tap shivammathur/extensions")
await BrewDiagnostics.loadInstalledTaps() await BrewDiagnostics.shared.loadInstalledTaps()
await self.checkEnvironment() await self.checkEnvironment()
} }
), ),

View File

@@ -61,7 +61,7 @@ class WarningManager: ObservableObject {
func checkEnvironment() async { func checkEnvironment() async {
container.shell.reload() container.shell.reload()
await BrewDiagnostics.loadInstalledTaps() await BrewDiagnostics.shared.loadInstalledTaps()
if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil { if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil {
self.temporaryWarnings = self.evaluations self.temporaryWarnings = self.evaluations

View File

@@ -66,7 +66,7 @@ extension PhpExtensionManagerView {
do { do {
self.status.busy = true self.status.busy = true
try await command.execute { progress in try await command.execute(shell: App.shared.container.shell) { progress in
Task { @MainActor in Task { @MainActor in
self.status.title = progress.title self.status.title = progress.title
self.status.description = progress.description self.status.description = progress.description

View File

@@ -22,7 +22,7 @@ extension PhpVersionManagerView {
do { do {
self.setBusyStatus(true) self.setBusyStatus(true)
try await command.execute { progress in try await command.execute(shell: App.shared.container.shell) { progress in
Task { @MainActor in Task { @MainActor in
self.status.title = progress.title self.status.title = progress.title
self.status.description = progress.description self.status.description = progress.description
@@ -107,7 +107,7 @@ extension PhpVersionManagerView {
do { do {
self.setBusyStatus(true) self.setBusyStatus(true)
try await command.execute { progress in try await command.execute(shell: App.shared.container.shell) { progress in
Task { @MainActor in Task { @MainActor in
self.status.title = progress.title self.status.title = progress.title
self.status.description = progress.description self.status.description = progress.description