1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50:08 +02:00

🚀 Version 5.5

This commit is contained in:
2022-08-28 15:24:13 +02:00
52 changed files with 1268 additions and 244 deletions

View File

@ -78,6 +78,7 @@
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; };
C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; };
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
@ -85,16 +86,21 @@
C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; };
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; };
C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; };
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
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 */; };
C4297F7A28970D59004C4630 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.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 */; };
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; };
C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; };
C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; };
C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; };
C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; };
@ -114,8 +120,10 @@
C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; };
C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; };
C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; };
C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; };
@ -128,8 +136,8 @@
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 */; };
C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; };
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; };
C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; };
C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; };
C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; };
@ -144,6 +152,8 @@
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 */; };
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
@ -159,8 +169,10 @@
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
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 */; };
C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; };
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; };
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; };
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.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 */; };
@ -204,6 +216,8 @@
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; };
C4CDA893288F1A71007CE25F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; };
C4CDA894288F1A71007CE25F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; };
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; };
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
@ -226,6 +240,7 @@
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 */; };
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.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 */; };
@ -263,6 +278,9 @@
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 */; };
C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; };
C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; };
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.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 */; };
@ -326,18 +344,23 @@
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = "<group>"; };
C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoWarningsView.swift; sourceTree = "<group>"; };
C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+Actions.swift"; sourceTree = "<group>"; };
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+ContextMenu.swift"; sourceTree = "<group>"; };
C4205A7D27F4D21800191A39 /* ValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxy.swift; sourceTree = "<group>"; };
C422DDA928A2C49900CEAC97 /* WarningListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningListView.swift; sourceTree = "<group>"; };
C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningsWindowController.swift; sourceTree = "<group>"; };
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = "<group>"; };
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
C42800A928452AA10099C999 /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4297F7928970D59004C4630 /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = "<group>"; };
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = "<group>"; };
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = "<group>"; };
C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = "<group>"; };
C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = "<group>"; };
C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = "<group>"; };
C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = "<group>"; };
C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = "<group>"; };
@ -350,7 +373,8 @@
C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = "<group>"; };
C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = "<group>"; };
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = "<group>"; };
C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = "<group>"; };
C44A874728905BB000498BC4 /* ProgressVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVC.swift; sourceTree = "<group>"; };
C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalProgressWindowController.swift; sourceTree = "<group>"; };
C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = "<group>"; };
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = "<group>"; };
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = "<group>"; };
@ -358,7 +382,7 @@
C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = "<group>"; };
C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = "<group>"; };
C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = "<group>"; };
C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWC.swift; sourceTree = "<group>"; };
C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWindowController.swift; sourceTree = "<group>"; };
C464ADAE275A7A69003FCD53 /* DomainListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListVC.swift; sourceTree = "<group>"; };
C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = "<group>"; };
C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = "<group>"; };
@ -370,6 +394,8 @@
C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = "<group>"; };
C47699EE28A2F2A30060FEB8 /* WarningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningManager.swift; sourceTree = "<group>"; };
C47699F028A2F3150060FEB8 /* Warning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warning.swift; sourceTree = "<group>"; };
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
@ -379,7 +405,8 @@
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = "<group>"; };
C4998F092617633900B2526E /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = "<group>"; };
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
@ -406,6 +433,7 @@
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = "<group>"; };
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = "<group>"; };
C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = "<group>"; };
C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = "<group>"; };
@ -420,6 +448,7 @@
C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = "<group>"; };
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
C4EB53E428551F9B006F9937 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
C4EB53E628553117006F9937 /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
@ -438,6 +467,8 @@
C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtensionTest.swift; sourceTree = "<group>"; };
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreferencesWindowController+Hotkey.swift"; sourceTree = "<group>"; };
C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWindowController.swift; sourceTree = "<group>"; };
C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionDetectionTest.swift; sourceTree = "<group>"; };
C4FE011028084FC200D1DE6D /* SelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionVC.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -463,7 +494,8 @@
5420395726135DB800FB00FA /* Preferences */ = {
isa = PBXGroup;
children = (
C4998F092617633900B2526E /* PrefsWC.swift */,
C4998F092617633900B2526E /* PreferencesWindowController.swift */,
C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */,
5420395826135DC100FB00FA /* PrefsVC.swift */,
5420395E2613607600FB00FA /* Preferences.swift */,
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
@ -471,6 +503,7 @@
C4DEB7D327A5D60B00834718 /* Stats.swift */,
C41CD0272628D8E20065BBED /* Keybinds */,
54FCFD28276C88C0004CE748 /* Views */,
C4CDA892288F1A71007CE25F /* Keys.swift */,
);
path = Preferences;
sourceTree = "<group>";
@ -580,6 +613,7 @@
C4B5853D2770FE3900DA4FBE /* Command.swift */,
C4B5853B2770FE3900DA4FBE /* Paths.swift */,
C4B5853C2770FE3900DA4FBE /* Shell.swift */,
C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */,
C4C1019A27C65C6F001FACC2 /* Process.swift */,
C40C7F2F27722E8D00DDDCDC /* Logger.swift */,
C417DC73277614690015E6EE /* Helpers.swift */,
@ -645,6 +679,8 @@
C4D9ADBD27761084007277F4 /* PHP */,
C47331A0247093AC009A0597 /* Menu */,
C464ADAA275A7A25003FCD53 /* DomainList */,
C422DDAB28A2DAA100CEAC97 /* Warnings */,
C44A874628905B8500498BC4 /* Onboarding */,
5420395726135DB800FB00FA /* Preferences */,
C44C198F276E3A380072762D /* Progress */,
C4C8E81D276F5686003AC782 /* Watcher */,
@ -654,6 +690,16 @@
path = Domain;
sourceTree = "<group>";
};
C422DDAB28A2DAA100CEAC97 /* Warnings */ = {
isa = PBXGroup;
children = (
C47699F028A2F3150060FEB8 /* Warning.swift */,
C47699EE28A2F2A30060FEB8 /* WarningManager.swift */,
C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */,
);
path = Warnings;
sourceTree = "<group>";
};
C42337A1281F19DC00459A48 /* Extensions */ = {
isa = PBXGroup;
children = (
@ -662,6 +708,16 @@
path = Extensions;
sourceTree = "<group>";
};
C4297F7828970D4E004C4630 /* Warning */ = {
isa = PBXGroup;
children = (
C4297F7928970D59004C4630 /* WarningView.swift */,
C422DDA928A2C49900CEAC97 /* WarningListView.swift */,
C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */,
);
path = Warning;
sourceTree = "<group>";
};
C44067F327E256560045BD4E /* Cells */ = {
isa = PBXGroup;
children = (
@ -675,10 +731,19 @@
path = Cells;
sourceTree = "<group>";
};
C44A874628905B8500498BC4 /* Onboarding */ = {
isa = PBXGroup;
children = (
C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */,
);
path = Onboarding;
sourceTree = "<group>";
};
C44C198F276E3A380072762D /* Progress */ = {
isa = PBXGroup;
children = (
C44C198C276E3A1C0072762D /* ProgressWindow.swift */,
C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */,
C44A874728905BB000498BC4 /* ProgressVC.swift */,
C44C1990276E44CB0072762D /* ProgressWindow.storyboard */,
);
path = Progress;
@ -742,7 +807,7 @@
isa = PBXGroup;
children = (
C44067F327E256560045BD4E /* Cells */,
C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */,
C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */,
C464ADAE275A7A69003FCD53 /* DomainListVC.swift */,
C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */,
C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */,
@ -835,6 +900,7 @@
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */,
C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */,
C40FE736282ABA4F00A302C2 /* AppVersion.swift */,
C45E76132854A65300B4FE0C /* ServicesManager.swift */,
@ -1005,9 +1071,19 @@
path = Switcher;
sourceTree = "<group>";
};
C4E9D2BE2878B32D008FFDAD /* Onboarding */ = {
isa = PBXGroup;
children = (
C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */,
);
path = Onboarding;
sourceTree = "<group>";
};
C4EE55B027708BB2001DF387 /* SwiftUI */ = {
isa = PBXGroup;
children = (
C4297F7828970D4E004C4630 /* Warning */,
C4E9D2BE2878B32D008FFDAD /* Onboarding */,
C4B609182853AAA700C95265 /* Domains */,
C4B609172853AA9E00C95265 /* Menu */,
C4B609162853AA9A00C95265 /* Common */,
@ -1199,12 +1275,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */,
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
C4B585412770FE3900DA4FBE /* Shell.swift in Sources */,
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */,
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
@ -1214,6 +1291,7 @@
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */,
C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */,
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
@ -1228,9 +1306,11 @@
C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */,
C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */,
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */,
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */,
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */,
C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */,
C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */,
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */,
C4B585442770FE3900DA4FBE /* Command.swift in Sources */,
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */,
@ -1238,27 +1318,33 @@
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */,
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */,
C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */,
C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */,
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */,
C4CDA893288F1A71007CE25F /* Keys.swift in Sources */,
C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */,
C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */,
C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */,
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */,
C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */,
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */,
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */,
54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */,
C4297F7A28970D59004C4630 /* WarningView.swift in Sources */,
C4C0E8E227F88B13002D32A9 /* ValetSiteScanner.swift in Sources */,
C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */,
5420395F2613607600FB00FA /* Preferences.swift in Sources */,
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
C47699F128A2F3150060FEB8 /* Warning.swift in Sources */,
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */,
@ -1275,6 +1361,7 @@
C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */,
C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */,
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */,
@ -1296,6 +1383,7 @@
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
@ -1306,10 +1394,11 @@
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */,
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */,
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */,
C464ADAC275A7A3F003FCD53 /* DomainListWC.swift in Sources */,
C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */,
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
@ -1324,6 +1413,7 @@
C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */,
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */,
54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */,
C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */,
@ -1334,6 +1424,7 @@
C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */,
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
@ -1348,6 +1439,7 @@
C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
@ -1376,6 +1468,7 @@
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */,
C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */,
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
@ -1398,14 +1491,15 @@
C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */,
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4CDA894288F1A71007CE25F /* Keys.swift in Sources */,
C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */,
C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */,
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */,
@ -1434,7 +1528,7 @@
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4B585422770FE3900DA4FBE /* Shell.swift in Sources */,
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
C464ADAD275A7A3F003FCD53 /* DomainListWC.swift in Sources */,
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */,
C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */,
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */,
@ -1593,7 +1687,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 912;
CURRENT_PROJECT_VERSION = 951;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1603,7 +1697,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.4.1;
MARKETING_VERSION = 5.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1620,7 +1714,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 912;
CURRENT_PROJECT_VERSION = 951;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1630,7 +1724,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.4.1;
MARKETING_VERSION = 5.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -68,6 +68,11 @@
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "EXTREME_DOCTOR_MODE"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "PAINT_PHPMON_SWIFTUI_VIEWS"
value = ""

View File

@ -1,3 +1,4 @@
> **Note**
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️
<p align="center"><img src="./docs/logo.png" alt="PHP Monitor Logo" width="500px" /></p>
@ -342,6 +343,7 @@ Here's an example of a working preset:
<pre>
{
"scan_apps": [],
"services": [],
"presets": [
{
"name": "Legacy Project",
@ -355,11 +357,67 @@ Here's an example of a working preset:
"post_max_size": "128M"
}
}
]
],
"export": {}
}
</pre>
You can omit the `php` key in the preset if you do not wish for the preset to switch to a given PHP version.
> **Warning**
> You must restart PHP Monitor for these changes to be detected.
</details>
<details>
<summary><strong>How do I ensure additional Homebrew services are shown in the app?</strong></summary>
You must set these services up in a JSON file, located in `~/.config/phpmon/config.json`.
You can specify custom services in the configuration file for Homebrew services that run as your own user (not root).
> **Info**
> If your service must run as root, it cannot currently be added to PHP Monitor.
You can find out which services are available by running `brew services list`.
Here's an example where we add the `mailhog` and `mysql` services to PHP Monitor:
<pre>
{
"scan_apps": [],
"services": ["mailhog", "mysql"],
"presets": [],
"export": {}
}
</pre>
> **Warning**
> You must restart PHP Monitor for these changes to be detected.
</details>
<details>
<summary><strong>How do I set custom environment variables?</strong></summary>
You must configure these custom environment variables up in a JSON file, located in `~/.config/phpmon/config.json`.
PHP Monitor uses a default Shell environment, with no custom environment variables. You need to set custom environment variables manually. These are then used for e.g. Composer.
Here's an example of a working `COMPOSER_HOME` environment variable which is respected:
<pre>
{
"scan_apps": [],
"services": [],
"presets": [],
"export": {
"COMPOSER_HOME": "/absolute/path/to/composer/folder"
}
}
</pre>
> **Warning**
> You must restart PHP Monitor for these changes to be detected.
</details>
<details>
@ -377,12 +435,14 @@ You can add your own apps by creating and editing a `~/.config/phpmon/config.jso
<pre>
{
"scan_apps": ["Xcode", "Kraken"],
"presets": []
"scan_apps": ["Xcode", "Kraken"]
}
</pre>
You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor will check for the existence of these apps. You do not need to set the full path, just the name of the app should work. Not all apps support opening a folder, though, so your success might vary.
> **Warning**
> You must restart PHP Monitor for these changes to be detected.
</details>
<details>
@ -472,14 +532,14 @@ Donations really help with the Apple Developer Program cost, and keep me motivat
## 😎 Acknowledgements
While I did make this application during my own free time, PHP Monitor started out from various learning experiments during work hours at my employer, DIVE. I'd also like to shout out the following folks:
Special thanks go out to:
* My colleagues at [DIVE](https://dive.be)
* Everyone supporting me via [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen)
* Everyone who has donated via [my sponsor page](https://nicoverbruggen.be/sponsor)
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so
* My [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) and those who have donated
* Everyone who has left feedback and reported bugs (appreciate it!)
* Everyone in the Laravel community who shared the app (thanks!)
* Everyone who has left feedback and reported bugs
* Everyone in the Laravel community who shared the app, especially on Twitter
Thank you very much for your contributions, kind words and support.
@ -513,7 +573,8 @@ If an extension or other process writes to a single file a bunch of times in a s
1. **Sites secured or not secured**: Whether the directory has been secured is determined by checking if a matching certificate exists under Valet's `Certificates` directory for that site name.
1. **Project type**: PHP Monitor checks your `composer.json` file for "notable dependencies". If you have `laravel/framework` in your `require`, there's a good chance the project type is `Laravel`, after all.
*Note*: If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly.
> **Note**
> If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly.
### Want to know more?

View File

@ -0,0 +1,27 @@
//
// Shell+PATH.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension Shell {
var PATH: String {
let task = Process()
task.launchPath = "/bin/zsh"
// We need an interactive shell so the user's PATH is loaded in correctly
task.arguments = ["--login", "-ilc", "echo $PATH"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: String.Encoding.utf8) ?? ""
}
}

View File

@ -32,6 +32,9 @@ public class Shell {
*/
public var shell: String = "/bin/sh"
/** Additional exports that are sent if `requiresPath` is set to true. */
public var exports: String = ""
/**
Singleton to access a user shell (with --login)
*/
@ -114,13 +117,23 @@ public class Shell {
Creates a new process with the correct PATH and shell.
*/
public func createTask(for command: String, requiresPath: Bool) -> Process {
let tailoredCommand = requiresPath
? "export PATH=\(Paths.binPath):$PATH && \(command)"
: command
var completeCommand = ""
if requiresPath {
// Basic export (PATH)
completeCommand += "export PATH=\(Paths.binPath):$PATH && "
// Put additional exports in between
if !self.exports.isEmpty {
completeCommand += "\(self.exports) && "
}
}
completeCommand += command
let task = Process()
task.launchPath = self.shell
task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand]
task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand]
return task
}

View File

@ -5,6 +5,7 @@
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import SwiftUI
extension String {
var localized: String {
@ -17,6 +18,10 @@ extension String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
var localizedForSwiftUI: LocalizedStringKey {
return LocalizedStringKey(self.localized)
}
func localized(_ args: CVarArg...) -> String {
String(format: self.localized, arguments: args)
}

View File

@ -1,5 +1,5 @@
//
// FileSystem.swift
// Filesystem.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 07/12/2021.
@ -7,17 +7,52 @@
//
import Cocoa
import Foundation
class Filesystem {
/**
Checks if a file exists at the provided path.
Uses `FileManager`.
Checks if a file or directory exists at the provided path.
*/
public static func fileExists(_ path: String) -> Bool {
public static func exists(_ path: String) -> Bool {
return FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
)
}
/**
Checks if a file exists at the provided path.
*/
public static func fileExists(_ path: String) -> Bool {
var isDirectory: ObjCBool = true
let exists = FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"),
isDirectory: &isDirectory
)
return exists && !isDirectory.boolValue
}
public static func fileIsSymlink(_ path: String) -> Bool {
do {
let attribs = try FileManager.default.attributesOfItem(atPath: path)
return attribs[.type] as! FileAttributeType == FileAttributeType.typeSymbolicLink
} catch {
return false
}
}
/**
Checks if a directory exists at the provided path.
*/
public static func directoryExists(_ path: String) -> Bool {
var isDirectory: ObjCBool = true
let exists = FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"),
isDirectory: &isDirectory
)
return exists && isDirectory.boolValue
}
}

View File

@ -10,7 +10,7 @@ import UserNotifications
class LocalNotification {
public static func send(title: String, subtitle: String, preference: PreferenceName) {
@MainActor public static func send(title: String, subtitle: String, preference: PreferenceName) {
if !Preferences.isEnabled(preference) {
return
}

View File

@ -16,8 +16,18 @@ class PhpHelper {
// Take the PHP version (e.g. "7.2") and generate a dotless version
let dotless = version.replacingOccurrences(of: ".", with: "")
// Determine the dotless name for this PHP version
let destination = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
// Check if the ~/.config/phpmon/bin directory is in the PATH
let inPath = Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin")
// Check if we can create symlinks (`/usr/local/bin` must be writable)
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
do {
let destination = "/usr/local/bin/pm\(dotless)"
Shell.run("mkdir -p ~/.config/phpmon/bin")
if FileManager.default.fileExists(atPath: destination) {
let contents = try String(contentsOfFile: destination)
if !contents.contains(keyPhrase) {
@ -52,10 +62,40 @@ class PhpHelper {
// Make sure the file is executable
Shell.run("chmod +x \(destination)")
// Create a symlink if the folder is not in the PATH
if !inPath {
// First, check if we can create symlinks at all
if !canWriteSymlinks {
Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.")
return
}
// Write the symlink
self.createSymlink(dotless)
}
} catch {
print(error)
Log.err("Could not write PHP Monitor helper for PHP \(version) to /usr/local/bin/pm\(dotless)")
Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))")
}
}
private static func createSymlink(_ dotless: String) {
let source = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
let destination = "/usr/local/bin/pm\(dotless)"
if !Filesystem.fileExists(destination) {
Log.info("Creating new symlink: \(destination)")
Shell.run("ln -s \(source) \(destination)")
return
}
if !Filesystem.fileIsSymlink(destination) {
Log.info("Overwriting existing file with new symlink: \(destination)")
Shell.run("ln -fs \(source) \(destination)")
return
}
Log.info("Symlink in \(destination) already exists, OK.")
}
}

View File

@ -13,9 +13,9 @@
</head>
<body>
<br>
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
<p><b>Do you enjoy using the app?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ</a> section, I did my best to ensure everything is documented.</p>
<p><b>Want to support further development of PHP Monitor?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
<p><b>Get the latest on Twitter</b> Give me a <a href="https://twitter.com/nicoverbruggen">follow on Twitter</a> to learn about the latest and greatest updates of this app.</p>
<br>
</body>

View File

@ -51,10 +51,16 @@ class App {
var preferences: [PreferenceName: Bool]!
/** The window controller of the currently active preferences window. */
var preferencesWindowController: PrefsWC?
var preferencesWindowController: PreferencesWindowController?
/** The window controller of the currently active site list window. */
var domainListWindowController: DomainListWC?
var domainListWindowController: DomainListWindowController?
/** The window controller of the onboarding window. */
var onboardingWindowController: OnboardingWindowController?
/** The window controller of the warnings window. */
var warningsWindowController: WarningsWindowController?
/** List of detected (installed) applications that PHP Monitor can work with. */
var detectedApplications: [Application] = []
@ -62,6 +68,9 @@ class App {
/** The services manager, responsible for figuring out what services are active/inactive. */
var services = ServicesManager.shared
/** The warning manager, responsible for keeping track of warnings. */
var warnings = WarningManager.shared
/** Timer that will periodically reload info about the user's PHP installation. */
var timer: Timer?

View File

@ -161,14 +161,14 @@ class AppUpdateChecker {
private static func notifyAboutConnectionIssue() {
DispatchQueue.main.async {
BetterAlert().withInformation(
title: "updater.errors.cannot_check_for_update.title".localized,
subtitle: "updater.errors.cannot_check_for_update.subtitle".localized,
description: "updater.errors.cannot_check_for_update.description".localized(
title: "updater.alerts.cannot_check_for_update.title".localized,
subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized,
description: "updater.alerts.cannot_check_for_update.description".localized(
App.version
)
)
.withTertiary(
text: "updater.errors.buttons.releases_on_github".localized,
text: "updater.alerts.buttons.releases_on_github".localized,
action: { _ in
NSWorkspace.shared.open(Constants.Urls.GitHubReleases)
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
@ -325,7 +324,7 @@
<scene sceneID="PQa-AT-b2a">
<objects>
<customObject id="OF0-qs-3Oh" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<windowController storyboardIdentifier="preferencesWindow" showSeguePresentationStyle="single" id="hLJ-Fd-wRr" customClass="PrefsWC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<windowController storyboardIdentifier="preferencesWindow" showSeguePresentationStyle="single" id="hLJ-Fd-wRr" customClass="PreferencesWindowController" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="h4c-3b-nko">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -399,7 +398,7 @@
<!--Window Controller-->
<scene sceneID="4XS-kY-YIS">
<objects>
<windowController storyboardIdentifier="domainListWindow" id="8Ec-9q-82s" customClass="DomainListWC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<windowController storyboardIdentifier="domainListWindow" id="8Ec-9q-82s" customClass="DomainListWindowController" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" separatorStyle="line" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="raw-02-3Q1">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -819,7 +818,7 @@ Gw
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="6IL-DW-37w">
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" multipleSelection="NO" autosaveName="phpmon-sitelist-columns" rowHeight="54" headerView="xUg-Mq-OSh" viewBased="YES" id="cp3-34-pQj">
<rect key="frame" x="0.0" y="0.0" width="626" height="281"/>

View File

@ -0,0 +1,45 @@
//
// EnvironmentCheck.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 10/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary.
Checks that require an app restart will always lead to an alert and app termination shortly after.
*/
struct EnvironmentCheck {
let command: () async -> Bool
let name: String
let titleText: String
let subtitleText: String
let descriptionText: String
let buttonText: String
let requiresAppRestart: Bool
init(
command: @escaping () async -> Bool,
name: String,
titleText: String,
subtitleText: String,
descriptionText: String = "",
buttonText: String = "OK",
requiresAppRestart: Bool = false
) {
self.command = command
self.name = name
self.titleText = titleText
self.subtitleText = subtitleText
self.descriptionText = descriptionText
self.buttonText = buttonText
self.requiresAppRestart = requiresAppRestart
}
public func succeeds() async -> Bool {
return await !self.command()
}
}

View File

@ -56,12 +56,14 @@ class InterApp {
if PhpEnv.shared.availablePhpVersions.contains(version) {
MainMenu.shared.switchToPhpVersion(version)
} else {
BetterAlert().withInformation(
title: "alert.php_switch_unavailable.title".localized,
subtitle: "alert.php_switch_unavailable.subtitle".localized(version)
).withPrimary(
text: "alert.php_switch_unavailable.ok".localized
).show()
DispatchQueue.main.async {
BetterAlert().withInformation(
title: "alert.php_switch_unavailable.title".localized,
subtitle: "alert.php_switch_unavailable.subtitle".localized(version)
).withPrimary(
text: "alert.php_switch_unavailable.ok".localized
).show()
}
}
})
]}

View File

@ -167,6 +167,18 @@ class Startup {
descriptionText: "startup.errors.services_json_error.desc".localized
),
// =================================================================================
// Determine that Valet is installed
// =================================================================================
EnvironmentCheck(
command: {
return !Filesystem.directoryExists("~/.config/valet")
},
name: "`.config/valet` not empty (Valet installed)",
titleText: "startup.errors.valet_not_installed.title".localized,
subtitleText: "startup.errors.valet_not_installed.subtitle".localized,
descriptionText: "startup.errors.valet_not_installed.desc".localized
),
// =================================================================================
// Determine that the Valet configuration JSON file is valid.
// =================================================================================
EnvironmentCheck(
@ -217,8 +229,19 @@ class Startup {
EnvironmentCheck(
command: {
let output = valet("--version", sudo: false)
// Failure condition #1: does not contain Laravel Valet
if !output.contains("Laravel Valet") {
return true
}
// Failure condition #2: version cannot be parsed
let versionString = output
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "Laravel Valet")[1]
.trimmingCharacters(in: .whitespaces)
// Extract the version number
Valet.shared.version = VersionExtractor.from(output)
return Valet.shared.version == nil && output.contains("Laravel Valet")
// Get the actual version
return Valet.shared.version == nil
},
name: "`valet --version` was loaded",
titleText: "startup.errors.valet_version_unknown.title".localized,
@ -226,42 +249,4 @@ class Startup {
descriptionText: "startup.errors.valet_version_unknown.desc".localized
)
]
// MARK: - EnvironmentCheck struct
/**
The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary.
Checks that require an app restart will always lead to an alert and app termination shortly after.
*/
struct EnvironmentCheck {
let command: () async -> Bool
let name: String
let titleText: String
let subtitleText: String
let descriptionText: String
let buttonText: String
let requiresAppRestart: Bool
init(
command: @escaping () async -> Bool,
name: String,
titleText: String,
subtitleText: String,
descriptionText: String = "",
buttonText: String = "OK",
requiresAppRestart: Bool = false
) {
self.command = command
self.name = name
self.titleText = titleText
self.subtitleText = subtitleText
self.descriptionText = descriptionText
self.buttonText = buttonText
self.requiresAppRestart = requiresAppRestart
}
public func succeeds() async -> Bool {
return await !self.command()
}
}
}

View File

@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
let path = pathControl.url!.path
let name = inputDomainName.stringValue
if !FileManager.default.fileExists(atPath: path) {
if !Filesystem.exists(path) {
Alert.confirm(
onWindow: view.window!,
messageText: "domain_list.alert.folder_missing.title".localized,

View File

@ -64,17 +64,16 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
let windowController = storyboard.instantiateController(
withIdentifier: "domainListWindow"
) as! DomainListWC
) as! DomainListWindowController
windowController.window!.title = "domain_list.title".localized
windowController.window!.subtitle = "domain_list.subtitle".localized
windowController.window!.delegate = delegate
windowController.window!.styleMask = [
.titled, .closable, .resizable, .miniaturizable
]
windowController.window!.minSize = NSSize(width: 550, height: 200)
windowController.window!.delegate = windowController
windowController.window!.setFrameAutosaveName("domainListWindow")
guard let window = windowController.window else { return }
window.title = "domain_list.title".localized
window.subtitle = "domain_list.subtitle".localized
window.delegate = delegate ?? windowController
window.styleMask = [.titled, .closable, .resizable, .miniaturizable]
window.minSize = NSSize(width: 550, height: 200)
window.setFrameAutosaveName("domainListWindow")
App.shared.domainListWindowController = windowController
}

View File

@ -1,5 +1,5 @@
//
// DomainListWC.swift
// DomainListWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 03/12/2021.
@ -8,7 +8,7 @@
import Cocoa
class DomainListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate {
class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate {
// MARK: - Window Identifier
@ -124,8 +124,6 @@ class DomainListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate
withIdentifier: "addProxyWindow"
) as! NSWindowController
// let viewController = windowController.window!.contentViewController as! AddSiteVC
self.window?.beginSheet(windowController.window!)
}
}

View File

@ -11,7 +11,7 @@ import Cocoa
class SelectionVC: NSViewController {
weak var domainListWC: DomainListWC?
weak var domainListWC: DomainListWindowController?
@IBOutlet weak var textFieldTitle: NSTextField!
@IBOutlet weak var textFieldDescription: NSTextField!

View File

@ -13,7 +13,7 @@ class ComposerWindow {
private var menu: MainMenu?
private var shouldNotify: Bool! = nil
private var completion: ((Bool) -> Void)! = nil
private var window: ProgressWindowController?
private var window: TerminalProgressWindowController?
/**
Updates the global dependencies and runs the completion callback when done.
@ -25,7 +25,9 @@ class ComposerWindow {
Paths.shared.detectBinaryPaths()
if Paths.composer == nil {
presentMissingAlert()
DispatchQueue.main.async {
self.presentMissingAlert()
}
return
}
@ -33,7 +35,7 @@ class ComposerWindow {
menu?.setBusyImage()
menu?.rebuild()
window = ProgressWindowController.display(
window = TerminalProgressWindowController.display(
title: "alert.composer_progress.title".localized,
description: "alert.composer_progress.info".localized
)
@ -116,7 +118,7 @@ class ComposerWindow {
// MARK: Alert
private func presentMissingAlert() {
@MainActor private func presentMissingAlert() {
BetterAlert()
.withInformation(
title: "alert.composer_missing.title".localized,

View File

@ -71,7 +71,7 @@ struct PhpFrameworks {
public static func detectFallbackDependency(_ basePath: String) -> String? {
for entry in Self.FileMapping {
let found = entry.value
.map { path in return Filesystem.fileExists(basePath + path) }
.map { path in return Filesystem.exists(basePath + path) }
.contains(true)
if found {

View File

@ -53,13 +53,11 @@ extension MainMenu {
Actions.restartPhpFpm()
Actions.restartNginx()
} success: {
DispatchQueue.main.async {
LocalNotification.send(
title: "notification.services_restarted".localized,
subtitle: "notification.services_restarted_desc".localized,
preference: .notifyAboutServices
)
}
LocalNotification.send(
title: "notification.services_restarted".localized,
subtitle: "notification.services_restarted_desc".localized,
preference: .notifyAboutServices
)
}
}
@ -67,13 +65,11 @@ extension MainMenu {
asyncExecution {
Actions.stopValetServices()
} success: {
DispatchQueue.main.async {
LocalNotification.send(
title: "notification.services_stopped".localized,
subtitle: "notification.services_stopped_desc".localized,
preference: .notifyAboutServices
)
}
LocalNotification.send(
title: "notification.services_stopped".localized,
subtitle: "notification.services_stopped_desc".localized,
preference: .notifyAboutServices
)
}
}
@ -155,7 +151,7 @@ extension MainMenu {
}
}
@objc func rollbackPreset() {
@MainActor @objc func rollbackPreset() {
guard let preset = PresetHelper.rollbackPreset else {
return
}
@ -180,7 +176,7 @@ extension MainMenu {
}
}
@objc func showPresetHelp() {
@MainActor @objc func showPresetHelp() {
BetterAlert().withInformation(
title: "preset_help_title".localized,
subtitle: "preset_help_info".localized,

View File

@ -36,8 +36,8 @@ extension MainMenu {
*/
func asyncExecution(
_ execute: @escaping () throws -> Void,
success: @escaping () -> Void = {},
failure: @escaping (Error) -> Void = { _ in },
success: @MainActor @escaping () -> Void = {},
failure: @MainActor @escaping (Error) -> Void = { _ in },
behaviours: [AsyncBehaviour] = [
.setsBusyUI,
.reloadsPhpInstallation,

View File

@ -11,7 +11,7 @@ import AppKit
extension MainMenu {
@objc func fixMyValet() {
@MainActor @objc func fixMyValet() {
let previousVersion = PhpEnv.phpInstall.version.short
if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) {
@ -42,7 +42,7 @@ extension MainMenu {
}
}
private func presentAlertForMissingFormula() {
@MainActor private func presentAlertForMissingFormula() {
BetterAlert()
.withInformation(
title: "alert.php_formula_missing.title".localized,
@ -52,7 +52,7 @@ extension MainMenu {
.show()
}
private func presentAlertForSameVersion() {
@MainActor private func presentAlertForSameVersion() {
BetterAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,
@ -63,7 +63,7 @@ extension MainMenu {
.show()
}
private func presentAlertForDifferentVersion(version: String) {
@MainActor private func presentAlertForDifferentVersion(version: String) {
BetterAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,

View File

@ -57,6 +57,9 @@ extension MainMenu {
let installation = PhpEnv.phpInstall
installation.notifyAboutBrokenPhpFpm()
// Check for other problems
WarningManager.shared.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching)
Log.info("Setting up watchers...")
App.shared.handlePhpConfigWatcher()
@ -79,6 +82,7 @@ extension MainMenu {
// A non-default TLD is not officially supported since Valet 3.2.x
Valet.notifyAboutUnsupportedTLD()
// Find out which services are active
ServicesManager.shared.loadData()
// Start the background refresh timer
@ -88,6 +92,14 @@ extension MainMenu {
Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
// Present first launch screen if needed
if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview {
Log.info("Should present the first launch screen!")
DispatchQueue.main.async {
OnboardingWindowController.show()
}
}
// Check for updates
DispatchQueue.global(qos: .utility).async {
AppUpdateChecker.checkIfNewerVersionIsAvailable()

View File

@ -52,14 +52,14 @@ extension MainMenu {
}
}
private func checkForPlatformIssues() {
@MainActor private func checkForPlatformIssues() {
if Valet.shared.hasPlatformIssues() {
Log.info("Composer platform issue(s) detected.")
self.suggestFixMyComposer()
}
}
private func suggestFixMyValet(failed version: String) {
@MainActor private func suggestFixMyValet(failed version: String) {
let outcome = BetterAlert()
.withInformation(
title: "alert.php_switch_failed.title".localized(version),
@ -74,7 +74,7 @@ extension MainMenu {
}
}
private func suggestFixMyComposer() {
@MainActor private func suggestFixMyComposer() {
BetterAlert().withInformation(
title: "alert.global_composer_platform_issues.title".localized,
subtitle: "alert.global_composer_platform_issues.subtitle".localized,
@ -111,12 +111,14 @@ extension MainMenu {
}
private func notifyAboutVersionChange(to version: String) {
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version),
preference: .notifyAboutVersionChange
)
DispatchQueue.main.async {
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version),
preference: .notifyAboutVersionChange
)
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
PhpEnv.phpInstall.notifyAboutBrokenPhpFpm()
}
}
}

View File

@ -21,7 +21,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
/**
The status bar item with variable length.
*/
let statusItem = NSStatusBar.system.statusItem(
@MainActor let statusItem = NSStatusBar.system.statusItem(
withLength: NSStatusItem.variableLength
)
@ -77,7 +77,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
/**
Sets the status bar image based on a version string.
*/
func setStatusBarImage(version: String) {
@MainActor func setStatusBarImage(version: String) {
setStatusBar(
image: (Preferences.preferences[.iconTypeToDisplay] as! String != MenuBarIcon.noIcon.rawValue)
? MenuBarImageGenerator.textToImageWithIcon(text: version)
@ -89,7 +89,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
Sets the status bar image, based on the provided NSImage.
The image will be used as a template image.
*/
func setStatusBar(image: NSImage) {
@MainActor func setStatusBar(image: NSImage) {
if let button = statusItem.button {
image.isTemplate = true
button.image = image
@ -125,6 +125,16 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
ServicesManager.shared.loadData()
}
/**
Shows the Welcome Tour screen, again.
Did this need a comment? No, probably not.
*/
@objc func showWelcomeTour() {
DispatchQueue.main.async {
OnboardingWindowController.show()
}
}
/** Reloads the menu in the background, using `asyncExecution`. */
@objc func reloadPhpMonitorMenuInBackground() {
asyncExecution({
@ -171,7 +181,11 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
}
@objc func openPrefs() {
PrefsWC.show()
PreferencesWindowController.show()
}
@objc func openWarnings() {
WarningsWindowController.show()
}
@objc func openDomainList() {

View File

@ -157,6 +157,7 @@ extension StatusMenu {
return
}
self.addItem(NSMenuItem.separator())
let xdebugSwitch = NSMenuItem(
title: "mi_xdebug_mode".localized,
action: nil,
@ -199,6 +200,14 @@ extension StatusMenu {
let servicesMenu = NSMenu()
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized))
servicesMenu.addItem(NSMenuItem(title: "mi_view_onboarding".localized,
action: #selector(MainMenu.showWelcomeTour), keyEquivalent: ""))
servicesMenu.addItem(NSMenuItem(title: "mi_fa_php_doctor".localized,
action: #selector(MainMenu.openWarnings), keyEquivalent: ""))
servicesMenu.addItem(NSMenuItem.separator())
let fixMyValetMenuItem = NSMenuItem(
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet), keyEquivalent: ""
@ -216,22 +225,15 @@ extension StatusMenu {
servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized))
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_dnsmasq".localized,
action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_php_fpm".localized,
action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_nginx".localized,
action: #selector(MainMenu.restartNginx), keyEquivalent: "n")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_valet_services".localized,
action: #selector(MainMenu.restartValetServices), keyEquivalent: "s")
)
servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized,
action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized,
action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized,
action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_valet_services".localized,
action: #selector(MainMenu.restartValetServices), keyEquivalent: "s"))
servicesMenu.addItem(
NSMenuItem(title: "mi_stop_valet_services".localized,
action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"),

View File

@ -66,16 +66,40 @@ class StatusMenu: NSMenu {
self.addExtensionsMenuItems()
self.addXdebugMenuItem()
self.addPhpDoctorMenuItem()
self.addItem(NSMenuItem.separator())
self.addXdebugMenuItem()
self.addPresetsMenuItem()
self.addFirstAidAndServicesMenuItems()
}
func addPhpDoctorMenuItem() {
if !Preferences.isEnabled(.showPhpDoctorSuggestions) ||
!WarningManager.shared.hasWarnings() {
return
}
self.addItem(NSMenuItem.separator())
self.addItem(HeaderView.asMenuItem(text: "mi_php_doctor".localized))
self.addItem(NSMenuItem(
title: "mi_recommendations_count".localized(WarningManager.shared.warnings.count),
action: nil,
keyEquivalent: ""
))
self.addItem(NSMenuItem(
title: "mi_view_recommendations".localized,
action: #selector(MainMenu.openWarnings),
keyEquivalent: ""
))
}
func addCoreMenuItems() {
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_preferences".localized,
action: #selector(MainMenu.openPrefs), keyEquivalent: ","))
self.addItem(NSMenuItem(title: "mi_check_for_updates".localized,

View File

@ -103,14 +103,14 @@ class BetterAlert {
/**
Shows the modal and does not return anything.
*/
public func show() {
@MainActor public func show() {
_ = self.runModal()
}
/**
Shows the modal for a particular error.
*/
public static func show(for error: Error & AlertableError) {
@MainActor public static func show(for error: Error & AlertableError) {
let key = error.getErrorMessageKey()
return BetterAlert().withInformation(
title: "\(key).title".localized,

View File

@ -0,0 +1,45 @@
//
// OnboardingWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/06/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
import SwiftUI
class OnboardingWindowController: PMWindowController {
// MARK: - Window Identifier
override var windowName: String {
return "Onboarding"
}
public static func create(delegate: NSWindowDelegate?) {
let windowController = Self()
windowController.window = NSWindow()
guard let window = windowController.window else { return }
window.title = ""
window.styleMask = [.titled, .closable, .miniaturizable]
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: OnboardingView())
window.setContentSize(NSSize(width: 600, height: 600))
App.shared.onboardingWindowController = windowController
}
public static func show(delegate: NSWindowDelegate? = nil) {
if App.shared.onboardingWindowController == nil {
Self.create(delegate: delegate)
}
App.shared.onboardingWindowController?.showWindow(self)
App.shared.onboardingWindowController?.window?.setCenterPosition(offsetY: 70)
NSApp.activate(ignoringOtherApps: true)
}
}

View File

@ -12,6 +12,7 @@ struct CustomPrefs: Decodable {
let scanApps: [String]
let presets: [Preset]?
let services: [String]?
let environmentVariables: [String: String]?
public func hasPresets() -> Bool {
return self.presets != nil && !self.presets!.isEmpty
@ -21,9 +22,20 @@ struct CustomPrefs: Decodable {
return self.services != nil && !self.services!.isEmpty
}
public func hasEnvironmentVariables() -> Bool {
return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty
}
public func getEnvironmentVariables() -> String {
return self.environmentVariables!.map { (key, value) in
return "export \(key)=\(value)"
}.joined(separator: "&&")
}
private enum CodingKeys: String, CodingKey {
case scanApps = "scan_apps"
case presets = "presets"
case services = "services"
case environmentVariables = "export"
}
}

View File

@ -0,0 +1,14 @@
//
// Keys.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/07/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
struct Keys {
static let Escape = 53
static let Space = 49
}

View File

@ -21,6 +21,7 @@ enum PreferenceName: String {
case allowProtocolForIntegrations = "allow_protocol_for_integrations"
case globalHotkey = "global_hotkey"
case automaticBackgroundUpdateCheck = "backgroundUpdateCheck"
case showPhpDoctorSuggestions = "show_php_doctor_suggestions"
// APPEARANCE
case shouldDisplayDynamicIcon = "use_dynamic_icon"
@ -65,7 +66,7 @@ class Preferences {
public init() {
Preferences.handleFirstTimeLaunch()
cachedPreferences = Self.cache()
customPreferences = CustomPrefs(scanApps: [], presets: [], services: [])
customPreferences = CustomPrefs(scanApps: [], presets: [], services: [], environmentVariables: [:])
loadCustomPreferences()
}
@ -88,6 +89,7 @@ class Preferences {
PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue: false,
PreferenceName.allowProtocolForIntegrations.rawValue: true,
PreferenceName.automaticBackgroundUpdateCheck.rawValue: true,
PreferenceName.showPhpDoctorSuggestions.rawValue: true,
/// Preferences: Appearance
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,
@ -174,6 +176,8 @@ class Preferences {
forKey: PreferenceName.allowProtocolForIntegrations.rawValue) as Any,
.automaticBackgroundUpdateCheck: UserDefaults.standard.bool(
forKey: PreferenceName.automaticBackgroundUpdateCheck.rawValue) as Any,
.showPhpDoctorSuggestions: UserDefaults.standard.bool(
forKey: PreferenceName.showPhpDoctorSuggestions.rawValue) as Any,
.notifyAboutVersionChange: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutVersionChange.rawValue) as Any,
@ -252,6 +256,11 @@ class Preferences {
if customPreferences.hasServices() {
Log.info("There are custom services: \(customPreferences.services!)")
}
if customPreferences.hasEnvironmentVariables() {
Log.info("Configuring the additional exports...")
Shell.user.exports = customPreferences.getEnvironmentVariables()
}
} catch {
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
}

View File

@ -0,0 +1,38 @@
//
// PreferencesWindowController+Hotkey.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 25/07/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
extension PreferencesWindowController {
// MARK: - Key Interaction
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
guard let tabVC = self.contentViewController as? NSTabViewController else {
return
}
guard let vc = tabVC.tabViewItems[tabVC.selectedTabViewItemIndex].viewController as? GenericPreferenceVC else {
return
}
if vc.listeningForHotkeyView == nil {
return
}
if event.keyCode == Keys.Escape || event.keyCode == Keys.Space {
Log.info("A blacklisted key was pressed, canceling listen!")
vc.listeningForHotkeyView!.unregister(nil)
} else {
vc.listeningForHotkeyView!.updateShortcut(event)
}
}
}

View File

@ -1,5 +1,5 @@
//
// PrefsWC.swift
// PreferencesWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 02/04/2021.
@ -8,12 +8,7 @@
import Cocoa
struct Keys {
static let Escape = 53
static let Space = 49
}
class PrefsWC: PMWindowController {
class PreferencesWindowController: PMWindowController {
// MARK: - Window Identifier
@ -26,13 +21,14 @@ class PrefsWC: PMWindowController {
let windowController = storyboard.instantiateController(
withIdentifier: "preferencesWindow"
) as! PrefsWC
) as! PreferencesWindowController
windowController.window!.title = "prefs.title".localized
windowController.window!.subtitle = "prefs.subtitle".localized
windowController.window!.delegate = delegate
windowController.window!.styleMask = [.titled, .closable, .miniaturizable]
windowController.window!.delegate = windowController
guard let window = windowController.window else { return }
window.title = "prefs.title".localized
window.subtitle = "prefs.subtitle".localized
window.delegate = delegate ?? windowController
window.styleMask = [.titled, .closable, .miniaturizable]
App.shared.preferencesWindowController = windowController
}
@ -54,7 +50,7 @@ class PrefsWC: PMWindowController {
for vc in preferencesWC.tabVCs {
tabVC.addChild(vc.viewController)
let item = tabVC.tabViewItem(for: vc.viewController)
item?.image = NSImage(systemSymbolName: vc.icon, accessibilityDescription: "")
item?.image = NSImage(systemSymbolName: vc.icon, accessibilityDescription: "\(vc.label) Icon")
item?.label = vc.label
}
@ -75,6 +71,8 @@ class PrefsWC: PMWindowController {
NSApp.activate(ignoringOtherApps: true)
}
// MARK: - Tabs
struct PrefTabView {
let viewController: GenericPreferenceVC
let label: String
@ -101,29 +99,4 @@ class PrefsWC: PMWindowController {
]
}()
// MARK: - Key Interaction
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
guard let tabVC = self.contentViewController as? NSTabViewController else {
return
}
guard let selected = tabVC.tabViewItems[tabVC.selectedTabViewItemIndex].viewController else {
return
}
if let vc = selected as? GenericPreferenceVC {
if vc.listeningForHotkeyView != nil {
if event.keyCode == Keys.Escape || event.keyCode == Keys.Space {
Log.info("A blacklisted key was pressed, canceling listen!")
vc.listeningForHotkeyView!.unregister(nil)
} else {
vc.listeningForHotkeyView!.updateShortcut(event)
}
}
}
}
}

View File

@ -114,6 +114,20 @@ class GenericPreferenceVC: NSViewController {
)
}
func getShowPhpDoctorSuggestionsPV() -> NSView {
return CheckboxPreferenceView.make(
sectionText: "prefs.php_doctor".localized,
descriptionText: "prefs.php_doctor_suggestions_desc".localized,
checkboxText: "prefs.php_doctor_suggestions_title".localized,
preference: .showPhpDoctorSuggestions,
action: {
MainMenu.shared.refreshIcon()
MainMenu.shared.rebuild()
}
)
}
func getNotifyAboutVersionChangePV() -> NSView {
return CheckboxPreferenceView.make(
sectionText: "prefs.notifications".localized,
@ -194,6 +208,7 @@ class GeneralPreferencesVC: GenericPreferenceVC {
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
vc.views = [
vc.getShowPhpDoctorSuggestionsPV(),
vc.getAutoRestartPV(),
vc.getAutomaticComposerUpdatePV(),
vc.getShortcutPV(),

View File

@ -107,13 +107,13 @@ struct Preset: Codable, Equatable {
// Show the correct notification
if wasRollback {
LocalNotification.send(
await LocalNotification.send(
title: "notification.preset_reverted_title".localized,
subtitle: "notification.preset_reverted_desc".localized,
preference: .notifyAboutPresets
)
} else {
LocalNotification.send(
await LocalNotification.send(
title: "notification.preset_applied_title".localized,
subtitle: "notification.preset_applied_desc".localized(self.name),
preference: .notifyAboutPresets

View File

@ -0,0 +1,24 @@
//
// ProgressVC.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 26/07/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import AppKit
class ProgressViewController: NSViewController {
@IBOutlet weak var labelTitle: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet var textView: NSTextView!
@IBOutlet weak var imageViewType: NSImageView!
deinit {
Log.perf("Deinitializing ProgressViewController")
}
}

View File

@ -1,15 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="pUZ-6w-gUX">
<objects>
<windowController storyboardIdentifier="progressWindow" id="LSr-Iw-X1T" customClass="ProgressWindowController" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<windowController storyboardIdentifier="progressWindow" id="LSr-Iw-X1T" customClass="TerminalProgressWindowController" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="none" frameAutosaveName="" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="PD9-0p-i0S" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES"/>
<windowPositionMask key="initialPositionMask" rightStrut="YES" topStrut="YES"/>
@ -43,7 +42,7 @@
<rect key="frame" x="0.0" y="0.0" width="591" height="210"/>
<clipView key="contentView" drawsBackground="NO" id="2Mc-oy-AzN">
<rect key="frame" x="0.0" y="0.0" width="591" height="210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" smartInsertDelete="YES" id="d1T-N1-CRe">
<rect key="frame" x="0.0" y="0.0" width="591" height="210"/>
@ -65,11 +64,11 @@
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="51v-CN-AuA">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="51v-CN-AuA">
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="lSO-JG-QOf">
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="lSO-JG-QOf">
<rect key="frame" x="-100" y="-100" width="15" height="173"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>

View File

@ -1,5 +1,5 @@
//
// ProgressView.swift
// TerminalProgressWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 18/12/2021.
@ -9,14 +9,14 @@
import Foundation
import AppKit
class ProgressWindowController: NSWindowController, NSWindowDelegate {
class TerminalProgressWindowController: NSWindowController, NSWindowDelegate {
static func display(title: String, description: String) -> ProgressWindowController {
static func display(title: String, description: String) -> TerminalProgressWindowController {
let storyboard = NSStoryboard(name: "ProgressWindow", bundle: nil)
let windowController = storyboard.instantiateController(
withIdentifier: "progressWindow"
) as! ProgressWindowController
) as! TerminalProgressWindowController
windowController.showWindow(windowController)
windowController.window?.makeKeyAndOrderFront(nil)
@ -60,17 +60,3 @@ class ProgressWindowController: NSWindowController, NSWindowDelegate {
}
}
class ProgressViewController: NSViewController {
@IBOutlet weak var labelTitle: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet var textView: NSTextView!
@IBOutlet weak var imageViewType: NSImageView!
deinit {
Log.perf("Deinitializing ProgressViewController")
}
}

View File

@ -56,7 +56,9 @@ struct ServicesView: View {
}.frame(minWidth: 0, maxWidth: .infinity)
} else {
// Empty cell
VStack { EmptyView() }.frame(minWidth: 0, maxWidth: .infinity)
VStack {
EmptyView()
}.frame(minWidth: 0, maxWidth: .infinity)
}
}
}

View File

@ -0,0 +1,127 @@
//
// OnboardingView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 08/07/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct OnboardingTextItem: View {
@State var icon: String
@State var title: String
@State var description: String
var body: some View {
HStack(alignment: .top, spacing: 5) {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.foregroundColor(Color.appPrimary)
.padding(.trailing, 10)
VStack(alignment: .leading, spacing: 4) {
Text(title.localizedForSwiftUI)
.font(.system(size: 14))
.lineLimit(3)
Text(description.localizedForSwiftUI)
.foregroundColor(Color.secondary)
.font(.system(size: 13))
.lineLimit(4)
.fixedSize(horizontal: false, vertical: true)
.frame(minWidth: 0, maxWidth: 800, alignment: .leading)
}
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray.opacity(0.3), lineWidth: 1))
}
}
struct OnboardingView: View {
var body: some View {
VStack(alignment: .center, spacing: 5) {
HStack {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 80, height: 80)
.padding(.bottom, 5)
.padding(.trailing, 25)
VStack(alignment: .leading, spacing: 0) {
Text("onboarding.welcome".localized)
.font(.title)
.bold()
.padding(.bottom, 5)
Text("onboarding.explore".localized)
.padding(.bottom)
.padding(.trailing)
}
.padding(.top, 10)
}
.padding(.leading)
.padding(.trailing)
VStack {
VStack(alignment: .leading, spacing: 10) {
OnboardingTextItem(
icon: "bolt.circle.fill",
title: "onboarding.tour.menu_bar.title",
description: "onboarding.tour.menu_bar"
)
OnboardingTextItem(
icon: "checkmark.circle.fill",
title: "onboarding.tour.services.title",
description: "onboarding.tour.services"
)
OnboardingTextItem(
icon: "list.bullet.circle.fill",
title: "onboarding.tour.domains.title",
description: "onboarding.tour.domains"
)
OnboardingTextItem(
icon: "pin.circle.fill",
title: "onboarding.tour.isolation.title",
description: "onboarding.tour.isolation"
)
}
}.padding()
VStack(spacing: 20) {
HStack {
Image(systemName: "questionmark.circle.fill")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(Color.appSecondary)
.padding(.trailing, 10)
HStack {
Text("onboarding.tour.faq_hint".localizedForSwiftUI)
.lineLimit(5)
}.fixedSize(horizontal: false, vertical: true)
}
VStack {
Text("onboarding.tour.once".localized)
.font(.subheadline)
.foregroundColor(.gray)
.padding(.top, 5)
.padding(.bottom, 5)
.lineLimit(5)
Button("onboarding.tour.close".localized) {
App.shared.onboardingWindowController?.close()
}
}
}
.padding(.leading)
.padding(.trailing)
}
}
}
struct OnboardingView_Previews: PreviewProvider {
static var previews: some View {
Group {
OnboardingView()
OnboardingView().preferredColorScheme(.dark)
}
}
}

View File

@ -0,0 +1,33 @@
//
// NoWarningsView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct NoWarningsView: View {
var body: some View {
VStack(alignment: .center, spacing: 15) {
Image(systemName: "checkmark.circle.fill")
.resizable()
.renderingMode(.template)
.foregroundColor(Color.green)
.frame(width: 24, height: 24)
VStack(alignment: .center) {
Text("warnings.none".localizedForSwiftUI)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(25)
}
}
struct NoWarningsView_Previews: PreviewProvider {
static var previews: some View {
NoWarningsView()
}
}

View File

@ -0,0 +1,91 @@
//
// WarningListView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 09/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct WarningListView: View {
@State var warnings: [Warning]
init(empty: Bool = false) {
self.warnings = empty ? [] : WarningManager.shared.warnings
}
var body: some View {
VStack {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "stethoscope.circle.fill")
.resizable()
.frame(width: 40, height: 40)
.foregroundColor(Color.red)
.padding(12)
VStack(alignment: .leading, spacing: 5) {
Text("warnings.description".localizedForSwiftUI)
.font(.system(size: 12))
.frame(maxWidth: .infinity, alignment: .leading)
Text("warnings.disclaimer".localizedForSwiftUI)
.font(.system(size: 12))
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(10)
Divider()
HStack(alignment: .center, spacing: 15) {
Button("warnings.refresh.button".localizedForSwiftUI) {
Task {
await WarningManager.shared.checkEnvironment()
self.warnings = WarningManager.shared.warnings
}
}
Text("warnings.refresh.button.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
}
.padding(10)
List {
VStack(alignment: .leading, spacing: 0) {
if warnings.isEmpty {
NoWarningsView()
} else {
ForEach(warnings) { warning in
Group {
WarningView(
title: warning.title,
paragraphs: warning.paragraphs,
documentationUrl: warning.url
)
.fixedSize(horizontal: false, vertical: true)
Divider()
}.padding(5)
}
}
}.frame(minHeight: 0, maxHeight: .infinity).padding(5)
}
.listRowInsets(EdgeInsets())
.listStyle(.plain)
.frame(maxHeight: .infinity, alignment: .top)
}
}
}
struct WarningListView_Previews: PreviewProvider {
static var previews: some View {
WarningListView(empty: true)
.frame(width: 600, height: 480)
/*
WarningListView()
// TODO: Figure out how the empty() only applies to this single instance
// .empty()
.frame(width: 600, height: 480)
*/
}
}

View File

@ -0,0 +1,69 @@
//
// WarningView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 31/07/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct WarningView: View {
@State var title: String
@State var paragraphs: [String]
@State var documentationUrl: String?
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "bandage.fill")
.resizable()
.frame(width: 18, height: 18)
.foregroundColor(Color.orange)
.padding(.trailing, 5)
VStack(alignment: .leading, spacing: 15) {
VStack(alignment: .leading, spacing: 10) {
Text(title.localizedForSwiftUI)
.fontWeight(.bold)
ForEach(paragraphs, id: \.self) { paragraph in
Text(paragraph.localizedForSwiftUI)
.font(.system(size: 13))
}
}
.fixedSize(horizontal: false, vertical: false)
.frame(
minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity,
alignment: .topLeading
)
if documentationUrl != nil {
Button("Learn More") {
NSWorkspace.shared.open(URL(string: documentationUrl!)!)
}
}
}
}.padding(5)
}
}
}
struct WarningView_Previews: PreviewProvider {
static var previews: some View {
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.frame(width: 600, height: 105)
WarningView(
title: "warnings.helper_permissions.title",
paragraphs: ["warnings.helper_permissions.description"],
documentationUrl: "https://nicoverbruggen.be"
)
.preferredColorScheme(.dark)
.frame(width: 600, height: 105)
// WarningListView().frame(width: 600, height: 580)
}
}

View File

@ -0,0 +1,36 @@
//
// Warning.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 09/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
struct Warning: Identifiable {
var id = UUID()
let command: () async -> Bool
let name: String
let title: String
let paragraphs: [String]
let url: String?
init(
command: @escaping () async -> Bool,
name: String,
title: String,
paragraphs: [String],
url: String?
) {
self.command = command
self.name = name
self.title = title
self.paragraphs = paragraphs
self.url = url
}
public func applies() async -> Bool {
return await self.command()
}
}

View File

@ -0,0 +1,85 @@
//
// WarningManager.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 09/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class WarningManager {
static var shared = WarningManager()
init() {
if isRunningSwiftUIPreview {
self.warnings = self.evaluations
}
}
public let evaluations: [Warning] = [
Warning(
command: {
return Shell.pipe("sysctl -n sysctl.proc_translated")
.trimmingCharacters(in: .whitespacesAndNewlines) == "1"
},
name: "Running PHP Monitor with Rosetta on M1",
title: "warnings.arm_compatibility.title",
paragraphs: ["warnings.arm_compatibility.description"],
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon"
),
Warning(
command: {
return !Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") &&
!FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
},
name: "Helpers cannot be symlinked and not in PATH",
title: "warnings.helper_permissions.title",
paragraphs: [
"warnings.helper_permissions.description",
"warnings.helper_permissions.unavailable",
"warnings.helper_permissions.symlink"
],
url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries"
)
]
@Published public var warnings: [Warning] = []
public func hasWarnings() -> Bool {
return !warnings.isEmpty
}
func evaluateWarnings() {
Task { await WarningManager.shared.checkEnvironment() }
}
/**
Checks the user's environment and checks if any special warnings apply.
*/
func checkEnvironment() async {
self.warnings = []
if ProcessInfo.processInfo.environment["EXTREME_DOCTOR_MODE"] != nil {
// For debugging purposes, we may wish to see all possible evaluations listed
self.warnings = self.evaluations
} else {
// Otherwise, loop over the actual evaluations and list the warnings
await loopOverEvaluations()
}
MainMenu.shared.rebuild()
}
private func loopOverEvaluations() async {
for check in self.evaluations {
if await check.applies() {
Log.info("[DOCTOR] \(check.name) (!)")
self.warnings.append(check)
continue
}
}
}
}

View File

@ -0,0 +1,45 @@
//
// WarningsWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 09/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
import SwiftUI
class WarningsWindowController: PMWindowController {
// MARK: - Window Identifier
override var windowName: String {
return "Warnings"
}
public static func create(delegate: NSWindowDelegate?) {
let windowController = Self()
windowController.window = NSWindow()
guard let window = windowController.window else { return }
window.title = ""
window.styleMask = [.titled, .closable, .miniaturizable]
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: WarningListView())
window.setContentSize(NSSize(width: 600, height: 480))
App.shared.warningsWindowController = windowController
}
public static func show(delegate: NSWindowDelegate? = nil) {
if App.shared.warningsWindowController == nil {
Self.create(delegate: delegate)
}
App.shared.warningsWindowController?.showWindow(self)
App.shared.warningsWindowController?.window?.setCenterPosition(offsetY: 70)
NSApp.activate(ignoringOtherApps: true)
}
}

View File

@ -51,7 +51,7 @@ class PhpConfigWatcher {
eventMask: DispatchSource.FileSystemEvent,
behaviour: FSWatcherBehaviour = .reloadsMenu
) {
if !Filesystem.fileExists(url.path) {
if !Filesystem.exists(url.path) {
Log.warn("No watcher was created for \(url.path) because the requested file does not exist.")
return
}

View File

@ -55,6 +55,11 @@
"mi_detected_extensions" = "Detected Extensions";
"mi_no_extensions_detected" = "No additional extensions detected.";
"mi_php_doctor" = "PHP Doctor";
"mi_fa_php_doctor" = "Open PHP Doctor...";
"mi_recommendations_count" = "%i Issue(s) Detected!";
"mi_view_recommendations" = "View Recommendations...";
"mi_valet" = "Laravel Valet";
"mi_domain_list" = "View Domains List...";
@ -72,6 +77,8 @@
"mi_no_presets" = "No presets available.";
"mi_set_up_presets" = "Learn more about presets...";
"mi_view_onboarding" = "Open Welcome Tour...";
"mi_xdebug_available_modes" = "Available Modes";
"mi_xdebug_actions" = "Actions";
"mi_xdebug_disable_all" = "Disable All Modes";
@ -210,6 +217,7 @@
"prefs.info_density" = "Info Density:";
"prefs.services" = "Services:";
"prefs.switcher" = "Switcher:";
"prefs.php_doctor" = "PHP Doctor:";
"prefs.integrations" = "Integrations:";
"prefs.updates" = "Updates:";
"prefs.notifications" = "Notifications:";
@ -237,6 +245,9 @@
"prefs.automatic_update_check_title" = "Automatically check for updates";
"prefs.automatic_update_check_desc" = "When checked, PHP Monitor will automatically check if there is a newer version available, and notify you if that is the case.";
"prefs.php_doctor_suggestions_title" = "Show suggestions (if applicable)";
"prefs.php_doctor_suggestions_desc" = "If you uncheck this item, no PHP Doctor suggestions will appear in PHP Monitor's menu. Keep in mind that PHP Doctor will not appear if there are no recommendations.";
"prefs.shortcut_set" = "Set global shortcut";
"prefs.shortcut_listening" = "<listening for keypress>";
@ -435,6 +446,12 @@ You can do this by running `composer global update` in your terminal. After that
"startup.errors.valet_version_unknown.subtitle" = "Parsing the output of `valet --version` failed. Make sure your Valet installation works and is up-to-date.";
"startup.errors.valet_version_unknown.desc" = "Try running `valet --version` in a terminal to find out what's going on.";
"startup.errors.valet_not_installed.title" = "Your Valet configuration directory is missing";
"startup.errors.valet_not_installed.subtitle" = "The required directory `~/.config/valet` is missing. This usually means that you forgot to run `valet install`.";
"startup.errors.valet_not_installed.desc" = "Assuming you already installed Valet via Composer, please run `valet install` to finish setting up Laravel Valet.
If you are seeing this message but are confused why this folder has gone missing, then you may want to investigate why it is gone—it shouldn't just disappear and it means your Valet installation is broken.";
/// Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
"startup.errors.sudoers_brew.subtitle" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.";
@ -497,3 +514,38 @@ You can do this by running `composer global update` in your terminal. After that
"alert.warnings.tld_issue.title" = "You are not using `.test` as the TLD for Valet.";
"alert.warnings.tld_issue.subtitle" = "Using a non-default TLD may not work correctly and is not officially supported.";
"alert.warnings.tld_issue.description" = "PHP Monitor will remain functional, but there might be issues: the app might not correctly show which domains have been secured. For optimal results, go to your Valet configuration file (config.json in the Valet directory) and change the TLD back to `test`.";
// WARNINGS
"warnings.title" = "PHP Doctor";
"warnings.description" = "**PHP Doctor** will suggest improvements to your active system configuration.";
"warnings.disclaimer" = "You may choose to hide all recommendations from the PHP Monitor menu in Preferences, but it is recommended that you deal with all actionable items.";
"warnings.refresh.button" = "Scan Again";
"warnings.refresh.button.description" = "Press this button once you've fixed an issue. This will cause PHP Monitor to re-evaluate your environment. If it's really fixed, the recommendation should disappear.";
"warnings.helper_permissions.title" = "PHP Monitors helpers are currently unavailable.";
"warnings.helper_permissions.description" = "PHP Monitor comes with various helper binaries. Using these binaries allows you to easily invoke a specific version of PHP without switching the linked PHP version.";
"warnings.helper_permissions.unavailable" = "However, these helpers are potentially *unavailable* because PHP Monitor cannot currently create or update the required symlinks.";
"warnings.helper_permissions.symlink" = "If you do not wish to make `/usr/local/bin` writable, you can add PHP Monitor's helper directory to your `PATH` variable to make this warning go away. (Click on ”Learn More” to find out how to fix this issue.)";
"warnings.arm_compatibility.title" = "You are running PHP Monitor using Rosetta on Apple Silicon, which means your PHP environment is also running via Rosetta.";
"warnings.arm_compatibility.description" = "You appear to be running an ARM-compatible version of macOS, but you are currently running PHP Monitor using Rosetta. While this will work correctly, it is recommended that you use the native version of Homebrew.";
"warnings.none" = "There are no recommendations available for you right now. You're all good!";
// ONBOARDING
"onboarding.title" = "Welcome Tour";
"onboarding.welcome" = "Welcome to PHP Monitor!";
"onboarding.explore" = "Learn more about some of the features that PHP Monitor has to offer. You can find a more comprehensive list of features on GitHub.";
"onboarding.tour.menu_bar.title" = "Power In Your Menu Bar";
"onboarding.tour.menu_bar" = "PHP Monitor lives in your menu bar. From this menu, you can access most of PHP Monitor's key functionality, including switching the globally linked PHP version, locating config files, and much more.";
"onboarding.tour.faq_hint" = "I recommend that you check out the [README](https://github.com/nicoverbruggen/phpmon/blob/main/README.md) on GitHub: it contains a comprehensive FAQ with various tips and common questions and answers.";
"onboarding.tour.services.title" = "Manage Homebrew Services";
"onboarding.tour.services" = "Once you click on the menu bar item, you can see at a glance based on the checkmarks or crosses if all of the Homebrew services are up and running. You can also click on a service to quickly toggle it.";
"onboarding.tour.domains.title" = "Manage Domains";
"onboarding.tour.domains" = "By opening the Domains window via the menu bar item, you can view which domains are linked and parked, as well as active nginx proxies.";
"onboarding.tour.isolation.title" = "Isolate Domains";
"onboarding.tour.isolation" = "If you have Valet 3 installed, you can even use domain isolation by right-clicking on a given domain in the Domains window. This allows you to pick a specific version of PHP to use for that domain, and that domain only.";
"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (under First Aid & Services).";
"onboarding.tour.close" = "Close Tour";