1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-11-05 12:30:07 +01:00

🚀 Version 25.08

This commit is contained in:
2025-08-31 12:39:31 +02:00
64 changed files with 839 additions and 106 deletions

View File

@@ -21,6 +21,25 @@
033D45A02B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
036C39022E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
036C39032E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
036C39042E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
036C39052E5C883B008DAEDF /* Packagist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39012E5C883A008DAEDF /* Packagist.swift */; };
036C39082E5C88A7008DAEDF /* PackagistTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39072E5C88A2008DAEDF /* PackagistTest.swift */; };
036C390A2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
036C390D2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */; };
036C390F2E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
036C39102E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
036C39112E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39132E5CB820008DAEDF /* TestBundle.swift */; };
036C3A212E5CBBAA008DAEDF /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; };
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
039E1D7C2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF5282E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
@@ -29,6 +48,14 @@
03BFF52D2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03BFF52F2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03CC1FE52E3D22120050FC18 /* InstallHomebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */; };
03CC1FE62E3D22120050FC18 /* InstallHomebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */; };
03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */; };
03CC1FE82E3D22120050FC18 /* InstallHomebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */; };
03CC1FF42E3D23130050FC18 /* 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 */; };
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 */; };
5420395926135DC100FB00FA /* PreferencesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PreferencesVC.swift */; };
@@ -670,7 +697,6 @@
C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; };
C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */; };
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; };
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; };
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F792754499000D44ED0 /* Valet.swift */; };
C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F7C275454A900D44ED0 /* ValetVersionExtractorTest.swift */; };
@@ -928,8 +954,16 @@
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpExtensionManagerView+Actions.swift"; sourceTree = "<group>"; };
036C39012E5C883A008DAEDF /* Packagist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Packagist.swift; sourceTree = "<group>"; };
036C39072E5C88A2008DAEDF /* PackagistTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistTest.swift; sourceTree = "<group>"; };
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistP2Response.swift; sourceTree = "<group>"; };
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.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>"; };
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>"; };
5420395826135DC100FB00FA /* PreferencesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesVC.swift; sourceTree = "<group>"; };
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
@@ -1265,6 +1299,52 @@
path = "PHP Versions";
sourceTree = "<group>";
};
036C38FB2E5C8827008DAEDF /* Packagist */ = {
isa = PBXGroup;
children = (
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */,
036C39012E5C883A008DAEDF /* Packagist.swift */,
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */,
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */,
);
path = Packagist;
sourceTree = "<group>";
};
036C39062E5C8890008DAEDF /* Integration */ = {
isa = PBXGroup;
children = (
036C39072E5C88A2008DAEDF /* PackagistTest.swift */,
);
path = Integration;
sourceTree = "<group>";
};
036C3A222E5CBC33008DAEDF /* _ST */ = {
isa = PBXGroup;
children = (
C4C1019927C65A4D001FACC2 /* Commands */,
036C3A232E5CBC57008DAEDF /* Parsers */,
036C39062E5C8890008DAEDF /* Integration */,
);
path = _ST;
sourceTree = "<group>";
};
036C3A232E5CBC57008DAEDF /* Parsers */ = {
isa = PBXGroup;
children = (
C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */,
);
path = Parsers;
sourceTree = "<group>";
};
03BFF1D12E3CF4F2004C56A9 /* Provision */ = {
isa = PBXGroup;
children = (
03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */,
03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */,
);
path = Provision;
sourceTree = "<group>";
};
5420395726135DB800FB00FA /* Preferences */ = {
isa = PBXGroup;
children = (
@@ -1472,6 +1552,7 @@
C41E181722CB61EB0072CF09 /* Domain */ = {
isa = PBXGroup;
children = (
03BFF1D12E3CF4F2004C56A9 /* Provision */,
C4AF9F6B275445D300D44ED0 /* Integrations */,
C4B13B1D25C4915000548C3A /* App */,
C4D9ADBD27761084007277F4 /* PHP */,
@@ -1909,6 +1990,7 @@
C4AF9F6B275445D300D44ED0 /* Integrations */ = {
isa = PBXGroup;
children = (
036C38FB2E5C8827008DAEDF /* Packagist */,
C4463FD029804C13007B93D5 /* Common */,
C4C0E8DA27F887CC002D32A9 /* Nginx */,
C4D89BC42783C98800A02B68 /* Composer */,
@@ -2047,7 +2129,6 @@
isa = PBXGroup;
children = (
C456A0D02AA6175D0080144F /* Config */,
C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */,
C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */,
C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */,
C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */,
@@ -2141,6 +2222,7 @@
children = (
C4E2E86328FC2F1B003B070C /* XCPMApplication.swift */,
C40F505428ECA64E004AD45B /* TestableConfigurations.swift */,
036C39132E5CB820008DAEDF /* TestBundle.swift */,
C43A8A1925D9CD1000591B77 /* Utility.swift */,
);
path = Shared;
@@ -2198,9 +2280,9 @@
C4F7807A25D7F84B000DBC97 /* unit */ = {
isa = PBXGroup;
children = (
036C3A222E5CBC33008DAEDF /* _ST */,
C471E6D928F9AFC20021E251 /* Testables */,
C40C7F1C27720E1400DDDCDC /* Test Files */,
C4C1019927C65A4D001FACC2 /* Commands */,
C4C1019827C65A1A001FACC2 /* Versions */,
C4C1019727C65A11001FACC2 /* Parsers */,
);
@@ -2617,6 +2699,7 @@
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
C4BB39392981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */,
036C39032E5C883B008DAEDF /* Packagist.swift in Sources */,
C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */,
C4CDA893288F1A71007CE25F /* Keys.swift in Sources */,
C43931C529C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
@@ -2651,6 +2734,7 @@
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */,
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
036C39102E5C8D42008DAEDF /* PackagistError.swift in Sources */,
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */,
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */,
@@ -2682,6 +2766,7 @@
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */,
C4CE7F9629683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
03CC1FF42E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */,
033D459E2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
@@ -2698,7 +2783,9 @@
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */,
039E1D7C2E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
036C390D2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C485707028BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
@@ -2722,6 +2809,7 @@
C42106662AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79ECB29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
C40D725F2A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
03CC1FE62E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
C43BCD4429FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C4E2E84A28FC1E70003B070C /* DataExtension.swift in Sources */,
@@ -2744,15 +2832,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */,
C471E82D28F9BB650021E251 /* AlertableError.swift in Sources */,
C471E82E28F9BB650021E251 /* Errors.swift in Sources */,
C471E82F28F9BB650021E251 /* Alert.swift in Sources */,
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
C4FD87A929AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C471E83028F9BB650021E251 /* Application.swift in Sources */,
C471E83128F9BB650021E251 /* LocalNotification.swift in Sources */,
C4B79EB829CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
C471E83228F9BB650021E251 /* MenuBarImageGenerator.swift in Sources */,
C4BB393B2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
036C39052E5C883B008DAEDF /* Packagist.swift in Sources */,
C471E83328F9BB650021E251 /* PMWindowController.swift in Sources */,
C471E83428F9BB650021E251 /* VersionExtractor.swift in Sources */,
C471E83528F9BB650021E251 /* ValetProxy.swift in Sources */,
@@ -2818,6 +2909,7 @@
C471E86428F9BB650021E251 /* Warning.swift in Sources */,
C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */,
C43931C729C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
036C390A2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */,
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */,
@@ -2881,6 +2973,7 @@
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */,
C471E7E728F9BAC20021E251 /* Constants.swift in Sources */,
C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */,
03CC1FF72E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C471E7D728F9BA8F0021E251 /* TestableFileSystem.swift in Sources */,
C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */,
@@ -2924,6 +3017,7 @@
C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */,
C436B39F29F3C42500B6A64E /* PreferencesTabs.swift in Sources */,
03CC1FE82E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C471E7E328F9BAC20021E251 /* Logger.swift in Sources */,
C471E7FD28F9BACE0021E251 /* HomebrewService.swift in Sources */,
C471E7E428F9BAC20021E251 /* Helpers.swift in Sources */,
@@ -2960,6 +3054,7 @@
C471E89228F9BB8F0021E251 /* Alert.swift in Sources */,
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C471E89328F9BB8F0021E251 /* Application.swift in Sources */,
036C39022E5C883B008DAEDF /* Packagist.swift in Sources */,
C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */,
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C40934A5298EEB2C00D25014 /* CaskFile.swift in Sources */,
@@ -3004,6 +3099,7 @@
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */,
C471E8BA28F9BB8F0021E251 /* DomainListTLSCell.swift in Sources */,
C43B8FD82BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
@@ -3039,6 +3135,7 @@
C471E8CD28F9BB8F0021E251 /* PreferencesVC.swift in Sources */,
C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */,
C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */,
03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */,
C471E8D028F9BB8F0021E251 /* CustomPrefs.swift in Sources */,
@@ -3084,15 +3181,18 @@
C471E8F128F9BB8F0021E251 /* KeyCombo.swift in Sources */,
C471E8F228F9BB8F0021E251 /* ModifierFlagsExtension.swift in Sources */,
C471E7F028F9BAC30021E251 /* Paths.swift in Sources */,
03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C471E7CF28F9BA600021E251 /* ActiveShell.swift in Sources */,
C4BB393C2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
C471E7F628F9BAC80021E251 /* PhpHelper.swift in Sources */,
039E1D7B2E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
C471E7EE28F9BAC30021E251 /* Constants.swift in Sources */,
C40934A0298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4611E5A2AEAD2E20010BE24 /* ConfigManagerWindowController.swift in Sources */,
C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */,
036C390F2E5C8D42008DAEDF /* PackagistError.swift in Sources */,
C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */,
@@ -3196,6 +3296,7 @@
C485707728BF455300539B36 /* HeaderView.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */,
C4E6840A2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */,
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
@@ -3275,12 +3376,13 @@
C40C5C9D2846A40600E28255 /* Preset.swift in Sources */,
C4CE7F9729683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
036C39082E5C88A7008DAEDF /* PackagistTest.swift in Sources */,
C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */,
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C4E49DEE28F764A00026AC4E /* TestableCommand.swift in Sources */,
C4AF9F78275447F100D44ED0 /* ValetConfigurationTest.swift in Sources */,
C4611E612AEAD3110010BE24 /* ByteLimitView.swift in Sources */,
C40175B92903108900763A68 /* ValetInteractor.swift in Sources */,
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */,
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */,
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
@@ -3295,10 +3397,12 @@
C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */,
C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
C4B79ECC29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
C43B8FD62BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
C4FD87AA29AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
036C39112E5C8D42008DAEDF /* PackagistError.swift in Sources */,
C485707D28BF45A200539B36 /* WarningView.swift in Sources */,
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
@@ -3309,6 +3413,7 @@
C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */,
C45B91542956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C4611E5C2AEAD2E30010BE24 /* ConfigManagerWindowController.swift in Sources */,
03CC1FE52E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */,
C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */,
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
@@ -3320,6 +3425,7 @@
C4EA3C482BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
036C39042E5C883B008DAEDF /* Packagist.swift in Sources */,
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
C4B79EBD29CA38DB00A483EE /* BrewCommand.swift in Sources */,
C485707828BF456300539B36 /* Warning.swift in Sources */,
@@ -3343,6 +3449,7 @@
C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */,
C4463FCD29804BCB007B93D5 /* RCFile.swift in Sources */,
C47015052C46D7F10069AAE7 /* NVAlertExtension.swift in Sources */,
03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C409349E298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */,
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
@@ -3354,6 +3461,7 @@
C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */,
C46EBC4828DB9644007ACC74 /* RealShell.swift in Sources */,
C456A0CC2AA6166F0080144F /* BytePhpPreference.swift in Sources */,
036C3A212E5CBBAA008DAEDF /* ValetConfigurationTest.swift in Sources */,
C48DDD0E29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */,
C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */,
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */,
@@ -3466,7 +3574,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3500,7 +3608,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3534,7 +3642,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3568,7 +3676,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3634,7 +3742,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -3694,7 +3802,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@@ -3712,7 +3820,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -3724,7 +3832,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3742,7 +3850,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
ENABLE_HARDENED_RUNTIME = YES;
@@ -3754,7 +3862,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -3957,7 +4065,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@@ -3975,7 +4083,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
ENABLE_HARDENED_RUNTIME = YES;
@@ -3987,7 +4095,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@@ -4072,7 +4180,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -4091,7 +4199,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4103,7 +4211,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@@ -4188,7 +4296,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -4207,7 +4315,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4219,7 +4327,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@@ -4252,7 +4360,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4368,7 +4476,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@@ -4386,7 +4494,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1565;
CURRENT_PROJECT_VERSION = 1588;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4398,7 +4506,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
MARKETING_VERSION = 25.08;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@@ -4431,7 +4539,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -111,6 +111,11 @@
value = ""
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "PHPMON_MARKETING_MODE"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction

View File

@@ -5,17 +5,19 @@
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up if you want to use all of the functionality of the app</u> (consult the FAQ below with info about how to set up your environment).
<img src="./docs/screenshot.jpg" width="1280px" alt="phpmon screenshot (menu bar app)"/>
<img src="./docs/screenshot@2x.jpg" width="1280px" alt="phpmon screenshot (menu bar app)"/>
<small><i>Screenshot: Showing the key functionality of PHP Monitor.</i></small>
<small><i>Screenshot: Showing the key functionality of PHP Monitor. You can switch between PHP versions, manage PHP installations, install extensions, manage domains, and much more.</i></small>
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
It's super convenient to **globally switch** between different versions of PHP. This applies the selected version of PHP as the default on your system.
If you prefer, you can also **pick a PHP version on a per-site basis** (this uses Valet's site isolation feature) which is very helpful.
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
You can also add new domains as links, isolate sites, manage various services, and perform First Aid to fix all kinds of common PHP link issues.
You can also add new domains as links, isolate sites, manage various services, and perform First Aid to fix all kinds of common PHP and Homebrew issues.
## 🖥 System requirements

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

BIN
docs/screenshot@2x.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

BIN
docs/screenshot_og@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 KiB

After

Width:  |  Height:  |  Size: 633 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 KiB

After

Width:  |  Height:  |  Size: 485 KiB

View File

@@ -92,7 +92,7 @@ public struct PhpVersionNumberCollection: Equatable {
// If a patch is provided then the minor version cannot be bumped.
? $0.hasSameMajorAndMinorButNewerOrSamePatch(version, strict)
// If a patch is not provided then the major version cannot be bumped.
: $0.hasSameMajorButNewerOrSameMinor(version, strict)
: $0.hasSameMajorButNewerOrSameMinor(version)
}
}

View File

@@ -93,7 +93,7 @@ public struct VersionNumber: Equatable, Hashable {
return self.major == version.major
}
internal func isSameAs(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func isSameAs(_ version: VersionNumber, _ strict: Bool = true) -> Bool {
return self.major == version.major
&& self.minor == version.minor
&& (strict ? self.patch(strict, version) == version.patch(strict) : true)
@@ -103,7 +103,7 @@ public struct VersionNumber: Equatable, Hashable {
return self.major == version.major && self.minor == version.minor
}
internal func isNewerThan(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func isNewerThan(_ version: VersionNumber, _ strict: Bool = true) -> Bool {
return (
self.major > version.major ||
self.major == version.major && self.minor > version.minor ||
@@ -112,7 +112,7 @@ public struct VersionNumber: Equatable, Hashable {
)
}
internal func isOlderThan(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func isOlderThan(_ version: VersionNumber, _ strict: Bool = true) -> Bool {
return (
self.major < version.major ||
self.major == version.major && self.minor < version.minor ||
@@ -121,7 +121,7 @@ public struct VersionNumber: Equatable, Hashable {
)
}
internal func hasNewerMinorVersionOrPatch(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func hasNewerMinorVersionOrPatch(_ version: VersionNumber, _ strict: Bool = true) -> Bool {
return self.major == version.major &&
(
(self.minor == version.minor && self.patch(strict) >= version.patch(strict, self))
@@ -129,12 +129,12 @@ public struct VersionNumber: Equatable, Hashable {
)
}
internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func hasSameMajorAndMinorButNewerOrSamePatch(_ version: VersionNumber, _ strict: Bool = true) -> Bool {
return self.major == version.major && self.minor == version.minor
&& self.patch(strict, version) >= version.patch(strict)
}
internal func hasSameMajorButNewerOrSameMinor(_ version: VersionNumber, _ strict: Bool) -> Bool {
internal func hasSameMajorButNewerOrSameMinor(_ version: VersionNumber) -> Bool {
return self.major == version.major
&& self.minor >= version.minor
}

View File

@@ -15,6 +15,13 @@ var Shell: ShellProtocol {
class ActiveShell {
static var shared: ShellProtocol = RealShell()
public static func reload() {
if shared is RealShell {
// Start a new shell, this will re-populate the PATH
shared = RealShell()
}
}
public static func useTestable(_ expectations: [String: BatchFakeShellOutput]) {
Self.shared = TestableShell(expectations: expectations)
}

View File

@@ -278,19 +278,7 @@ class Startup {
// =================================================================================
EnvironmentCheck(
command: {
let output = await Shell.pipe("valet --version").out
// Failure condition #1: does not contain Laravel Valet
if !output.contains("Laravel Valet") {
return true
}
// Failure condition #2: version cannot be parsed
let versionString = output
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "Laravel Valet")[1]
.trimmingCharacters(in: .whitespaces)
// Extract the version number
Valet.shared.version = try! VersionNumber.parse(VersionExtractor.from(versionString)!)
// Get the actual version
await Valet.shared.updateVersionNumber()
return Valet.shared.version == nil
},
name: "`valet --version` was loaded",

View File

@@ -84,7 +84,14 @@ import NVAlert
)
}
window = nil
// Update the internal Valet number because it may have updated
await Valet.shared.updateVersionNumber()
// Update the UI
removeBusyStatus()
// Fire completion callback
completion(true)
}
}

View File

@@ -138,6 +138,22 @@ class BrewDiagnostics {
}
}
public static func verifyThirdPartyTaps() async {
let requiredTaps = [
"shivammathur/php",
"shivammathur/extensions"
]
// Check the status of the installed taps
for tap in requiredTaps {
if installedTaps.contains(tap) {
Log.info("As expected, `\(tap)` is installed!")
} else {
Log.warn("`\(tap)` does not appear to be installed, will be noted in warnings.")
}
}
}
/**
Check if the alias conflict as documented in `checkForCaskConflict` actually occurred.
*/

View File

@@ -51,6 +51,7 @@ class InstallPhpExtensionCommand: BrewCommand {
let command = """
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
\(Paths.brew) install \(self.installing.map { $0.formulaName }.joined(separator: " ")) --force
"""

View File

@@ -36,6 +36,7 @@ class RemovePhpExtensionCommand: BrewCommand {
let command = """
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
\(Paths.brew) remove \(phpExtension.formulaName) --force --ignore-dependencies
"""

View File

@@ -92,6 +92,7 @@ class ModifyPhpVersionCommand: BrewCommand {
// Upgrade the main formula
let command = """
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) upgrade php;
\(Paths.brew) install php@\(short);
@@ -108,6 +109,7 @@ class ModifyPhpVersionCommand: BrewCommand {
}
let command = """
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) upgrade \(self.upgrading.map { $0.name }.joined(separator: " "))

View File

@@ -33,6 +33,7 @@ class RemovePhpVersionCommand: BrewCommand {
))
let command = """
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
\(Paths.brew) remove \(formula) --force --ignore-dependencies

View File

@@ -0,0 +1,74 @@
//
// Untitled.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
class Packagist {
static func getLatestStableVersion(packageName: String) async throws -> VersionNumber {
guard let url = URL(string: "https://repo.packagist.org/p2/\(packageName).json") else {
throw PackagistError.invalidURL
}
let agent = "phpmon/\(App.shortVersion)"
var request = URLRequest(url: url)
request.setValue(agent, forHTTPHeaderField: "User-Agent")
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
let code = (response as? HTTPURLResponse)?.statusCode ?? 0
throw PackagistError.networkError(NSError(domain: "", code: code, userInfo: nil))
}
let decodedResponse = try JSONDecoder()
.decode(PackagistP2Response.self, from: data)
guard let versionsArray = decodedResponse.packages[packageName] else {
throw PackagistError.unexpectedResponseStructure
}
// Filter for stable versions using the version_normalized string.
// A stable version typically does not have a hyphen (-) indicating a pre-release.
let stableVersions = versionsArray.filter { version in
guard let versionNormalized = version.version_normalized else {
return false
}
// Filter out versions with a hyphen, which are usually unstable.
return !versionNormalized.contains("-")
}
// Sort the filtered versions using version_normalized, which is designed for lexicographical sorting.
let sortedVersions = stableVersions.sorted { (version1, version2) -> Bool in
guard let v1 = version1.version_normalized, let v2 = version2.version_normalized else {
return false
}
return v1.lexicographicallyPrecedes(v2)
}
// The last element of the sorted array is the latest version
guard let latestVersionInfo = sortedVersions.last,
let latestVersion = latestVersionInfo.version else {
throw PackagistError.noStableVersions
}
return try! VersionNumber.parse(latestVersion)
} catch {
// Catch any errors that occurred and re-throw them as our custom error type for better diagnostics.
if let decodingError = error as? DecodingError {
throw PackagistError.jsonDecodingError(decodingError)
} else if let urlError = error as? URLError {
throw PackagistError.networkError(urlError)
} else {
throw error
}
}
}
}

View File

@@ -0,0 +1,32 @@
//
// PackagistError.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
enum PackagistError: Error, LocalizedError {
case invalidURL
case networkError(Error)
case jsonDecodingError(Error)
case noStableVersions
case unexpectedResponseStructure
var errorDescription: String? {
switch self {
case .invalidURL:
return "The provided URL is invalid."
case .networkError(let error):
return "A network error occurred: \(error.localizedDescription)"
case .jsonDecodingError(let error):
return "Failed to decode JSON: \(error.localizedDescription)"
case .noStableVersions:
return "No stable versions were found for the package."
case .unexpectedResponseStructure:
return "The API response structure was not as expected."
}
}
}

View File

@@ -0,0 +1,16 @@
//
// PackagistP2Response.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
struct PackagistP2Response: Codable {
let packages: [String: [PackageInfo]]
}
struct PackageInfo: Codable {
let version: String?
let version_normalized: String?
}

View File

@@ -0,0 +1,109 @@
//
// ValetUpgrader.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 27/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
import NVAlert
class ValetUpgrader {
private static func getGlobalComposerJson() -> ComposerJson? {
let path = "~/.composer/composer.json".replacingTildeWithHomeDirectory
do {
if FileSystem.fileExists(path) {
return try JSONDecoder().decode(
ComposerJson.self,
from: String(
contentsOf: URL(fileURLWithPath: path),
encoding: .utf8
).data(using: .utf8)!
)
} else {
Log.err("The global Composer file is missing. This should, uh, not happen!")
return nil
}
} catch {
Log.err("Something went wrong reading the Composer JSON file.")
return nil
}
}
public static func showUpgradeAlert() {
var valetConstraint: String = "unknown"
var constraintCheckPassed: Bool?
if let json = getGlobalComposerJson(), let dependencies = json.dependencies {
if dependencies.keys.contains("laravel/valet") {
valetConstraint = dependencies["laravel/valet"]!
}
}
guard let latest = Valet.shared.latestVersion else {
return Log.err("The latest version is unknown. This should, uh, not happen!")
}
if valetConstraint != "unknown" {
// Do a constraint check
constraintCheckPassed = !PhpVersionNumberCollection(versions: [latest])
.matching(constraint: valetConstraint).isEmpty
}
Task { @MainActor in
notifyAboutUpgrade(
latest: latest.text,
constraint: valetConstraint,
passing: constraintCheckPassed ?? false
)
}
}
@MainActor private static func upgradeValet() {
ComposerWindow().updateGlobalDependencies(
notify: true,
completion: { success in
if success {
notifyAboutCompletion()
}
}
)
}
@MainActor private static func notifyAboutCompletion() {
return NVAlert().withInformation(
title: "valet_upgraded.title".localized,
subtitle: "valet_upgraded.subtitle".localized,
description: "valet_upgraded.description".localized,
)
.withPrimary(text: "generic.ok".localized, action: { vc in
vc.close(with: .OK)
})
.show()
}
@MainActor private static func notifyAboutUpgrade(latest: String, constraint: String, passing: Bool) {
let alert = NVAlert().withInformation(
title: "valet_upgrade_available.title".localized,
subtitle: "valet_upgrade_available.subtitle".localized(latest),
description: passing
? "valet_upgrade_available.description_constraint_ok".localized(latest)
: "valet_upgrade_available.description_constraint_fail".localized(constraint, latest)
)
.withPrimary(text: "valet_upgrade_available.upgrade".localized, action: { vc in
vc.close(with: .OK)
ValetUpgrader.upgradeValet()
})
.withSecondary(text: "valet_upgrade_available.cancel".localized)
if !passing {
_ = alert.withTertiary(text: "valet_upgrade_available.open_composer".localized, action: { _ in
MainMenu.shared.openGlobalComposerFolder()
})
}
alert.show()
}
}

View File

@@ -9,23 +9,27 @@
class FakeDomainScanner: DomainScanner {
var sites: [ValetSite] = [
FakeValetSite(fakeWithName: "laravel", tld: "test", secure: true,
path: "~/Code/laravel/framework", linked: true),
FakeValetSite(fakeWithName: "larament", tld: "test", secure: true,
path: "~/Code/sites/larament", linked: true),
FakeValetSite(fakeWithName: "tailwind", tld: "test", secure: true,
path: "~/Code/tailwind/site", linked: true, constraint: "8.0"),
FakeValetSite(fakeWithName: "symfony", tld: "test", secure: true,
path: "~/Code/sites/symfony", linked: true, driver: "Symfony (^7.3)"),
FakeValetSite(fakeWithName: "forge", tld: "test", secure: true,
path: "~/Code/laravel/forge", linked: true),
FakeValetSite(fakeWithName: "concord", tld: "test", secure: false,
path: "~/Code/concord", linked: true, driver: "Laravel (^8.0)", constraint: "^7.4", isolated: "7.4"),
FakeValetSite(fakeWithName: "tempest", tld: "test", secure: true,
path: "~/Code/sites/tempest", linked: true, driver: "Tempest (^1.6)", constraint: "^8.4"),
FakeValetSite(fakeWithName: "drupal", tld: "test", secure: false,
path: "~/Sites/drupal", linked: false, driver: "Drupal", constraint: "^7.4", isolated: "7.4"),
path: "~/Sites/drupal", linked: false, driver: "Drupal", constraint: "^8.2", isolated: "8.2"),
FakeValetSite(fakeWithName: "wordpress", tld: "test", secure: false,
path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^7.4", isolated: "7.4")
path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^8.0", isolated: "8.0"),
FakeValetSite(fakeWithName: "concord", tld: "test", secure: false,
path: "~/Code/concord", linked: true, driver: "Laravel (^10)", constraint: "^8.3", isolated: "8.3"),
FakeValetSite(fakeWithName: "gen-ai-mcp", tld: "test", secure: true,
path: "~/Code/gen-ai-mcp", linked: true, driver: "Laravel (^12)",
constraint: "^8.4", isolated: "8.4")
]
var proxies: [ValetProxy] = [

View File

@@ -15,8 +15,8 @@ class FakeValetSite: ValetSite {
secure: Bool,
path: String,
linked: Bool,
driver: String = "Laravel (^9.0)",
constraint: String = "^8.1",
driver: String = "Laravel (^12)",
constraint: String = "^8.4",
isolated: String? = nil
) {
self.init(

View File

@@ -24,6 +24,9 @@ class Valet {
/// The version of Valet that was detected.
var version: VersionNumber?
/// The latest version of Valet, checked via Packagist, if possible.
var latestVersion: VersionNumber?
/// The Valet configuration file.
var config: Valet.Configuration!
@@ -80,6 +83,29 @@ class Valet {
return self.shared.sites + self.shared.proxies
}
/**
Updates the internal version number of Laravel Valet.
If this version number cannot be determined, it fails,
and the app cannot start.
*/
public func updateVersionNumber() async {
let output = await Shell.pipe("valet --version").out
// Failure condition #1: does not contain Laravel Valet
if !output.contains("Laravel Valet") {
return
}
// Failure condition #2: version cannot be parsed
let versionString = output
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "Laravel Valet")[1]
.trimmingCharacters(in: .whitespaces)
// Extract the version number
Valet.shared.version = try? VersionNumber.parse(VersionExtractor.from(versionString)!)
}
/**
We don't want to load the initial config.json file as soon as the class is initialised.
@@ -184,6 +210,28 @@ class Valet {
.out.contains("Composer detected issues in your platform")
}
/**
Determine if there is a newer version of Laravel Valet available.
Checks by verifying the latest version via Packagist (Composer).
*/
public func checkForUpdates() async {
if let currentVersion = self.version,
let latestVersion = try? await Packagist.getLatestStableVersion(packageName: "laravel/valet") {
self.latestVersion = latestVersion
if latestVersion.isNewerThan(currentVersion) {
Log.info("The latest version of Valet is \(latestVersion.text); current is \(currentVersion.text).")
// Update the menu so this update is visible.
await MainMenu.shared.rebuild()
} else {
Log.info("You are running the latest version of Valet (\(latestVersion.text)).")
}
} else {
Log.warn("Could not check for latest version of Valet.")
}
}
/**
Determine if PHP-FPM is configured correctly.

View File

@@ -56,6 +56,10 @@ extension MainMenu {
// Actually detect the PHP versions
await PhpEnvironments.detectPhpVersions()
// Verify third party taps
// The missing tap(s) will be actionable later
await BrewDiagnostics.verifyThirdPartyTaps()
// Check for an alias conflict
await BrewDiagnostics.checkForCaskConflict()
@@ -142,6 +146,11 @@ extension MainMenu {
// Check if the linked version has changed between launches of phpmon
await PhpGuard().compareToLastGlobalVersion()
// Check if Valet has updates, but only if the driver display is enabled
if Preferences.isEnabled(.displayDriver) {
await Valet.shared.checkForUpdates()
}
}
}

View File

@@ -137,6 +137,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
}
}
@objc func showValetUpgradeAvailableAlert() {
ValetUpgrader.showUpgradeAlert()
}
/** Reloads the menu in the background, using `asyncExecution`. */
@objc func reloadPhpMonitorMenuInBackground() {
asyncExecution({

View File

@@ -7,6 +7,7 @@
//
import Cocoa
import NVAlert
extension StatusMenu {
@MainActor func addLiteModeMenuItem() {
@@ -18,11 +19,23 @@ extension StatusMenu {
@MainActor func addValetVersionItem() {
if let version = Valet.shared.version {
addItems([
var items = [
NSMenuItem.separator(),
NSMenuItem(title: "mi_driver".localized("Valet \(version.text)"),
action: nil, customImage: "ValetDriverIcon")
])
]
if let latest = Valet.shared.latestVersion {
if latest.isNewerThan(version) {
items.append(
NSMenuItem(title: "mi_valet_upgrade_action".localized(latest.text),
action: #selector(MainMenu.showValetUpgradeAvailableAlert),
systemImage: "arrow.up.square.fill")
)
}
}
addItems(items)
}
}
}

View File

@@ -66,6 +66,7 @@ class StatusMenu: NSMenu {
addPreferencesMenuItems()
if Preferences.isEnabled(.displayDriver) {
if Valet.installed {
// Add the menu item displaying the driver information
addValetVersionItem()
@@ -73,6 +74,7 @@ class StatusMenu: NSMenu {
// No driver, using Standalone Mode (internally: lite mode)
addLiteModeMenuItem()
}
}
addCoreMenuItems()
}

View File

@@ -48,6 +48,7 @@ enum PreferenceName: String, Codable {
case displayExtensions = "display_extensions"
case displayPresets = "display_presets"
case displayMisc = "display_misc"
case displayDriver = "display_driver"
/**
What type of data each preference contains.
@@ -81,7 +82,8 @@ enum PreferenceName: String, Codable {
.displayLimitsWidget,
.displayExtensions,
.displayPresets,
.displayMisc
.displayMisc,
.displayDriver
],
.string: [
.globalHotkey,

View File

@@ -72,6 +72,7 @@ class Preferences {
PreferenceName.notifyAboutGlobalComposerStatus.rawValue: true,
/// Preferences: UI Preferences
PreferenceName.displayDriver.rawValue: true,
PreferenceName.displayGlobalVersionSwitcher.rawValue: true,
PreferenceName.displayServicesManager.rawValue: true,
PreferenceName.displayValetIntegration.rawValue: true,

View File

@@ -55,7 +55,8 @@ class MenuStructurePreferencesVC: GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
_ = vc.addView(when: true, vc.displayFeature("prefs.display_global_version_switcher", .displayGlobalVersionSwitcher, true))
return vc
.addView(when: true, vc.displayFeature("prefs.display_global_version_switcher", .displayGlobalVersionSwitcher, true))
.addView(when: Valet.installed, vc.displayFeature("prefs.display_services_manager", .displayServicesManager))
.addView(when: Valet.installed, vc.displayFeature("prefs.display_valet_integration", .displayValetIntegration))
.addView(when: true, vc.displayFeature("prefs.display_php_config_finder", .displayPhpConfigFinder))
@@ -64,8 +65,7 @@ class MenuStructurePreferencesVC: GenericPreferenceVC {
.addView(when: true, vc.displayFeature("prefs.display_extensions", .displayExtensions))
.addView(when: true, vc.displayFeature("prefs.display_presets", .displayPresets))
.addView(when: true, vc.displayFeature("prefs.display_misc", .displayMisc))
return vc
.addView(when: true, vc.displayFeature("prefs.display_driver", .displayDriver))
}
// swiftlint:enable line_length
}
@@ -76,15 +76,13 @@ class NotificationPreferencesVC: GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
_ = vc.addView(when: true, vc.getNotifyAboutVersionChangePV())
return vc.addView(when: true, vc.getNotifyAboutVersionChangePV())
.addView(when: true, vc.getNotifyAboutPresetsPV())
.addView(when: Valet.installed, vc.getNotifyAboutSecureTogglePV())
.addView(when: true, vc.getNotifyAboutGlobalComposerStatusPV())
.addView(when: true, vc.getNotifyAboutServicesPV())
.addView(when: Valet.installed, vc.getNotifyAboutPhpFpmChangePV())
.addView(when: Valet.installed, vc.getWarnAboutNonStandardTldPV())
return vc
}
}

View File

@@ -0,0 +1,25 @@
//
// InstallHomebrew.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
class InstallHomebrew {
public func run() async throws {
let script = """
NONINTERACTIVE=1 /bin/bash -c \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
"""
_ = try await Shell.attach(script, didReceiveOutput: { (string: String, _: ShellStream) in
print(string)
}, withTimeout: 60 * 10)
}
public func verify() async {
// Make sure the Homebrew directory exists
// Make sure the `brew` binary exists
}
}

View File

@@ -0,0 +1,40 @@
//
// Provision+zshrc.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
class ZshRunCommand {
/**
Adds a given line to .zshrc, which may be needed to adjust the PATH.
*/
private func add(_ text: String) async -> Bool {
let outcome = await Shell.pipe("""
touch ~/.zshrc && \
grep -qxF '\(text)' ~/.zshrc \
|| echo '\n\n\(text)\n' >> ~/.zshrc
""")
if outcome.hasError {
return false
}
return true
}
/**
Adds Homebrew binaries to the PATH.
*/
public func addHomebrewPath() async {
_ = await add("export PATH=$HOME/bin:/opt/homebrew/bin:$PATH")
}
/**
Adds PHP Monitor binaries to the PATH.
*/
public func addPhpMonitorPath() async {
_ = await add("export PATH=$HOME/bin:~/.config/phpmon/bin:$PATH")
}
}

View File

@@ -15,6 +15,7 @@ struct Warning: Identifiable, Hashable {
let title: String
let paragraphs: () -> [String]
let url: String?
let fix: (() async -> Void)?
/**
- Parameters:
@@ -23,19 +24,22 @@ struct Warning: Identifiable, Hashable {
- title: The title displayed for the user
- paragraphs: The main body of text displayed for the user
- url: The URL that one can navigate to for more information (if applicable)
- fix: The automatic fix that can be applied (if applicable)
*/
init(
command: @escaping () async -> Bool,
name: String,
title: String,
paragraphs: @escaping () -> [String],
url: String?
url: String?,
fix: (() async -> Void)?
) {
self.command = command
self.name = name
self.title = title
self.paragraphs = paragraphs
self.url = url
self.fix = fix
}
public func applies() async -> Bool {

View File

@@ -36,7 +36,8 @@ class WarningManager: ObservableObject {
name: "Running PHP Monitor with Rosetta on M1",
title: "warnings.arm_compatibility.title",
paragraphs: { return ["warnings.arm_compatibility.description"] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon"
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon",
fix: nil
),
Warning(
command: {
@@ -50,7 +51,75 @@ class WarningManager: ObservableObject {
"warnings.helper_permissions.unavailable",
"warnings.helper_permissions.symlink"
] },
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries"
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries",
fix: Paths.shell == "/bin/zsh" ? {
// Add to PATH
await ZshRunCommand().addPhpMonitorPath()
// Finally, perform environment checks again
await WarningManager.shared.checkEnvironment()
} : nil
),
Warning(
command: {
PhpEnvironments.shared.currentInstall?.extensions.contains { $0.name == "xdebug" } ?? false
&& !Xdebug.enabled
},
name: "Missing configuration file for `xdebug.mode`",
title: "warnings.xdebug_conf_missing.title",
paragraphs: { return [
"warnings.xdebug_conf_missing.description"
] },
url: "https://xdebug.org/docs/install#mode",
fix: {
if let php = PhpEnvironments.shared.currentInstall {
if let xdebug = php.extensions.first(where: { $0.name == "xdebug" }),
let original = try? FileSystem.getStringFromFile(xdebug.file) {
// Append xdebug.mode = off to the file
try? FileSystem.writeAtomicallyToFile(
xdebug.file,
content: original + "\nxdebug.mode = off"
)
// Reload extension configuration by updating PHP installation info (reload)
PhpEnvironments.shared.currentInstall = ActivePhpInstallation()
// Finally, reload warnings
await WarningManager.shared.checkEnvironment()
}
}
}
),
Warning(
command: {
!BrewDiagnostics.installedTaps.contains("shivammathur/php")
},
name: "`shivammathur/php` tap is missing",
title: "warnings.php_tap_missing.title",
paragraphs: { return [
"warnings.php_tap_missing.description"
] },
url: "https://github.com/shivammathur/homebrew-php",
fix: {
await Shell.quiet("brew tap shivammathur/php")
await BrewDiagnostics.loadInstalledTaps()
await WarningManager.shared.checkEnvironment()
}
),
Warning(
command: {
!BrewDiagnostics.installedTaps.contains("shivammathur/extensions")
},
name: "`shivammathur/extensions` tap is missing",
title: "warnings.extensions_tap_missing.title",
paragraphs: { return [
"warnings.extensions_tap_missing.description"
] },
url: "https://github.com/shivammathur/homebrew-extensions",
fix: {
await Shell.quiet("brew tap shivammathur/extensions")
await BrewDiagnostics.loadInstalledTaps()
await WarningManager.shared.checkEnvironment()
}
),
Warning(
command: {
@@ -64,7 +133,8 @@ class WarningManager: ObservableObject {
PhpConfigChecker.shared.missing.joined(separator: "\n")
)
] },
url: nil
url: nil,
fix: nil
)
]
@@ -88,6 +158,10 @@ class WarningManager: ObservableObject {
Checks the user's environment and checks if any special warnings apply.
*/
func checkEnvironment() async {
ActiveShell.reload()
await BrewDiagnostics.loadInstalledTaps()
if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil {
self.temporaryWarnings = self.evaluations
await self.broadcastWarnings()

View File

@@ -76,7 +76,8 @@ struct PhpDoctorView: View {
WarningView(
title: warning.title,
paragraphs: warning.paragraphs(),
documentationUrl: warning.url
documentationUrl: warning.url,
automaticFix: warning.fix
)
.fixedSize(horizontal: false, vertical: true)

View File

@@ -23,7 +23,7 @@ class PhpDoctorWindowController: PMWindowController {
guard let window = windowController.window else { return }
window.title = ""
window.styleMask = [.titled, .closable, .miniaturizable]
window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: PhpDoctorView())

View File

@@ -12,6 +12,8 @@ struct WarningView: View {
@State var title: String
@State var paragraphs: [String]
@State var documentationUrl: String?
@State var automaticFix: (() async -> Void)?
@State var busyFixing: Bool = false
var body: some View {
VStack(alignment: .leading) {
@@ -36,9 +38,38 @@ struct WarningView: View {
minHeight: 0, maxHeight: .infinity,
alignment: .topLeading
)
if documentationUrl != nil {
HStack {
if let automaticFix {
Button(
action: {
Task {
busyFixing = true
await automaticFix()
busyFixing = false
}
},
label: {
Text("Fix Automatically")
}
)
.disabled(busyFixing) }
if let documentationUrl {
Button("Learn More") {
NSWorkspace.shared.open(URL(string: documentationUrl!)!)
NSWorkspace.shared.open(URL(string: documentationUrl)!)
}
}
}
if busyFixing {
HStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(0.6)
Text("warnings.being_fixed.description".localizedForSwiftUI)
.font(.system(size: 12))
.foregroundStyle(Color.secondary)
}
}
}

View File

@@ -71,6 +71,7 @@ struct PhpVersionManagerView: View {
)
}
// Finally, load PHP information
await PhpEnvironments.detectPhpVersions()
await self.handler.refreshPhpVersions(loadOutdated: false)
await self.handler.refreshPhpVersions(loadOutdated: true)

View File

@@ -72,6 +72,9 @@
"mi_quit" = "Quit PHP Monitor";
"mi_about" = "About PHP Monitor";
"mi_valet_upgrade_available" = "An updated version of Valet is available.";
"mi_valet_upgrade_action" = "Upgrade to Valet %@...";
"mi_presets_title" = "Configuration Presets";
"mi_apply_presets_title" = "Apply Configuration Presets";
"mi_revert_to_prev_config" = "Revert to Previous Configuration...";
@@ -467,6 +470,9 @@ This has no effect on other terminals, only for the particular terminal session
"prefs.warn_about_non_standard_tld_desc" = "If you use a non-standard TLD, you may not wish to get repeated notifications about this.";
"prefs.warn_about_non_standard_tld" = "Warn about non-standard TLD";
"prefs.display_driver_desc" = "If disabled, you will not be able to see which driver is in use. If Valet is active, the version number will also no longer be visible, and you will not be notified about updates to Valet in the menu.";
"prefs.display_driver" = "Driver & Updates";
"prefs.display_global_version_switcher_desc" = "If disabled, you will not be able to change the globally linked PHP version via the main menu.";
"prefs.display_global_version_switcher" = "PHP Switcher";
@@ -816,12 +822,13 @@ COMMON TROUBLESHOOTING TIPS
"warnings.limits_error.title" = "PHP Monitor could not retrieve limits.";
"warnings.limits_error.steps" = "Try running php -v in your terminal.";
"warnings.being_fixed.description" = "PHP Monitor is addressing this issue. Please wait a bit.";
"warnings.title" = "PHP Doctor";
"warnings.description" = "**PHP Doctor** will suggest improvements to your active system configuration.";
"warnings.disclaimer" = "You may choose to hide all recommendations from the PHP Monitor menu in Preferences, but it is recommended that you deal with all actionable items.";
"warnings.refresh.button" = "Scan Again";
"warnings.refresh.button.description" = "Press this button once you've fixed an issue. This will cause PHP Monitor to re-evaluate your environment. If it's really fixed, the recommendation should disappear.";
"warnings.refresh.button.description" = "Press this button if you've manually fixed an issue. This will cause PHP Monitor to re-evaluate your environment. (Automatic fixes do not require you to press this button.)";
"warnings.helper_permissions.title" = "PHP Monitors helpers are currently unavailable.";
"warnings.helper_permissions.description" = "PHP Monitor comes with various helper scripts. Using these scripts allows you to easily invoke a specific version of PHP without switching the linked PHP version.";
@@ -838,6 +845,15 @@ COMMON TROUBLESHOOTING TIPS
When files like these are missing, you should switch to the PHP version associated with those files: that may resolve the problem. If this doesn't fix the issue, it's recommended to reinstall the appropriate PHP version(s) via Homebrew again, which should restore the configuration files that are missing. Missing configuration files can be the reason why you get '502 Bad Gateway' errors, even after running Fix My Valet (if you are using Valet).";
"warnings.xdebug_conf_missing.title" = "Xdebug configuration is incomplete";
"warnings.xdebug_conf_missing.description" = "You have Xdebug installed, but no mode is specified. Would you like for PHP Monitor to update your configuration? You can also fix this manually.";
"warnings.php_tap_missing.title" = "`shivammathur/php` tap is missing";
"warnings.php_tap_missing.description" = "This Homebrew tap is required to install older PHP versions via this app's PHP Version Manager.";
"warnings.extensions_tap_missing.title" = "`shivammathur/extensions` tap is missing";
"warnings.extensions_tap_missing.description" = "This Homebrew tap is required to install extensions via this app's PHP Extension Manager.";
"warnings.none" = "There are no recommendations available for you right now. You're all good!";
// ONBOARDING
@@ -878,3 +894,21 @@ Please note that some features (greyed out below) are currently unavailable beca
It is recommended to restart PHP Monitor afterwards. Learn more about this issue at: https://github.com/nicoverbruggen/phpmon/issues/294.
If PHP Monitor has finished initializing anyway or you want to wait a bit longer, feel free to click 'Ignore' and use PHP Monitor as usual. Either way, you may want to investigate, because this isn't supposed to take this long.";
// VALET UPGRADES
"valet_upgrade_available.title" = "Do you want to upgrade Valet to the latest compatible version?";
"valet_upgrade_available.subtitle" = "Laravel Valet %@ is available. PHP Monitor can update your global Composer dependencies for you.";
"valet_upgrade_available.description_constraint_ok" = "Given your global Composer setup, the latest version that will be installed should be the very latest, Laravel Valet %@.";
"valet_upgrade_available.description_constraint_fail" = "Please note that given your global Composer setup, the latest version that can be automatically installed WILL NOT be latest version available. This is due to constraints in your global `composer.json` file.
Your version constraint for Valet is: `%@`, which prohibits the installation of Valet %@.
If you want to make edits to this file, please do so before upgrading. When you see this message, you should probably check Valet's release notes, as Valet's requirements may have changed.";
"valet_upgrade_available.upgrade" = "Upgrade";
"valet_upgrade_available.cancel" = "Cancel";
"valet_upgrade_available.open_composer" = "Locate `composer.json`";
"valet_upgraded.title" = "Valet has been upgraded!";
"valet_upgraded.subtitle" = "Sometimes, upgrades may require you to run `valet install` in a terminal after upgrading.";
"valet_upgraded.description" = "PHP Monitor cannot know if this is necessary, so it doesn't do this automatically. However, if things don't seem to work correctly, you can try running `valet install` again and restart PHP Monitor.";

View File

@@ -0,0 +1,50 @@
//
// BundleHelper.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
This file is used to access bundle resources via test structs
that cannot access the bundle via the previous class syntax.
After converting a test to Swift Testing, this is no longer possible:
```swift
class MyTest: XCTestCase {
static var configurationFileUrl: URL {
return Bundle(for: Self.self).url(
forResource: "valet-config",
withExtension: "json"
)!
}
}
```
Normally, we would be able to access the bundle via the class
itself, but we'd prefer _not_ to make this accessible to other
classes. Thankfully, this is where `fileprivate` shines.
The bundle is now accessed via `TestBundleClass`, which is not
accessible outside the scope of this file. `TestBundle` as a
global variable, though, is!
Usage:
```swift
return TestBundle.url(
forResource: "valet-config",
withExtension: "json"
)!
```
*/
fileprivate class TestBundleClass {}
public var TestBundle: Bundle {
return Bundle(for: TestBundleClass.self)
}

View File

@@ -6,21 +6,23 @@
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import XCTest
import Testing
class CommandTest: XCTestCase {
@Suite("Commands")
struct CommandTest {
func test_determine_php_version() {
@Test
func determinePhpVersion() {
let version = Command.execute(
path: Paths.php,
arguments: ["-v"],
trimNewlines: false
)
XCTAssert(version.contains("(cli)"))
XCTAssert(version.contains("NTS"))
XCTAssert(version.contains("built"))
XCTAssert(version.contains("Zend"))
#expect(version.contains("(cli)"))
#expect(version.contains("NTS"))
#expect(version.contains("built"))
#expect(version.contains("Zend"))
}
}

View File

@@ -0,0 +1,19 @@
//
// PackagistTest.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/08/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Testing
@Suite("Integration")
struct PackagistTest {
@Test func canRetrieveLaravelValetVersion() async {
let packageToCheck = "laravel/valet"
let latestVersion = try? await Packagist.getLatestStableVersion(packageName: packageToCheck)
#expect(latestVersion != nil)
}
}

View File

@@ -6,18 +6,20 @@
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import XCTest
class ValetConfigurationTest: XCTestCase {
import Testing
import Foundation
@Suite("Parsers")
struct ValetConfigurationTest {
static var jsonConfigFileUrl: URL {
return Bundle(for: Self.self).url(
return TestBundle.url(
forResource: "valet-config",
withExtension: "json"
)!
}
func test_can_load_config_file() throws {
@Test("Can load config file")
func can_load_config_file() throws {
let json = try? String(
contentsOf: Self.jsonConfigFileUrl,
encoding: .utf8
@@ -27,13 +29,13 @@ class ValetConfigurationTest: XCTestCase {
from: json!.data(using: .utf8)!
)
XCTAssertEqual(config.tld, "test")
XCTAssertEqual(config.paths, [
#expect(config.tld == "test")
#expect(config.paths == [
"/Users/username/.config/valet/Sites",
"/Users/username/Sites"
])
XCTAssertEqual(config.defaultSite, "/Users/username/default-site")
XCTAssertEqual(config.loopback, "127.0.0.1")
#expect(config.defaultSite == "/Users/username/default-site")
#expect(config.loopback == "127.0.0.1")
}
}