diff --git a/DEVELOPER.md b/DEVELOPER.md index 6f1aca6..f8a7396 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -41,6 +41,12 @@ If you'd like to create a production build, choose "Any Mac" as the target and s 10. Update Cask with new version + hash 11. Check new version can be installed via Cask +## šŸ± Marketing Mode + +You can enable marketing mode by setting the `PHPMON_MARKETING_MODE` environment variable. It preloads a list of (fake) domains in the domain window list for screenshot & marketing purposes. + + launchctl setenv PHPMON_MARKETING_MODE true + ## šŸ› Symbolication of crashes If you have an archived build of the app and exported the DSYM, it is possible to symbolicate .ips crash logs. diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 5457102..ef30296 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; 5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; + 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; + 5489625928313231004F647A /* CreatedFromFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5489625728312FAD004F647A /* CreatedFromFile.swift */; }; 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */ = {isa = PBXBuildFile; fileRef = 54A18D3F282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test */; }; 54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; 54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; @@ -43,9 +45,10 @@ C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; }; C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; }; - C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; }; C40B24F227A310770018C7D2 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; + C40C5C9C2846A40600E28255 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; + C40C5C9D2846A40600E28255 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; }; C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpEnv.swift */; }; C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; @@ -86,6 +89,8 @@ C42337A3281F19F000459A48 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; + C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; }; + C42800AB28452AA50099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; }; C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; @@ -102,6 +107,8 @@ C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; + C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; }; + C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; }; C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; }; C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; @@ -115,7 +122,12 @@ C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; }; + C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C44F868D2835BD8D005C353A /* phpmon-config.json */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; + C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; + C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; + C463E380284930EE00422731 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; + C463E381284930EE00422731 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; C464ADAC275A7A3F003FCD53 /* DomainListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */; }; C464ADAD275A7A3F003FCD53 /* DomainListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */; }; C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; @@ -125,6 +137,10 @@ C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; + C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; + C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; }; + C46FA98C2822F08F00D78807 /* PhpConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */; }; + C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; }; C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; @@ -135,11 +151,7 @@ C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; }; - C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C8F25CC7FD000CC7490 /* StatsView.xib */; }; C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; - C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; }; - C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C9925CC888B00CC7490 /* HeaderView.xib */; }; - C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; }; C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; }; C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; }; @@ -149,7 +161,6 @@ C493084B279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; }; C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; }; - C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49E171E27A5736E00787921 /* PMServicesView.swift */; }; C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; @@ -166,6 +177,8 @@ C4B585422770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; }; C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; }; + C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; }; + C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; }; C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; }; C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */; }; @@ -195,8 +208,8 @@ C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; - C4D5CFCA27E0F9CD00035329 /* NginxConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfiguration.swift */; }; - C4D5CFCB27E0F9CD00035329 /* NginxConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfiguration.swift */; }; + C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; + C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; }; @@ -213,15 +226,10 @@ C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; - C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4EC1E65279DE0380010F296 /* ServicesView.xib */; }; - C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E67279DE0540010F296 /* ServicesView.swift */; }; + C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; + C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; - C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; }; - C4EE55AA27708B9E001DF387 /* PMHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A627708B9E001DF387 /* PMHeaderView.swift */; }; - C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A727708B9E001DF387 /* Preview.swift */; }; - C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; }; - C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE55A827708B9E001DF387 /* PMStatsView.swift */; }; C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; }; @@ -235,6 +243,7 @@ C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; }; C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; }; C4F319C927B034A500AFF46F /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; }; + C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; }; C4F780A825D80AE8000DBC97 /* php.ini in Resources */ = {isa = PBXBuildFile; fileRef = C4F780A725D80AE8000DBC97 /* php.ini */; }; @@ -244,19 +253,18 @@ C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; - C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; }; C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; }; C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; - C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; }; C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */; }; + C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; /* End PBXBuildFile section */ @@ -274,6 +282,7 @@ /* Begin PBXFileReference section */ 5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = ""; }; 5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + 5489625728312FAD004F647A /* CreatedFromFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatedFromFile.swift; sourceTree = ""; }; 54A18D3F282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy-custom-tld.test"; sourceTree = ""; }; 54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKeysController.swift; sourceTree = ""; }; @@ -294,6 +303,7 @@ C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarIcons.swift; sourceTree = ""; }; C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlert.swift; sourceTree = ""; }; C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlertVC.swift; sourceTree = ""; }; + C40C5C9B2846A40600E28255 /* Preset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preset.swift; sourceTree = ""; }; C40C7F1D2772136000DDDCDC /* PhpEnv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpEnv.swift; sourceTree = ""; }; C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = ""; }; C40C7F2F27722E8D00DDDCDC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -323,6 +333,7 @@ C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = ""; }; C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = ""; }; + C42800A928452AA10099C999 /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = ""; }; C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = ""; }; C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; @@ -337,17 +348,25 @@ C44067F627E258410045BD4E /* DomainListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListPhpCell.swift; sourceTree = ""; }; C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTypeCell.swift; sourceTree = ""; }; C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = ""; }; + C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = ""; }; + C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = ""; }; C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = ""; }; C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = ""; }; C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = ""; }; C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = ""; }; + C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; + C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = ""; }; + C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = ""; }; C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWC.swift; sourceTree = ""; }; C464ADAE275A7A69003FCD53 /* DomainListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListVC.swift; sourceTree = ""; }; C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; + C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = ""; }; + C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationTest.swift; sourceTree = ""; }; + C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; }; @@ -355,17 +374,12 @@ C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; C484437A2804BB560041A78A /* ValetProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxyScanner.swift; sourceTree = ""; }; - C48D0C8F25CC7FD000CC7490 /* StatsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsView.xib; sourceTree = ""; }; C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; - C48D0C9525CC80B100CC7490 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; - C48D0C9925CC888B00CC7490 /* HeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HeaderView.xib; sourceTree = ""; }; - C48D0CA225CC992000CC7490 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = ""; }; C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = ""; }; - C49E171E27A5736E00787921 /* PMServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMServicesView.swift; sourceTree = ""; }; C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = ""; }; C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = ""; }; C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = ""; }; @@ -377,6 +391,8 @@ C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = ""; }; C4B5853C2770FE3900DA4FBE /* Shell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = ""; }; C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; + C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; + C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MenuOutlets.swift"; sourceTree = ""; }; C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+ActivationPolicy.swift"; sourceTree = ""; }; C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+GlobalHotkey.swift"; sourceTree = ""; }; @@ -392,7 +408,7 @@ C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = ""; }; C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; - C4D5CFC927E0F9CD00035329 /* NginxConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfiguration.swift; sourceTree = ""; }; + C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = ""; }; C4D936C827E3EB6100BD69FE /* PhpHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpHelper.swift; sourceTree = ""; }; @@ -404,18 +420,16 @@ C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = ""; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; - C4EC1E65279DE0380010F296 /* ServicesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ServicesView.xib; sourceTree = ""; }; - C4EC1E67279DE0540010F296 /* ServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = ""; }; + C4EB53E428551F9B006F9937 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; + C4EB53E628553117006F9937 /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; }; C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - C4EE55A627708B9E001DF387 /* PMHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMHeaderView.swift; sourceTree = ""; }; - C4EE55A727708B9E001DF387 /* Preview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preview.swift; sourceTree = ""; }; - C4EE55A827708B9E001DF387 /* PMStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMStatsView.swift; sourceTree = ""; }; C4EED88827A48778006D7272 /* InterAppHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterAppHandler.swift; sourceTree = ""; }; C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = ""; }; C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = ""; }; C4F30B02278E16BA00755FCE /* HomebrewService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewService.swift; sourceTree = ""; }; C4F30B06278E195800755FCE /* brew-services.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "brew-services.json"; sourceTree = ""; }; + C4F361602836BFD9003598CC /* MainMenu+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Actions.swift"; sourceTree = ""; }; C4F5FBCC28218C93001065C5 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -461,6 +475,14 @@ path = Preferences; sourceTree = ""; }; + 5489625628312F95004F647A /* Protocols */ = { + isa = PBXGroup; + children = ( + 5489625728312FAD004F647A /* CreatedFromFile.swift */, + ); + path = Protocols; + sourceTree = ""; + }; 54B20EDF263AA22C00D3250E /* PHP */ = { isa = PBXGroup; children = ( @@ -471,6 +493,7 @@ C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */, C4F2E4392752F7D00020E974 /* PhpInstallation.swift */, C4ACA38E25C754C100060C66 /* PhpExtension.swift */, + C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */, ); path = PHP; sourceTree = ""; @@ -527,9 +550,19 @@ path = Notice; sourceTree = ""; }; + C40C5C9E2846A42D00E28255 /* Presets */ = { + isa = PBXGroup; + children = ( + C40C5C9B2846A40600E28255 /* Preset.swift */, + C463E37F284930EE00422731 /* PresetHelper.swift */, + ); + path = Presets; + sourceTree = ""; + }; C40C7F1C27720E1400DDDCDC /* Test Files */ = { isa = PBXGroup; children = ( + C44F868C2835BD60005C353A /* phpmon */, C459B4C127F6097E00E9B4B4 /* php */, C459B4C027F6096300E9B4B4 /* valet */, C459B4BF27F6094100E9B4B4 /* brew */, @@ -615,6 +648,7 @@ 5420395726135DB800FB00FA /* Preferences */, C44C198F276E3A380072762D /* Progress */, C4C8E81D276F5686003AC782 /* Watcher */, + C40C5C9E2846A42D00E28255 /* Presets */, C4EE55B027708BB2001DF387 /* SwiftUI */, ); path = Domain; @@ -659,6 +693,14 @@ path = Errors; sourceTree = ""; }; + C44F868C2835BD60005C353A /* phpmon */ = { + isa = PBXGroup; + children = ( + C44F868D2835BD8D005C353A /* phpmon-config.json */, + ); + path = phpmon; + sourceTree = ""; + }; C459B4BE27F6093A00E9B4B4 /* nginx */ = { isa = PBXGroup; children = ( @@ -719,13 +761,9 @@ C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */, C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */, C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */, + C4F361602836BFD9003598CC /* MainMenu+Actions.swift */, C47331A1247093B7009A0597 /* StatusMenu.swift */, - C48D0C9525CC80B100CC7490 /* HeaderView.swift */, - C48D0C9925CC888B00CC7490 /* HeaderView.xib */, - C48D0CA225CC992000CC7490 /* StatsView.swift */, - C48D0C8F25CC7FD000CC7490 /* StatsView.xib */, - C4EC1E67279DE0540010F296 /* ServicesView.swift */, - C4EC1E65279DE0380010F296 /* ServicesView.xib */, + C42800A928452AA10099C999 /* StatusMenu+Items.swift */, ); path = Menu; sourceTree = ""; @@ -799,6 +837,7 @@ C4D8016522B1584700C6DA1B /* Startup.swift */, C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, + C45E76132854A65300B4FE0C /* ServicesManager.swift */, ); path = App; sourceTree = ""; @@ -811,10 +850,38 @@ C44CCD4327AFE93300CE40E5 /* Errors */, C4F8C0A222D4F100002EFE61 /* Extensions */, C4811D2822D70D9C00B5F6B3 /* Helpers */, + 5489625628312F95004F647A /* Protocols */, ); path = Common; sourceTree = ""; }; + C4B609162853AA9A00C95265 /* Common */ = { + isa = PBXGroup; + children = ( + C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */, + ); + path = Common; + sourceTree = ""; + }; + C4B609172853AA9E00C95265 /* Menu */ = { + isa = PBXGroup; + children = ( + C4B6091C2853AB9700C95265 /* ServicesView.swift */, + C4709CA128524B3400088BB8 /* StatsView.swift */, + C4B609192853AAD300C95265 /* SectionHeaderView.swift */, + C4EB53E428551F9B006F9937 /* HeaderView.swift */, + ); + path = Menu; + sourceTree = ""; + }; + C4B609182853AAA700C95265 /* Domains */ = { + isa = PBXGroup; + children = ( + C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */, + ); + path = Domains; + sourceTree = ""; + }; C4C0E8D827F887A5002D32A9 /* Sites */ = { isa = PBXGroup; children = ( @@ -838,7 +905,7 @@ C4C0E8DA27F887CC002D32A9 /* Nginx */ = { isa = PBXGroup; children = ( - C4D5CFC927E0F9CD00035329 /* NginxConfiguration.swift */, + C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */, ); path = Nginx; sourceTree = ""; @@ -867,6 +934,7 @@ children = ( C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */, C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */, + C46FA98A2822F08F00D78807 /* PhpConfigurationTest.swift */, C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, ); @@ -940,10 +1008,9 @@ C4EE55B027708BB2001DF387 /* SwiftUI */ = { isa = PBXGroup; children = ( - C49E171E27A5736E00787921 /* PMServicesView.swift */, - C4EE55A627708B9E001DF387 /* PMHeaderView.swift */, - C4EE55A827708B9E001DF387 /* PMStatsView.swift */, - C4EE55A727708B9E001DF387 /* Preview.swift */, + C4B609182853AAA700C95265 /* Domains */, + C4B609172853AA9E00C95265 /* Menu */, + C4B609162853AA9A00C95265 /* Common */, ); path = SwiftUI; sourceTree = ""; @@ -978,6 +1045,7 @@ C48D0C9225CC804200CC7490 /* XibLoadable.swift */, C42759662627662800093CAE /* NSMenuExtension.swift */, C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */, + C4EB53E628553117006F9937 /* ArrayExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -1071,17 +1139,14 @@ files = ( C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */, C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */, - C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */, C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */, C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C4232EE52612526500158FC6 /* Credits.html in Resources */, 54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */, C473319F2470923A009A0597 /* Localizable.strings in Resources */, C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */, - C4EC1E66279DE0380010F296 /* ServicesView.xib in Resources */, 54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */, C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */, - C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1094,6 +1159,7 @@ C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */, C4F780A825D80AE8000DBC97 /* php.ini in Resources */, C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */, + C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */, C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */, C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */, C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */, @@ -1139,39 +1205,43 @@ C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4B585412770FE3900DA4FBE /* Shell.swift in Sources */, C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */, + C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, + C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */, 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, + 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, - C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */, - C4EE55AD27708B9E001DF387 /* PMStatsView.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */, - C4EC1E68279DE0540010F296 /* ServicesView.swift in Sources */, C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */, C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, - C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */, C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, - C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */, + C463E380284930EE00422731 /* PresetHelper.swift in Sources */, C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, + C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, + C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */, C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */, C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */, C4B585442770FE3900DA4FBE /* Command.swift in Sources */, C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */, + C40C5C9C2846A40600E28255 /* Preset.swift in Sources */, C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */, - C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */, + C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */, C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */, + C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */, C415D3B72770F294005EF286 /* Actions.swift in Sources */, C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */, + C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */, C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */, 54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */, @@ -1196,7 +1266,9 @@ C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, + C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, + C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */, C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C417DC74277614690015E6EE /* Helpers.swift in Sources */, C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, @@ -1212,15 +1284,16 @@ C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */, C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */, + C4709CA228524B3400088BB8 /* StatsView.swift in Sources */, C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */, C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */, + C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */, C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */, C476FF9822B0DD830098105B /* Alert.swift in Sources */, C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */, - C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */, - C4D5CFCA27E0F9CD00035329 /* NginxConfiguration.swift in Sources */, + C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */, C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */, C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, @@ -1250,7 +1323,6 @@ files = ( C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */, 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */, - C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */, C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, @@ -1268,7 +1340,7 @@ C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */, 54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */, C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */, - C4D5CFCB27E0F9CD00035329 /* NginxConfiguration.swift in Sources */, + C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, @@ -1287,11 +1359,14 @@ C4C0E8E327F88B13002D32A9 /* ValetSiteScanner.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, + C463E381284930EE00422731 /* PresetHelper.swift in Sources */, + C46FA98C2822F08F00D78807 /* PhpConfigurationTest.swift in Sources */, C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */, C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */, C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */, C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */, C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, + C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */, 54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */, C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */, @@ -1304,7 +1379,9 @@ C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, + C40C5C9D2846A40600E28255 /* Preset.swift in Sources */, C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */, + C42800AB28452AA50099C999 /* StatusMenu+Items.swift in Sources */, C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */, C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */, C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */, @@ -1314,6 +1391,7 @@ C417DC75277614690015E6EE /* Helpers.swift in Sources */, C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, + 5489625928313231004F647A /* CreatedFromFile.swift in Sources */, 54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */, C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, @@ -1327,7 +1405,6 @@ C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */, C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */, C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */, - C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */, C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */, C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, @@ -1341,7 +1418,6 @@ C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */, C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */, C4B585452770FE3900DA4FBE /* Command.swift in Sources */, - C40B24F127A3106D0018C7D2 /* ServicesView.swift in Sources */, C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */, C4F780B725D80B5D000DBC97 /* App.swift in Sources */, C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */, @@ -1349,6 +1425,7 @@ C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */, C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */, C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, + C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */, C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */, C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */, @@ -1356,15 +1433,14 @@ C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */, C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4B585422770FE3900DA4FBE /* Shell.swift in Sources */, + C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, C464ADAD275A7A3F003FCD53 /* DomainListWC.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, - C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */, C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, - C4EE55AA27708B9E001DF387 /* PMHeaderView.swift in Sources */, C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1512,11 +1588,12 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 790; + CURRENT_PROJECT_VERSION = 911; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1526,7 +1603,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.3.2; + MARKETING_VERSION = 5.4; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1538,11 +1615,12 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AppColor; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 790; + CURRENT_PROJECT_VERSION = 911; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1552,7 +1630,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.3.2; + MARKETING_VERSION = 5.4; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index ada3675..6d711af 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -69,8 +69,8 @@ diff --git a/README.md b/README.md index ad385c2..7532f33 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,15 @@ **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 you need to have it set up before you can use this app (consult the FAQ below with info about how to set up your environment). -phpmon screenshot (menu bar app) +phpmon screenshot (menu bar app) +phpmon screenshot (menu bar app) Screenshot: Showing the key functionality of PHP Monitor. 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)! -phpmon screenshot (notification) +phpmon screenshot (notification) +phpmon screenshot (notification) PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more). @@ -21,10 +23,10 @@ You can also add new domains as links, isolate sites, manage various services, a PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs. * Your user account can administer your computer (required for some functionality, e.g. certificate generation) -* macOS 11 Big Sur or higher (supports macOS 12 Monterey) +* macOS 11 Big Sur or later * Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew` * Homebrew `php` formula is installed -* Laravel Valet 2.16 or newer (supports Valet 3) +* Laravel Valet 3 recommended (but compatible with Valet 2) _You may need to update your Valet installation to keep everything working if a major version update of PHP has been released. You can do this by running `composer global update && valet install`. Some features are not supported when running Valet 2._ @@ -105,12 +107,10 @@ Super convenient! If you want to set up your computer for the very first time with PHP Monitor, here's how I do it: -Install [Homebrew](https://brew.sh) first. +Install [Homebrew](https://brew.sh) first. Follow the instructions there first! -Install PHP, composer, add to path: +Then, you'll need to set up your PATH. - brew install php - brew install composer nano .zshrc Make sure the following line is not in the comments: @@ -123,21 +123,27 @@ If you're on an Apple Silicon-based Mac, you'll need to add: # on an M1 Mac export PATH=$HOME/bin:/opt/homebrew/bin:$PATH -and add the following to your .zshrc, but add this BEFORE the homebrew PATH additions: +and add the following to your `.zshrc` file, but add this BEFORE the homebrew PATH additions: export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH -If you're adding composer and Homebrew binaries, ensure that Homebrew binaries are preferred by adding these to the path last. On my system, that looks like this: +If you're adding `composer` and Homebrew binaries, ensure that Homebrew binaries are preferred by adding these to the path last. On my system, that looks like this: export PATH=$HOME/bin:/usr/local/bin:$PATH export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH export PATH=$HOME/bin:/opt/homebrew/bin:$PATH +If you are *not* on Apple Silicon, you should remove the third line. + +Install the `php` and `composer` formulae: + + brew install php composer + Make sure PHP is linked correctly: which php -should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php`) +should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php` if you are on Apple Silicon) composer global require laravel/valet valet install @@ -146,7 +152,12 @@ This should install `dnsmasq` and set up Valet. Great, almost there! valet trust -Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work. +You can now install PHP Monitor, if you haven't already: + + brew tap nicoverbruggen/homebrew-cask + brew install --cask phpmon + +Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work. You will need to approve the initial launch of the app, but you should be ready to go now.
@@ -297,6 +308,38 @@ The app includes an Internet Access Policy file, so if you're using something li
+
+How do I various presets to show up? + +You must set these presets up in a JSON file, located in `~/.config/phpmon/config.json`. + +You must have set up at least one valid preset for this presets to work in PHP Monitor. + +Here's an example of a working preset: + +
+{
+    "scan_apps": [],
+    "presets": [
+        {
+            "name": "Legacy Project",
+            "php": "8.0",
+            "extensions": {
+                "xdebug": false
+            },
+            "configuration": {
+                "memory_limit": "128M",
+                "upload_max_filesize": "128M",
+                "post_max_size": "128M"
+            }
+        }
+    ]
+}
+
+ +You can omit the `php` key in the preset if you do not wish for the preset to switch to a given PHP version. +
+
How do I get various applications to show up in the domain list's right-click menu? @@ -308,11 +351,12 @@ All of these apps should just be detected correctly, no matter their location on To see which files are checked to determine availability, see [this file](./phpmon/Domain/Helpers/Application.swift). -You can add your own apps by creating and editing a `~/.phpmon.conf.json` file, with the following entry: +You can add your own apps by creating and editing a `~/.config/phpmon/config.json` file, and make sure the `scan_apps` key is set:
 {
-    "scan_apps": ["Xcode", "Kraken"]
+    "scan_apps": ["Xcode", "Kraken"],
+    "presets": []
 }
 
diff --git a/SECURITY.md b/SECURITY.md index 41a2df9..1c149f3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,9 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 5.x | āœ… Universal binary | āœ… Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 (*) | 3.0 (2.16.2 minimum) | +| 5.x | āœ… Universal binary | āœ… Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | -_(*) Support for PHP 5.6 is only included if you are using Valet 2.x, since support for PHP 5.6 was dropped in Valet 3.0._ +_(*) macOS Ventura (13.0) is not officially supported until it officially releases._ ## Legacy versions @@ -16,9 +16,9 @@ These versions of PHP Monitor are no longer supported, but if you’re using an | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 4.1 | āœ… Universal binary | āŒ | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 | -| 4.0 | āœ… Universal binary | āŒ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | -| 3.5 | āœ… Universal binary | āŒ | Big Sur (11.0) and Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | +| 4.1 | āœ… Universal binary | āŒ | Big Sur (11.0)
Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 | +| 4.0 | āœ… Universal binary | āŒ | Big Sur (11.0)
Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | +| 3.5 | āœ… Universal binary | āŒ | Big Sur (11.0)
Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | | 3.0—3.4 | āœ… Universal binary | āŒ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.1 | 2.13 | | 2.6 | āœ… Universal binary | āŒ | Big Sur (11.0) | macOS 10.14+ | PHP 5.6—PHP 8.0 | 2.13 | | 2.5 | āœ“ļø Universal binary
`/usr/local/homebrew` installations only | āŒ | Big Sur (11.0)
Catalina (10.15) | macOS 10.14+ | not applicable | not applicable | diff --git a/docs/notification-dark.png b/docs/notification-dark.png new file mode 100644 index 0000000..9cce2e9 Binary files /dev/null and b/docs/notification-dark.png differ diff --git a/docs/screenshot-dark.jpg b/docs/screenshot-dark.jpg new file mode 100644 index 0000000..2b5d623 Binary files /dev/null and b/docs/screenshot-dark.jpg differ diff --git a/docs/screenshot.jpg b/docs/screenshot.jpg index 8303e9b..0b90233 100644 Binary files a/docs/screenshot.jpg and b/docs/screenshot.jpg differ diff --git a/phpmon-tests/Parsers/NginxConfigurationTest.swift b/phpmon-tests/Parsers/NginxConfigurationTest.swift index 15a73af..ddbc7fc 100644 --- a/phpmon-tests/Parsers/NginxConfigurationTest.swift +++ b/phpmon-tests/Parsers/NginxConfigurationTest.swift @@ -37,43 +37,43 @@ class NginxConfigurationTest: XCTestCase { func testCanDetermineSiteNameAndTld() throws { XCTAssertEqual( "nginx-site", - NginxConfiguration.from(filePath: NginxConfigurationTest.regularUrl.path)?.domain + NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.domain ) XCTAssertEqual( "test", - NginxConfiguration.from(filePath: NginxConfigurationTest.regularUrl.path)?.tld + NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.tld ) } func testCanDetermineIsolation() throws { XCTAssertNil( - NginxConfiguration.from(filePath: NginxConfigurationTest.regularUrl.path)?.isolatedVersion + NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.isolatedVersion ) XCTAssertEqual( "8.1", - NginxConfiguration.from(filePath: NginxConfigurationTest.isolatedUrl.path)?.isolatedVersion + NginxConfigurationFile.from(filePath: NginxConfigurationTest.isolatedUrl.path)?.isolatedVersion ) } func testCanDetermineProxy() throws { - let proxied = NginxConfiguration.from(filePath: NginxConfigurationTest.proxyUrl.path)! + let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.proxyUrl.path)! XCTAssertTrue(proxied.contents.contains("# valet stub: proxy.valet.conf")) XCTAssertEqual("http://127.0.0.1:90", proxied.proxy) - let normal = NginxConfiguration.from(filePath: NginxConfigurationTest.regularUrl.path)! + let normal = NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)! XCTAssertFalse(normal.contents.contains("# valet stub: proxy.valet.conf")) XCTAssertEqual(nil, normal.proxy) } func testCanDetermineSecuredProxy() throws { - let proxied = NginxConfiguration.from(filePath: NginxConfigurationTest.secureProxyUrl.path)! + let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.secureProxyUrl.path)! XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) XCTAssertEqual("http://127.0.0.1:90", proxied.proxy) } func testCanDetermineProxyWithCustomTld() throws { - let proxied = NginxConfiguration.from(filePath: NginxConfigurationTest.customTldProxyUrl.path)! + let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.customTldProxyUrl.path)! XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) XCTAssertEqual("http://localhost:8080", proxied.proxy) } diff --git a/phpmon-tests/Parsers/PhpConfigurationTest.swift b/phpmon-tests/Parsers/PhpConfigurationTest.swift new file mode 100644 index 0000000..3ec73d4 --- /dev/null +++ b/phpmon-tests/Parsers/PhpConfigurationTest.swift @@ -0,0 +1,84 @@ +// +// PhpConfigurationTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/05/2022. +// Copyright Ā© 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class PhpConfigurationTest: XCTestCase { + + static var phpIniFileUrl: URL { + return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")! + } + + func testCanLoadExtension() throws { + let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! + + XCTAssertNotNil(iniFile) + + XCTAssertGreaterThan(iniFile.extensions.count, 0) + } + + func testCanCheckKeyExistence() throws { + let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! + + XCTAssertTrue(iniFile.has(key: "error_reporting")) + XCTAssertTrue(iniFile.has(key: "display_errors")) + XCTAssertFalse(iniFile.has(key: "my_unknown_key")) + } + + func testCanCheckKeyValue() throws { + let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! + + XCTAssertNotNil(iniFile.get(for: "error_reporting")) + XCTAssert(iniFile.get(for: "error_reporting") == "E_ALL") + + XCTAssertNotNil(iniFile.get(for: "display_errors")) + XCTAssert(iniFile.get(for: "display_errors") == "On") + } + + func testCanCustomizeConfigurationValue() throws { + let destination = Utility + .copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! + + let configurationFile = PhpConfigurationFile + .from(filePath: destination.path)! + + // 0. Verify the original value + XCTAssertEqual(configurationFile.get(for: "error_reporting"), "E_ALL") + + // 1. Change the value + try! configurationFile.replace( + key: "error_reporting", + value: "E_ALL & ~E_DEPRECATED & ~E_STRICT" + ) + XCTAssertEqual( + configurationFile.get(for: "error_reporting"), + "E_ALL & ~E_DEPRECATED & ~E_STRICT" + ) + + // 2. Ensure that same key and value doesn't break subsequent saves + try! configurationFile.replace( + key: "error_reporting", + value: "error_reporting" + ) + XCTAssertEqual( + configurationFile.get(for: "error_reporting"), + "error_reporting" + ) + + // 3. Verify subsequent saves weren't broken + try! configurationFile.replace( + key: "error_reporting", + value: "E_ALL" + ) + XCTAssertEqual( + configurationFile.get(for: "error_reporting"), + "E_ALL" + ) + } + +} diff --git a/phpmon-tests/Parsers/PhpExtensionTest.swift b/phpmon-tests/Parsers/PhpExtensionTest.swift index 375760b..0e38c5e 100644 --- a/phpmon-tests/Parsers/PhpExtensionTest.swift +++ b/phpmon-tests/Parsers/PhpExtensionTest.swift @@ -15,13 +15,13 @@ class PhpExtensionTest: XCTestCase { } func testCanLoadExtension() throws { - let extensions = PhpExtension.load(from: Self.phpIniFileUrl) + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) XCTAssertGreaterThan(extensions.count, 0) } func testExtensionNameIsCorrect() throws { - let extensions = PhpExtension.load(from: Self.phpIniFileUrl) + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) let extensionNames = extensions.map { (ext) -> String in return ext.name @@ -40,7 +40,7 @@ class PhpExtensionTest: XCTestCase { } func testExtensionStatusIsCorrect() throws { - let extensions = PhpExtension.load(from: Self.phpIniFileUrl) + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) // xdebug should be enabled XCTAssertEqual(extensions[0].enabled, true) @@ -51,7 +51,7 @@ class PhpExtensionTest: XCTestCase { func testToggleWorksAsExpected() throws { let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! - let extensions = PhpExtension.load(from: destination) + let extensions = PhpExtension.from(filePath: destination.path) XCTAssertEqual(extensions.count, 6) // Try to disable xdebug (should be detected first)! @@ -66,12 +66,7 @@ class PhpExtensionTest: XCTestCase { XCTAssertTrue(file.contains("; zend_extension=\"xdebug.so\"")) // Make sure if we load the data again, it's disabled - XCTAssertEqual(PhpExtension.load(from: destination).first!.enabled, false) - } - - func testCanRetrieveXdebugMode() throws { - let value = Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('xdebug.mode');"]) - XCTAssertEqual(value, "coverage") + XCTAssertEqual(PhpExtension.from(filePath: destination.path).first!.enabled, false) } } diff --git a/phpmon-tests/Test Files/phpmon/phpmon-config.json b/phpmon-tests/Test Files/phpmon/phpmon-config.json new file mode 100644 index 0000000..628d85e --- /dev/null +++ b/phpmon-tests/Test Files/phpmon/phpmon-config.json @@ -0,0 +1,34 @@ +{ + "scan_apps": [], + "presets": [ + { + "name": "Default PHP", + "extensions": { + "xdebug": false + }, + "configuration": { + "memory_limit": "128M" + } + }, + { + "name": "Personal Site", + "extensions": { + "xdebug": true + }, + "configuration": { + "xdebug.mode": "coverage", + "memory_limit": "512M" + } + }, + { + "name": "PHP Monitor", + "extensions": { + "xdebug": true + }, + "configuration": { + "xdebug.mode": "coverage", + "memory_limit": "512M" + } + } + ] +} diff --git a/phpmon/Assets.xcassets/AppColor.colorset/Contents.json b/phpmon/Assets.xcassets/AppColor.colorset/Contents.json new file mode 100644 index 0000000..4759aba --- /dev/null +++ b/phpmon/Assets.xcassets/AppColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.988", + "green" : "0.580", + "red" : "0.277" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.988", + "green" : "0.723", + "red" : "0.277" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phpmon/Assets.xcassets/AppSecondary.colorset/Contents.json b/phpmon/Assets.xcassets/AppSecondary.colorset/Contents.json new file mode 100644 index 0000000..596b851 --- /dev/null +++ b/phpmon/Assets.xcassets/AppSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.250", + "green" : "0.250", + "red" : "0.250" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.750", + "green" : "0.750", + "red" : "0.750" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phpmon/Assets.xcassets/ServiceLoading.imageset/Contents.json b/phpmon/Assets.xcassets/ServiceLoading.imageset/Contents.json deleted file mode 100644 index b5851cb..0000000 --- a/phpmon/Assets.xcassets/ServiceLoading.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "ServiceLoading.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "ServiceLoading@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading.png b/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading.png deleted file mode 100644 index 2967845..0000000 Binary files a/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading.png and /dev/null differ diff --git a/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading@2x.png b/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading@2x.png deleted file mode 100644 index 79ba93a..0000000 Binary files a/phpmon/Assets.xcassets/ServiceLoading.imageset/ServiceLoading@2x.png and /dev/null differ diff --git a/phpmon/Assets.xcassets/ServiceOff.imageset/Contents.json b/phpmon/Assets.xcassets/ServiceOff.imageset/Contents.json deleted file mode 100644 index 10f5302..0000000 --- a/phpmon/Assets.xcassets/ServiceOff.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "ServiceOff.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "ServiceOff@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff.png b/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff.png deleted file mode 100644 index 326901b..0000000 Binary files a/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff.png and /dev/null differ diff --git a/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff@2x.png b/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff@2x.png deleted file mode 100644 index 9344277..0000000 Binary files a/phpmon/Assets.xcassets/ServiceOff.imageset/ServiceOff@2x.png and /dev/null differ diff --git a/phpmon/Assets.xcassets/ServiceOn.imageset/Contents.json b/phpmon/Assets.xcassets/ServiceOn.imageset/Contents.json deleted file mode 100644 index b1566c6..0000000 --- a/phpmon/Assets.xcassets/ServiceOn.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "ServiceOn.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "ServiceOn@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn.png b/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn.png deleted file mode 100644 index 5f424a3..0000000 Binary files a/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn.png and /dev/null differ diff --git a/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn@2x.png b/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn@2x.png deleted file mode 100644 index c40fe32..0000000 Binary files a/phpmon/Assets.xcassets/ServiceOn.imageset/ServiceOn@2x.png and /dev/null differ diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index e8aaf5f..37107c0 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -24,7 +24,7 @@ class Actions { brew("services restart dnsmasq", sudo: true) } - public static func stopAllServices() { + public static func stopValetServices() { brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true) brew("services stop nginx", sudo: true) brew("services stop dnsmasq", sudo: true) @@ -64,6 +64,29 @@ class Actions { } } + // MARK: - Third Party Services + public static func stopService(name: String, completion: @escaping () -> Void) { + DispatchQueue.global(qos: .userInitiated).async { + brew("services stop \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }) + ServicesManager.loadHomebrewServices(completed: { + DispatchQueue.main.async { + completion() + } + }) + } + } + + public static func startService(name: String, completion: @escaping () -> Void) { + DispatchQueue.global(qos: .userInitiated).async { + brew("services start \(name)", sudo: ServicesManager.shared.rootServices.contains { $0.value.name == name }) + ServicesManager.loadHomebrewServices(completed: { + DispatchQueue.main.async { + completion() + } + }) + } + } + // MARK: - Finding Config Files public static func openGenericPhpConfigFolder() { @@ -88,6 +111,12 @@ class Actions { NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) } + public static func openPhpMonitorConfigFile() { + let file = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".config/phpmon") + NSWorkspace.shared.activateFileViewerSelecting([file] as [URL]) + } + // MARK: - Other Actions public static func createTempPhpInfoFile() -> URL { diff --git a/phpmon/Common/Extensions/ArrayExtension.swift b/phpmon/Common/Extensions/ArrayExtension.swift new file mode 100644 index 0000000..e2f067d --- /dev/null +++ b/phpmon/Common/Extensions/ArrayExtension.swift @@ -0,0 +1,24 @@ +// +// ArrayExtension.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 11/06/2022. +// Copyright Ā© 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension Array { + /** + Sourced from Stack Overflow + https://stackoverflow.com/a/33540708 + */ + func chunked(by distance: Int) -> [[Element]] { + let indicesSequence = stride(from: startIndex, to: endIndex, by: distance) + let array: [[Element]] = indicesSequence.map { + let newIndex = $0.advanced(by: distance) > endIndex ? endIndex : $0.advanced(by: distance) + return Array(self[$0 ..< newIndex]) + } + return array + } +} diff --git a/phpmon/Common/Extensions/NSMenuExtension.swift b/phpmon/Common/Extensions/NSMenuExtension.swift index eeed88d..105beaa 100644 --- a/phpmon/Common/Extensions/NSMenuExtension.swift +++ b/phpmon/Common/Extensions/NSMenuExtension.swift @@ -27,3 +27,25 @@ extension NSMenu { } } + +// MARK: - NSMenuItem subclasses + +class PhpMenuItem: NSMenuItem { + var version: String = "" +} + +class XdebugMenuItem: NSMenuItem { + var mode: String = "" +} + +class ExtensionMenuItem: NSMenuItem { + var phpExtension: PhpExtension? +} + +class EditorMenuItem: NSMenuItem { + var editor: Application? +} + +class PresetMenuItem: NSMenuItem { + var preset: Preset? +} diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index 74702df..897832d 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -7,8 +7,13 @@ import Foundation extension String { - var localized: String { + if #available(macOS 13, *) { + return NSLocalizedString( + self, tableName: nil, bundle: Bundle.main, value: "", comment: "" + ).replacingOccurrences(of: "Preferences", with: "Settings") + } + return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } @@ -32,7 +37,7 @@ extension String { return count } - subscript (r: Range) -> String { + subscript(r: Range) -> String { let start = r.lowerBound let end = r.upperBound return String(self[start ..< end]) @@ -71,4 +76,22 @@ extension String { } } + var stripped: String { + do { + guard let data = self.data(using: .unicode) else { + return "" + } + let attributed = try NSAttributedString( + data: data, + options: [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue], + documentAttributes: nil + ) + return attributed.string + } catch { + return "" + } + } + } diff --git a/phpmon/Common/Helpers/LocalNotification.swift b/phpmon/Common/Helpers/LocalNotification.swift index 51ca179..91ffa4b 100644 --- a/phpmon/Common/Helpers/LocalNotification.swift +++ b/phpmon/Common/Helpers/LocalNotification.swift @@ -10,7 +10,11 @@ import UserNotifications class LocalNotification { - public static func send(title: String, subtitle: String) { + public static func send(title: String, subtitle: String, preference: PreferenceName) { + if !Preferences.isEnabled(preference) { + return + } + let content = UNMutableNotificationContent() content.title = title content.body = subtitle diff --git a/phpmon/Common/PHP/ActivePhpInstallation.swift b/phpmon/Common/PHP/ActivePhpInstallation.swift index 73b60dd..cb1a0e2 100644 --- a/phpmon/Common/PHP/ActivePhpInstallation.swift +++ b/phpmon/Common/PHP/ActivePhpInstallation.swift @@ -20,7 +20,13 @@ class ActivePhpInstallation { var version: Version! var limits: Limits! - var extensions: [PhpExtension]! + var iniFiles: [PhpConfigurationFile] = [] + + var extensions: [PhpExtension] { + return iniFiles.flatMap { initFile in + return initFile.extensions + } + } // MARK: - Computed @@ -34,16 +40,21 @@ class ActivePhpInstallation { // Show information about the current version getVersion() + // Initialize the list of ini files that are loaded + iniFiles = [] + // If an error occurred, exit early if version.error { limits = Limits() - extensions = [] return } // Load extension information - let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini") - extensions = PhpExtension.load(from: path) + let mainConfigurationFileUrl = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini") + + if let file = PhpConfigurationFile.from(filePath: mainConfigurationFileUrl.path) { + iniFiles.append(file) + } // Get configuration values limits = Limits( @@ -60,9 +71,8 @@ class ActivePhpInstallation { // See if any extensions are present in said .ini files paths.forEach { (iniFilePath) in - let loadedExtensions = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath)) - if !loadedExtensions.isEmpty { - extensions.append(contentsOf: loadedExtensions) + if let file = PhpConfigurationFile.from(filePath: iniFilePath) { + iniFiles.append(file) } } } diff --git a/phpmon/Common/PHP/Extensions/Xdebug.swift b/phpmon/Common/PHP/Extensions/Xdebug.swift index e120110..a340a6a 100644 --- a/phpmon/Common/PHP/Extensions/Xdebug.swift +++ b/phpmon/Common/PHP/Extensions/Xdebug.swift @@ -11,16 +11,23 @@ import Foundation class Xdebug { public static var enabled: Bool { - return !self.mode.isEmpty + return PhpEnv.shared.getConfigFile(forKey: "xdebug.mode") != nil } - public static var mode: String { - return Command.execute(path: Paths.php, arguments: ["-r", "echo ini_get('xdebug.mode');"]) + public static var activeModes: [String] { + guard let file = PhpEnv.shared.getConfigFile(forKey: "xdebug.mode") else { + return [] + } + + guard let value = file.get(for: "xdebug.mode") else { + return [] + } + + return value.components(separatedBy: ",").filter { self.modes.contains($0) } } public static var modes: [String] { return [ - "off", "develop", "coverage", "debug", diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index a2cfbf6..3d7a361 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -19,20 +19,20 @@ struct HomebrewService: Decodable, Equatable { let log_path: String? let error_log_path: String? - public static func loadAll( - filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"], - completion: @escaping ([HomebrewService]) -> Void - ) { - DispatchQueue.global(qos: .background).async { - let data = Shell - .pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true) - .data(using: .utf8)! - - let services = try! JSONDecoder() - .decode([HomebrewService].self, from: data) - .filter({ return filter.contains($0.name) }) - - completion(services) - } + /** + Dummy data for preview purposes. + */ + public static func dummy(named service: String, enabled: Bool) -> Self { + return HomebrewService( + name: service, + service_name: service, + running: enabled, + loaded: enabled, + pid: nil, + user: nil, + status: nil, + log_path: nil, + error_log_path: nil + ) } } diff --git a/phpmon/Common/PHP/PHP Version/PhpEnv.swift b/phpmon/Common/PHP/PHP Version/PhpEnv.swift index 54c36f9..4dc3e88 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnv.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnv.swift @@ -174,4 +174,14 @@ class PhpEnv { return false } + + /** + Returns the configuration file instance that is used for a specific config value. + You can then use the configuration file instance to change values. + */ + public func getConfigFile(forKey key: String) -> PhpConfigurationFile? { + return PhpEnv.phpInstall.iniFiles + .reversed() + .first(where: { $0.has(key: key) }) + } } diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift index 421e0ae..4241648 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumber.swift @@ -91,7 +91,7 @@ public struct PhpVersionNumberCollection: Equatable { } } -public struct PhpVersionNumber: Equatable { +public struct PhpVersionNumber: Equatable, Hashable { let major: Int let minor: Int let patch: Int? diff --git a/phpmon/Common/PHP/PhpConfigurationFile.swift b/phpmon/Common/PHP/PhpConfigurationFile.swift new file mode 100644 index 0000000..bd01dd3 --- /dev/null +++ b/phpmon/Common/PHP/PhpConfigurationFile.swift @@ -0,0 +1,225 @@ +// +// PhpConfigurationFile.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/05/2022. +// Copyright Ā© 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class PhpConfigurationFile: CreatedFromFile { + + struct ConfigValue { + let lineIndex: Int + let value: String + } + + typealias Section = [String: ConfigValue] + typealias Config = [String: Section] + + /// The file where this configuration file was located. + let filePath: String + + /// The extensions found in this .ini file. + var extensions: [PhpExtension] + + /// The actual, structured content of the configuration file. + var content: Config + + /// The original lines of the file. + var lines: [String] + + /** Resolves a PHP configuration file (.ini) */ + static func from(filePath: String) -> Self? { + let path = filePath.replacingOccurrences( + of: "~", + with: "/Users/\(Paths.whoami)" + ) + + do { + let fileContents = try String(contentsOfFile: path) + + return Self.init( + path: path, + contents: fileContents + ) + } catch { + Log.warn("Could not read the PHP configuration file at: `\(filePath)`") + return nil + } + } + + required init(path: String, contents: String) { + self.filePath = path + self.lines = contents.components(separatedBy: "\n") + self.extensions = PhpExtension.from(lines, filePath: path) + self.content = Self.parseConfig(lines: lines) + } + + // MARK: API + + public func has(key: String) -> Bool { + return self.content.contains { (_: String, section: Section) in + return section.keys.contains(key) + } + } + + public func get(for key: String) -> String? { + return getConfig(for: key)?.value + } + + public func getConfig(for key: String) -> ConfigValue? { + for (_, section) in self.content { + if section.keys.contains(key) { + return section[key]! + } + } + return nil + } + + enum ReplacementErrors: Error { + case missingKey + } + + /** + Replaces the value for a specific (existing) key with a new value. + The key must exist for this to work. + */ + public func replace(key: String, value: String) throws { + // Ensure that the key exists + guard let item = getConfig(for: key) else { + throw ReplacementErrors.missingKey + } + + // Figure out what comes after the assignment + var components = self + .lines[item.lineIndex] + .components(separatedBy: "=") + + // Replace the value with the new one + components[1] = components[1] + .replacingOccurrences(of: item.value, with: value) + + // Replace the specific line + self.lines[item.lineIndex] = components.joined(separator: "=") + + // Finally, join the string and save the file atomatically again + try self.lines.joined(separator: "\n") + .write(toFile: self.filePath, atomically: true, encoding: .utf8) + + // Reload the original file + self.reload() + } + + public func reload() { + self.lines = try! String(contentsOfFile: self.filePath) + .components(separatedBy: "\n") + self.extensions = PhpExtension.from(lines, filePath: self.filePath) + self.content = Self.parseConfig(lines: lines) + } + + // MARK: Parsing Logic + // Slightly modified from: https://gist.github.com/jetmind/f776c0d223e4ac6aec1ff9389e874553 + + /** + Attempts to parse the configuration file, based on an array of strings. + Each string is a line from the configuration file. + */ + private static func parseConfig(lines: [String]) -> Config { + var config = Config() + + var currentSectionName = "main" + + for (index, line) in lines.enumerated() { + let line = trim(line) + + if line.hasPrefix("[") && line.hasSuffix("]") { + currentSectionName = parseSectionHeader(line) + } else if let (key, value) = parseLine(line) { + var section = config[currentSectionName] ?? [:] + section[key] = ConfigValue( + lineIndex: index, + value: value + ) + config[currentSectionName] = section + } + } + + return config + } + + /** + Remove all whitespace and additional characters from individual lines. + */ + private static func trim(_ string: String) -> String { + let whitespaces = CharacterSet(charactersIn: " \n\r\t") + return string.trimmingCharacters(in: whitespaces) + } + + /** + It may prove beneficial to strip all comments, which can start with # or ;. + In this case, strip both. + */ + private static func stripComment(_ line: String) -> String { + var line = line + + let characters: [String.Element] = ["#", ";"] + + for character in characters { + // Only keep checking for comments as long as the line isn't empty + if line.isEmpty { + return line + } + + // Check for the next comment character + line = strip(character: character, line) + } + + return line + } + + /** + Empties a line if it happens to be commented out, causing it to be ignored. + */ + private static func strip(character: String.Element, _ line: String) -> String { + let parts = line.split( + separator: character, + maxSplits: 1, + omittingEmptySubsequences: false + ) + + if !parts.isEmpty { + return String(parts[0]) + } + + return "" + } + + /** + Attempts to parse a section header. Requires the line to start with [ and end with ]. + */ + private static func parseSectionHeader(_ line: String) -> String { + let from = line.index(after: line.startIndex) + let to = line.index(before: line.endIndex) + + return line[from.. (String, String)? { + let parts = stripComment(line) + .split(separator: "=", maxSplits: 1) + + if parts.count == 2 { + let k = trim(String(parts[0])) + let v = trim(String(parts[1])) + return (k, v) + } + + return nil + } + +} diff --git a/phpmon/Common/PHP/PhpExtension.swift b/phpmon/Common/PHP/PhpExtension.swift index c6d86d7..84a63a9 100644 --- a/phpmon/Common/PHP/PhpExtension.swift +++ b/phpmon/Common/PHP/PhpExtension.swift @@ -89,24 +89,26 @@ class PhpExtension { // MARK: - Static Methods - /** - This method will attempt to identify all extensions in the .ini file at a certain URL. - */ - static func load(from path: URL) -> [PhpExtension] { - let file = try? String(contentsOf: path, encoding: .utf8) + static func from(_ lines: [String], filePath: String) -> [PhpExtension] { + return lines.filter { + return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil + }.map { + return PhpExtension($0, file: filePath) + } + } + + static func from(filePath: String) -> [PhpExtension] { + let file = try? String(contentsOfFile: filePath) if file == nil { Log.err("There was an issue reading the file. Assuming no extensions were found.") return [] } - return file!.components(separatedBy: "\n") - .filter { - return $0.range(of: Self.extensionRegex, options: .regularExpression) != nil - } - .map { - return PhpExtension($0, file: path.path) - } + return Self.from( + file!.components(separatedBy: "\n"), + filePath: filePath + ) } } diff --git a/phpmon/Common/Protocols/CreatedFromFile.swift b/phpmon/Common/Protocols/CreatedFromFile.swift new file mode 100644 index 0000000..0fb46b5 --- /dev/null +++ b/phpmon/Common/Protocols/CreatedFromFile.swift @@ -0,0 +1,15 @@ +// +// CreatedFromFile.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 15/05/2022. +// Copyright Ā© 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol CreatedFromFile { + + static func from(filePath: String) -> Self? + +} diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index 51bfc89..fcbee6f 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -59,6 +59,9 @@ class App { /** List of detected (installed) applications that PHP Monitor can work with. */ var detectedApplications: [Application] = [] + /** The services manager, responsible for figuring out what services are active/inactive. */ + var services = ServicesManager.shared + /** Timer that will periodically reload info about the user's PHP installation. */ var timer: Timer? diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index c455fff..96f5628 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -65,7 +65,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele override init() { logger.verbosity = .info #if DEBUG - logger.verbosity = .performance + // logger.verbosity = .performance #endif if CommandLine.arguments.contains("--v") { logger.verbosity = .performance diff --git a/phpmon/Domain/App/Base.lproj/Main.storyboard b/phpmon/Domain/App/Base.lproj/Main.storyboard index d627efb..679485d 100644 --- a/phpmon/Domain/App/Base.lproj/Main.storyboard +++ b/phpmon/Domain/App/Base.lproj/Main.storyboard @@ -324,36 +324,32 @@ + - + - + - - - - - + - - + @@ -378,7 +374,27 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -532,7 +548,7 @@ Gw