From 1216fe49740a9763d89a850c95094e29a39098cb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 31 Jan 2023 17:32:31 +0100 Subject: [PATCH 01/25] =?UTF-8?q?=F0=9F=90=9B=20Avoid=20duplicate=20verbos?= =?UTF-8?q?e=20mode=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Shell/RealShell.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/phpmon/Common/Shell/RealShell.swift b/phpmon/Common/Shell/RealShell.swift index 1ece815..88ed706 100644 --- a/phpmon/Common/Shell/RealShell.swift +++ b/phpmon/Common/Shell/RealShell.swift @@ -115,14 +115,12 @@ class RealShell: ShellProtocol { )! if Log.shared.verbosity == .cli { - var args = task.arguments - let last: String = "\"" + (args?.popLast() ?? "") + "\"" - let concat = [self.launchPath] + task.arguments! + [last] - let command = concat.joined(separator: " ") + var args = task.arguments ?? [] + let last = "\"" + (args.popLast() ?? "") + "\"" var log = """ <~~~~~~~~~~~~~~~~~~~~~~~ - $ \(command) + $ \(([self.launchPath] + args + [last]).joined(separator: " ")) [OUT]: \(stdOut) From 5ac4817048d763b788715627e1b8dfa55c906418 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 5 Feb 2023 18:17:07 +0100 Subject: [PATCH 02/25] =?UTF-8?q?=F0=9F=91=8C=20Add=20built=20app=20to=20.?= =?UTF-8?q?gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 80b325c..484172f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ phpmon.xcodeproj/project.xcworkspace phpmon.xcodeproj/xcuserdata PHP Monitor.xcodeproj/project.xcworkspace PHP Monitor.xcodeproj/xcuserdata +phpmon-updater/PHP Monitor Self-Updater.app .DS_Store \ No newline at end of file From 2c40f433d31f6216a7a37821b0d1437951b9bb96 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 5 Feb 2023 18:37:18 +0100 Subject: [PATCH 03/25] =?UTF-8?q?=E2=9C=A8=20Add=20updater=20to=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you want to see the source code to the updater, you can find it here: https://github.com/nicoverbruggen/phpmon-updater Starting with version 6.0, the code of the updater will be included in this repository. --- PHP Monitor.xcodeproj/project.pbxproj | 70 ++++--- .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Filesystem/RealFileSystem.swift | 11 +- phpmon/Common/Helpers/LocalNotification.swift | 4 +- phpmon/Common/Helpers/System.swift | 4 + phpmon/Domain/App/AppUpdateChecker.swift | 182 ----------------- phpmon/Domain/App/AppUpdater.swift | 184 ++++++++++++++++++ phpmon/Domain/App/AppVersion.swift | 26 ++- .../DomainList/Cells/DomainListPhpCell.swift | 3 +- .../Integrations/Homebrew/CaskFile.swift | 78 ++++++++ phpmon/Domain/Menu/MainMenu+Startup.swift | 5 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- phpmon/Localizable.strings | 7 +- tests/unit/Parsers/CaskFileParserTest.swift | 53 +++++ tests/unit/Test Files/brew/phpmon-dev.rb | 14 ++ tests/unit/Versions/AppUpdaterCheckTest.swift | 47 ----- tests/unit/Versions/AppVersionTest.swift | 46 ++++- 17 files changed, 466 insertions(+), 272 deletions(-) delete mode 100644 phpmon/Domain/App/AppUpdateChecker.swift create mode 100644 phpmon/Domain/App/AppUpdater.swift create mode 100644 phpmon/Domain/Integrations/Homebrew/CaskFile.swift create mode 100644 tests/unit/Parsers/CaskFileParserTest.swift create mode 100644 tests/unit/Test Files/brew/phpmon-dev.rb delete mode 100644 tests/unit/Versions/AppUpdaterCheckTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 8e6848d..edc6837 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -182,9 +182,6 @@ C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E701294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */; }; C469E706294CFDF700A82AB2 /* DomainsListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C469E702294CFDF700A82AB2 /* DomainsListTest.swift */; }; - C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; - C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; - C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */; }; C46EBC4428DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */; }; C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46EBC4628DB9644007ACC74 /* RealShell.swift */; }; @@ -324,7 +321,6 @@ C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C471E84728F9BB650021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; - C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; @@ -414,7 +410,6 @@ C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; }; C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; - C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */; }; C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */; }; @@ -511,6 +506,17 @@ C48D6C70279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C48D6C71279CD2AC00F26D7E /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */; }; C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; }; + C491997729901DD6001F3A21 /* CaskFileParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997629901DD6001F3A21 /* CaskFileParserTest.swift */; }; + C491997929901DE2001F3A21 /* phpmon-dev.rb in Resources */ = {isa = PBXBuildFile; fileRef = C491997829901DE2001F3A21 /* phpmon-dev.rb */; }; + C491997B29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997C29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997D29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491997E29901DF7001F3A21 /* CaskFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997A29901DF7001F3A21 /* CaskFile.swift */; }; + C491998029901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998129901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998229901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998329901E0F001F3A21 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C491997F29901E0F001F3A21 /* AppUpdater.swift */; }; + C491998A29902089001F3A21 /* PHP Monitor Self-Updater.app in Resources */ = {isa = PBXBuildFile; fileRef = C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */; }; C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; @@ -833,8 +839,6 @@ C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = ""; }; C469E702294CFDF700A82AB2 /* DomainsListTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsListTest.swift; sourceTree = ""; }; - C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; - C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdaterCheckTest.swift; sourceTree = ""; }; C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = ""; }; C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = ""; }; C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = ""; }; @@ -858,6 +862,11 @@ C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; C48D6C6F279CD2AC00F26D7E /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; + C491997629901DD6001F3A21 /* CaskFileParserTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaskFileParserTest.swift; sourceTree = ""; }; + C491997829901DE2001F3A21 /* phpmon-dev.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = "phpmon-dev.rb"; sourceTree = ""; }; + C491997A29901DF7001F3A21 /* CaskFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaskFile.swift; sourceTree = ""; }; + C491997F29901E0F001F3A21 /* AppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = ""; }; + C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "PHP Monitor Self-Updater.app"; sourceTree = ""; }; C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = ""; }; @@ -1140,6 +1149,7 @@ C4F5FBCC28218C93001065C5 /* .swiftlint.yml */, C4E713572570151400007428 /* docs */, C41C1B3522B0097F00E7CF16 /* phpmon */, + C491998829902061001F3A21 /* phpmon-updater */, C471E79628F9B4260021E251 /* tests */, C41C1B3422B0097F00E7CF16 /* Products */, C4D309E72770EF2F00958BCF /* Frameworks */, @@ -1300,6 +1310,7 @@ C459B4BF27F6094100E9B4B4 /* brew */ = { isa = PBXGroup; children = ( + C491997829901DE2001F3A21 /* phpmon-dev.rb */, C4E2E85228FC256B003B070C /* brew-services-normal.json */, C4E2E85128FC256B003B070C /* brew-services-sudo.json */, C43A8A1F25D9D1D700591B77 /* brew-formula.json */, @@ -1444,6 +1455,14 @@ path = "PHP Version"; sourceTree = ""; }; + C491998829902061001F3A21 /* phpmon-updater */ = { + isa = PBXGroup; + children = ( + C491998929902089001F3A21 /* PHP Monitor Self-Updater.app */, + ); + path = "phpmon-updater"; + sourceTree = ""; + }; C4AF9F6A275445C900D44ED0 /* Valet */ = { isa = PBXGroup; children = ( @@ -1471,6 +1490,7 @@ C4AF9F6C275445D900D44ED0 /* Homebrew */ = { isa = PBXGroup; children = ( + C491997A29901DF7001F3A21 /* CaskFile.swift */, C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */, ); path = Homebrew; @@ -1491,8 +1511,8 @@ C4EED88827A48778006D7272 /* InterAppHandler.swift */, C4D8016522B1584700C6DA1B /* Startup.swift */, C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */, - C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, + C491997F29901E0F001F3A21 /* AppUpdater.swift */, C4A6957528D23EE300A14CF8 /* EnvironmentManager.swift */, ); path = App; @@ -1580,6 +1600,7 @@ C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, C4551656297AED18009B8466 /* ValetRcTest.swift */, + C491997629901DD6001F3A21 /* CaskFileParserTest.swift */, ); path = Parsers; sourceTree = ""; @@ -1592,7 +1613,6 @@ C4B56360276AB0A500F12CCB /* VersionExtractorTest.swift */, C4AF9F7C275454A900D44ED0 /* ValetVersionExtractorTest.swift */, C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */, - C46E206F2829D27F00D909D6 /* AppUpdaterCheckTest.swift */, ); path = Versions; sourceTree = ""; @@ -1903,6 +1923,7 @@ C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */, C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */, C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */, + C491998A29902089001F3A21 /* PHP Monitor Self-Updater.app in Resources */, C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */, C4232EE52612526500158FC6 /* Credits.html in Resources */, 54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */, @@ -1958,6 +1979,7 @@ C455165B297AEDB5009B8466 /* valetrc.broken in Resources */, 54A18D40282A566E000A0D81 /* nginx-secure-proxy-custom-tld.test in Resources */, C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */, + C491997929901DE2001F3A21 /* phpmon-dev.rb in Resources */, C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */, C4E2E85428FC256B003B070C /* brew-services-sudo.json in Resources */, ); @@ -2070,6 +2092,7 @@ C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, + C491997B29901DF7001F3A21 /* CaskFile.swift in Sources */, C4297F7A28970D59004C4630 /* WarningView.swift in Sources */, C4C0E8E227F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C42F26732805B4B400938AC7 /* ValetListable.swift in Sources */, @@ -2081,7 +2104,6 @@ C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */, C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */, C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, - C46E206D28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */, 03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */, C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */, @@ -2147,6 +2169,7 @@ C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */, + C491998029901E0F001F3A21 /* AppUpdater.swift in Sources */, C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2178,13 +2201,13 @@ C471E84228F9BB650021E251 /* AppDelegate+InterApp.swift in Sources */, C471E84328F9BB650021E251 /* App.swift in Sources */, C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */, + C491997D29901DF7001F3A21 /* CaskFile.swift in Sources */, C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */, C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, C471E84728F9BB650021E251 /* Startup.swift in Sources */, C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */, - C471E84928F9BB650021E251 /* AppUpdateChecker.swift in Sources */, C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */, C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */, C471E84C28F9BB650021E251 /* EnvironmentManager.swift in Sources */, @@ -2226,6 +2249,7 @@ C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */, C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, + C491998229901E0F001F3A21 /* AppUpdater.swift in Sources */, C4D3660D29113F20006BD146 /* System.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, @@ -2350,7 +2374,6 @@ C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */, - C471E8AC28F9BB8F0021E251 /* AppUpdateChecker.swift in Sources */, C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */, C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */, C471E8AF28F9BB8F0021E251 /* EnvironmentManager.swift in Sources */, @@ -2444,6 +2467,7 @@ C471E81028F9BAE80021E251 /* StringExtension.swift in Sources */, C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */, C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */, + C491997E29901DF7001F3A21 /* CaskFile.swift in Sources */, C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, @@ -2457,6 +2481,7 @@ C471E7D228F9BA630021E251 /* ActiveFileSystem.swift in Sources */, C471E80028F9BAD10021E251 /* Xdebug.swift in Sources */, C471E7F528F9BAC80021E251 /* PhpEnv.swift in Sources */, + C491998329901E0F001F3A21 /* AppUpdater.swift in Sources */, C471E7ED28F9BAC30021E251 /* Process.swift in Sources */, C471E81128F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, C471E7CC28F9BA5B0021E251 /* TestableShell.swift in Sources */, @@ -2524,6 +2549,7 @@ C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, + C491997C29901DF7001F3A21 /* CaskFile.swift in Sources */, C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C485707A28BF457800539B36 /* WarningListView.swift in Sources */, C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */, @@ -2574,6 +2600,7 @@ C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, + C491998129901E0F001F3A21 /* AppUpdater.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C4AD38B328ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, @@ -2600,7 +2627,6 @@ C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */, C4E49DE828F764050026AC4E /* ActiveCommand.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, - C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, C485707D28BF45A200539B36 /* WarningView.swift in Sources */, C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */, C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, @@ -2623,6 +2649,7 @@ C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, + C491997729901DD6001F3A21 /* CaskFileParserTest.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */, C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */, @@ -2667,7 +2694,6 @@ C46EBC4B28DB966A007ACC74 /* TestableShell.swift in Sources */, C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */, C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */, - C46E206E28299B3800D909D6 /* AppUpdateChecker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2831,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2843,7 +2869,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2860,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2872,7 +2898,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3088,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3099,7 +3125,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME) DEV"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3198,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1035; + CURRENT_PROJECT_VERSION = 1060; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3209,7 +3235,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.7.2; + MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 403e4f6..db36ef9 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "NO"> [String] { - return try FileManager.default.contentsOfDirectory(atPath: path) + return try FileManager.default.contentsOfDirectory(atPath: path.replacingTildeWithHomeDirectory) } func getDestinationOfSymlink(_ path: String) throws -> String { - return try FileManager.default.destinationOfSymbolicLink(atPath: path) + return try FileManager.default.destinationOfSymbolicLink(atPath: path.replacingTildeWithHomeDirectory) } // MARK: - Move & Delete Files func move(from path: String, to newPath: String) throws { - try FileManager.default.moveItem(atPath: path, toPath: newPath) + try FileManager.default.moveItem( + atPath: path.replacingTildeWithHomeDirectory, + toPath: newPath.replacingTildeWithHomeDirectory + ) } func remove(_ path: String) throws { - try FileManager.default.removeItem(atPath: path) + try FileManager.default.removeItem(atPath: path.replacingTildeWithHomeDirectory) } // MARK: — FS Attributes diff --git a/phpmon/Common/Helpers/LocalNotification.swift b/phpmon/Common/Helpers/LocalNotification.swift index 6e4f7bc..eb8de9a 100644 --- a/phpmon/Common/Helpers/LocalNotification.swift +++ b/phpmon/Common/Helpers/LocalNotification.swift @@ -10,8 +10,8 @@ import UserNotifications class LocalNotification { - @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName) { - if !Preferences.isEnabled(preference) { + @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName?) { + if preference != nil && !Preferences.isEnabled(preference!) { return } diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift index 332bba2..bd54915 100644 --- a/phpmon/Common/Helpers/System.swift +++ b/phpmon/Common/Helpers/System.swift @@ -26,3 +26,7 @@ public func system(_ command: String) -> String { return output } + +public func system_quiet(_ command: String) { + _ = system(command) +} diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift deleted file mode 100644 index 0593b2b..0000000 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// Updater.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 09/05/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import Foundation -import AppKit - -class AppUpdateChecker { - - public static var enabled: Bool = { - return Preferences.isEnabled(.automaticBackgroundUpdateCheck) - }() - - public static var isDev: Bool = { - return App.version.contains("-dev") - }() - - public static func retrieveVersionFromCask( - _ initiatedFromBackground: Bool = true - ) async -> String { - let caskFile = App.version.contains("-dev") - ? Constants.Urls.DevBuildCaskFile.absoluteString - : Constants.Urls.StableBuildCaskFile.absoluteString - - var command = "curl -s" - - if initiatedFromBackground { - command = "curl -s --max-time 5" - } - - return await Shell.pipe( - "\(command) '\(caskFile)' | grep version" - ).out - } - - public static func checkIfNewerVersionIsAvailable( - initiatedFromBackground: Bool = true - ) async { - if initiatedFromBackground { - if !Preferences.isEnabled(.automaticBackgroundUpdateCheck) { - Log.info("Automatic updates are disabled. No check will be performed.") - return - } - - Log.info("Automatic updates are enabled, a check will be performed.") - } - - let versionString = await retrieveVersionFromCask(initiatedFromBackground) - - guard let onlineVersion = AppVersion.from(versionString) else { - Log.err("We couldn't check for updates!") - - // Only notify about connection issues if the request to check for updates was explicit - if !initiatedFromBackground { - notifyAboutConnectionIssue() - } - - return - } - - let currentVersion = AppVersion.fromCurrentVersion() - - handleVersionComparison( - currentVersion, - onlineVersion, - initiatedFromBackground - ) - } - - private static func handleVersionComparison( - _ currentVersion: AppVersion, - _ onlineVersion: AppVersion, - _ background: Bool - ) { - switch onlineVersion.version.versionCompare(currentVersion.version) { - case .orderedAscending: - Log.info("You are running a newer version of PHP Monitor " - + "(\(currentVersion.computerReadable) > \(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - case .orderedDescending: - Log.info("There is a newer version (\(onlineVersion)) available! " - + "(\(onlineVersion.computerReadable) > \(currentVersion.computerReadable))") - notifyAboutNewerVersion(version: onlineVersion) - case .orderedSame: - if currentVersion.build != nil - && onlineVersion.build != nil - && buildDiffers(currentVersion, onlineVersion, background) { - return - } - - Log.info("The installed version (\(currentVersion.computerReadable)) matches the latest release " - + "(\(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - } - } - - private static func buildDiffers( - _ currentVersion: AppVersion, - _ onlineVersion: AppVersion, - _ background: Bool - ) -> Bool { - if Int(onlineVersion.build!)! > Int(currentVersion.build!)! { - Log.info("There is a newer build of PHP Monitor available! " - + "(\(onlineVersion.computerReadable) > \(currentVersion.computerReadable))") - notifyAboutNewerVersion(version: onlineVersion) - return true - } else if Int(onlineVersion.build!)! < Int(currentVersion.build!)! { - Log.info("You are running a newer build of PHP Monitor " - + "(\(currentVersion.computerReadable) > \(onlineVersion.computerReadable)).") - if !background { notifyVersionDoesNotNeedUpgrade() } - return true - } - - return false - } - - private static func notifyVersionDoesNotNeedUpgrade() { - Task { @MainActor in - BetterAlert().withInformation( - title: "updater.alerts.is_latest_version.title".localized, - subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), - description: "" - ) - .withPrimary(text: "generic.ok".localized) - .show() - } - } - - private static func notifyAboutNewerVersion(version: AppVersion) { - let devSuffix = isDev ? "-dev" : "" - let command = isDev ? "brew upgrade phpmon-dev" : "brew upgrade phpmon" - - Task { @MainActor in - BetterAlert().withInformation( - title: "updater.alerts.newer_version_available.title".localized(version.humanReadable), - subtitle: "updater.alerts.newer_version_available.subtitle".localized, - description: HomebrewDiagnostics.customCaskInstalled - ? "updater.installation_source.brew".localized(command) - : "updater.installation_source.direct".localized - ) - .withPrimary( - text: "updater.alerts.buttons.release_notes".localized, - action: { vc in - vc.close(with: .OK) - - NSWorkspace.shared.open( - Constants.Urls.GitHubReleases.appendingPathComponent("/tag/v\(version.tagged)\(devSuffix)") - ) - } - ) - .withTertiary(text: "Dismiss", action: { vc in - vc.close(with: .OK) - }) - .show() - } - } - - private static func notifyAboutConnectionIssue() { - Task { @MainActor in - BetterAlert().withInformation( - 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.alerts.buttons.releases_on_github".localized, - action: { _ in - NSWorkspace.shared.open(Constants.Urls.GitHubReleases) - } - ) - .withPrimary(text: "generic.ok".localized) - .show() - } - } - -} diff --git a/phpmon/Domain/App/AppUpdater.swift b/phpmon/Domain/App/AppUpdater.swift new file mode 100644 index 0000000..6ef3c74 --- /dev/null +++ b/phpmon/Domain/App/AppUpdater.swift @@ -0,0 +1,184 @@ +// +// AppUpdater.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation +import Cocoa + +class AppUpdater { + var caskFile: CaskFile! + var latestVersionOnline: AppVersion! + var interactive: Bool = false + + public func checkForUpdates(interactive: Bool) async { + self.interactive = interactive + + if interactive && !Preferences.isEnabled(.automaticBackgroundUpdateCheck) { + Log.info("Skipping automatic update check due to user preference.") + return + } + + Log.info("The app will search for updates...") + + let caskUrl = App.identifier.contains(".dev") + ? Constants.Urls.DevBuildCaskFile + : Constants.Urls.StableBuildCaskFile + + guard let caskFile = await CaskFile.from(url: caskUrl) else { + Log.err("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") + return presentCouldNotRetrieveUpdateIfInteractive() + } + + self.caskFile = caskFile + + let currentVersion = AppVersion.fromCurrentVersion() + + guard let onlineVersion = AppVersion.from(caskFile.version) else { + Log.err("The version string from the CaskFile could not be read.") + return presentCouldNotRetrieveUpdateIfInteractive() + } + + latestVersionOnline = onlineVersion + Log.info("The latest version read from '\(caskUrl.lastPathComponent)' is: v\(onlineVersion.computerReadable).") + + if latestVersionOnline > currentVersion { + presentNewerVersionAvailableAlert() + } else if interactive { + presentNoNewerVersionAvailableAlert() + } + } + + private func presentCouldNotRetrieveUpdateIfInteractive() { + if interactive { + return presentCouldNotRetrieveUpdate() + } else { + return + } + } + + // MARK: - Alerts + + public func presentNewerVersionAvailableAlert() { + let command = App.identifier.contains(".dev") + ? "brew upgrade phpmon-dev" + : "brew upgrade phpmon" + + Task { @MainActor in + BetterAlert().withInformation( + title: "updater.alerts.newer_version_available.title" + .localized(latestVersionOnline.humanReadable), + subtitle: "updater.alerts.newer_version_available.subtitle" + .localized, + description: HomebrewDiagnostics.customCaskInstalled + ? "updater.installation_source.brew".localized(command) + : "updater.installation_source.direct".localized + ) + .withPrimary( + text: "updater.alerts.buttons.install".localized, + action: { vc in + self.prepareForDownload() + vc.close(with: .OK) + } + ) + .withSecondary( + text: "updater.alerts.buttons.release_notes".localized, + action: { _ in + let urlSegments = self.caskFile.url.split(separator: "/") + let tag = urlSegments[urlSegments.count - 2] // ../download/{tag}/{file.zip} + NSWorkspace.shared.open( + Constants.Urls.GitHubReleases.appendingPathComponent("/tag/\(tag)") + ) + } + ) + .withTertiary(text: "updater.alerts.buttons.dismiss".localized, action: { vc in + vc.close(with: .OK) + }) + .show() + } + } + + public func presentNoNewerVersionAvailableAlert() { + Task { @MainActor in + BetterAlert().withInformation( + title: "updater.alerts.is_latest_version.title".localized, + subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), + description: "" + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + } + + public func presentCouldNotRetrieveUpdate() { + Task { @MainActor in + BetterAlert().withInformation( + 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.alerts.buttons.releases_on_github".localized, + action: { _ in + NSWorkspace.shared.open(Constants.Urls.GitHubReleases) + } + ) + .withPrimary(text: "generic.ok".localized) + .show() + } + } + + // MARK: - Preparing for Self-Updater + + private func prepareForDownload() { + let updater = Bundle.main.resourceURL!.path + "/PHP Monitor Self-Updater.app" + + system_quiet("mkdir -p ~/.config/phpmon/updater 2> /dev/null") + + let updaterDirectory = "~/.config/phpmon/updater" + .replacingOccurrences(of: "~", with: NSHomeDirectory()) + + system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/PHP Monitor Self-Updater.app\"") + + try! FileSystem.writeAtomicallyToFile( + "\(updaterDirectory)/update.json", + content: "{ \"url\": \"\(caskFile.url)\", \"sha256\": \"\(caskFile.sha256)\" }" + ) + + let updaterUrl = NSURL(fileURLWithPath: updater, isDirectory: true) as URL + let configuration = NSWorkspace.OpenConfiguration() + + NSWorkspace.shared.openApplication(at: updaterUrl, configuration: configuration) { _, _ in + Log.info("The updater has been launched successfully!") + } + } + + // MARK: - Checking if Self-Updater Worked + + public static func checkIfUpdateWasPerformed() { + // Cleanup the upgrade.success file + if FileSystem.fileExists("~/.config/phpmon/updater/upgrade.success") { + Task { @MainActor in + LocalNotification.send( + title: "notification.phpmon_updated.title".localized, + subtitle: "notification.phpmon_updated.desc".localized(App.shortVersion), + preference: nil + ) + } + + Log.info("The `upgrade.success` file was found! An update was installed. Cleaning up...") + try! FileSystem.remove("~/.config/phpmon/updater/upgrade.success") + } + + // Cleanup the previous updater + if FileSystem.anyExists("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") { + Log.info("A remnant of the self-updater must still be removed...") + try? FileSystem.remove("~/.config/phpmon/updater/PHP Monitor Self-Updater.app") + } + } +} diff --git a/phpmon/Domain/App/AppVersion.swift b/phpmon/Domain/App/AppVersion.swift index 62231b4..5d020af 100644 --- a/phpmon/Domain/App/AppVersion.swift +++ b/phpmon/Domain/App/AppVersion.swift @@ -8,14 +8,14 @@ import Foundation -class AppVersion { +class AppVersion: Comparable { var version: String - var build: String? + var build: Int? var suffix: String? init(version: String, build: String?, suffix: String? = nil) { self.version = version - self.build = build + self.build = Int(build ?? "0") self.suffix = suffix } @@ -75,11 +75,27 @@ class AppVersion { } var computerReadable: String { - return "\(version)_\(build ?? "0")" + return "\(version)_\(build ?? 0)" } var humanReadable: String { - return "\(version) (\(build ?? "???"))" + return "\(version) (\(build ?? 0))" } + // MARK: - Comparable Protocol + + static func < (lhs: AppVersion, rhs: AppVersion) -> Bool { + let comparisonResult = lhs.version.versionCompare(rhs.version) + + if comparisonResult == .orderedAscending { + return true + } + + return lhs.build ?? 0 < rhs.build ?? 0 + } + + static func == (lhs: AppVersion, rhs: AppVersion) -> Bool { + lhs.version.versionCompare(rhs.version) == .orderedSame + && lhs.build == rhs.build + } } diff --git a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift index 9271a29..9d2d73c 100644 --- a/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift +++ b/phpmon/Domain/DomainList/Cells/DomainListPhpCell.swift @@ -37,7 +37,8 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol { imageViewPhpVersionOK.image = NSImage(named: "Isolated") imageViewPhpVersionOK.toolTip = "domain_list.tooltips.isolated".localized(site.servingPhpVersion) } else { - imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???" || !site.isCompatibleWithPreferredPhpVersion) + imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???" + || !site.isCompatibleWithPreferredPhpVersion) imageViewPhpVersionOK.image = NSImage(named: "Checkmark") imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.preferredPhpVersion) diff --git a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift new file mode 100644 index 0000000..c2ddf0b --- /dev/null +++ b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift @@ -0,0 +1,78 @@ +// +// CaskFile.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +struct CaskFile { + var properties: [String: String] + + var name: String { + return self.properties["name"]! + } + var url: String { + return self.properties["url"]! + } + var sha256: String { + return self.properties["sha256"]! + } + var version: String { + return self.properties["version"]! + } + + public static func from(url: URL) async -> CaskFile? { + var string: String? + + if url.scheme == "file" { + string = try? String(contentsOf: url) + } else { + string = await Shell.pipe("curl -s --max-time 10 '\(url.absoluteString)'").out + } + + guard let string else { + Log.err("The content of the URL for the CaskFile could not be retrieved") + return nil + } + + let lines = string.split(separator: "\n") + .filter { $0 != "" } + + if lines.count < 4 { + Log.err("The CaskFile is <4 lines long, which is too short") + return nil + } + + if !lines.first!.starts(with: "cask") || !lines.last!.starts(with: "end") { + Log.err("The CaskFile does not start with 'cask' or does not end with 'end'") + return nil + } + + var props: [String: String] = [:] + + let regex = try! NSRegularExpression(pattern: "(\\w+)\\s+'([^']+)'") + + for line in lines { + if let match = regex.firstMatch( + in: String(line), + range: NSRange(location: 0, length: line.utf16.count) + ) { + let keyRange = match.range(at: 1) + let valueRange = match.range(at: 2) + let key = (line as NSString).substring(with: keyRange) + let value = (line as NSString).substring(with: valueRange) + props[key] = value + } + } + + for required in ["version", "sha256", "url", "name"] where !props.keys.contains(required) { + Log.err("Property '\(required)' expected on CaskFile, assuming CaskFile is invalid") + return nil + } + + return CaskFile(properties: props) + } +} diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index e08035c..a83fe73 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -112,12 +112,15 @@ extension MainMenu { } } - await AppUpdateChecker.checkIfNewerVersionIsAvailable() + await AppUpdater().checkForUpdates(interactive: false) } // Check if the linked version has changed between launches of phpmon Stats.evaluateLastLinkedPhpVersion() + // Check if an update was performed earlier + AppUpdater.checkIfUpdateWasPerformed() + // We are ready! Log.info("PHP Monitor is ready to serve!") } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index bf05c1f..b4b0656 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -193,7 +193,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate } @objc func checkForUpdates() { - Task { await AppUpdateChecker.checkIfNewerVersionIsAvailable(initiatedFromBackground: false) } + Task { await AppUpdater().checkForUpdates(interactive: true) } } // MARK: - Menu Delegate diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index c15ad2b..660ffea 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -617,9 +617,8 @@ COMMON TROUBLESHOOTING TIPS "updater.alerts.newer_version_available.title" = "PHP Monitor v%@ is now available!"; "updater.alerts.newer_version_available.subtitle" = "Keeping PHP Monitor up-to-date is highly recommended, since newer versions usually fix bugs and include fixes to support the latest versions of Valet and PHP."; -"updater.alerts.newer_version_available.description" = "PHP Monitor is supposed to be updated via Homebrew, so there is no built-in updater. This check is only meant to inform you of the existence of a new version, you do not need to upgrade."; -"updater.installation_source.brew" = "You appear to have installed PHP Monitor via Homebrew (or have at least tapped the required Caskfile) so it is recommended that you upgrade via the terminal by running `%@`."; -"updater.installation_source.direct" = "You do not appear to have installed PHP Monitor via Homebrew, so you will need to visit GitHub to download the latest update."; +"updater.installation_source.brew" = "The recommended method of installing updates to PHP Monitor is to simply press 'Install Update'.\n\n(You may also upgrade via the terminal by running `%@`, but this is not recommended.)"; +"updater.installation_source.direct" = "The recommended method of installing updates to PHP Monitor is to simply press 'Install Update'."; "updater.alerts.buttons.release_notes" = "View Release Notes"; "updater.alerts.is_latest_version.title" = "PHP Monitor is up-to-date!"; @@ -629,6 +628,8 @@ COMMON TROUBLESHOOTING TIPS "updater.alerts.cannot_check_for_update.subtitle" = "You might not be connected to the internet, are blocking traffic or GitHub is down and won't allow you to check for updates. If you keep seeing this message, you may want to manually check the releases page."; "updater.alerts.cannot_check_for_update.description" = "The currently installed version is: %@. You can go to the list of the latest releases (on GitHub) by clicking on the button on the left."; "updater.alerts.buttons.releases_on_github" = "View Releases"; +"updater.alerts.buttons.install" = "Install Update"; +"updater.alerts.buttons.dismiss" = "Dismiss"; // WARNINGS ABOUT NON-DEFAULT TLD diff --git a/tests/unit/Parsers/CaskFileParserTest.swift b/tests/unit/Parsers/CaskFileParserTest.swift new file mode 100644 index 0000000..1b861f6 --- /dev/null +++ b/tests/unit/Parsers/CaskFileParserTest.swift @@ -0,0 +1,53 @@ +// +// CaskFileParserTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 04/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import XCTest + +class CaskFileParserTest: XCTestCase { + + // MARK: - Test Files + static var exampleFilePath: URL { + return Bundle(for: Self.self) + .url(forResource: "phpmon-dev", withExtension: "rb")! + } + + func test_can_extract_fields_from_cask_file() async throws { + guard let caskFile = await CaskFile.from(url: CaskFileParserTest.exampleFilePath) else { + return XCTFail("The CaskFile could not be parsed, check the log for more info") + } + + XCTAssertEqual( + caskFile.version, + "5.7.2_1035" + ) + XCTAssertEqual( + caskFile.sha256, + "1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a" + ) + XCTAssertEqual( + caskFile.name, + "PHP Monitor DEV" + ) + XCTAssertEqual( + caskFile.url, + "https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip" + ) + } + + func test_can_extract_fields_from_remote_cask_file() async throws { + guard let caskFile = await CaskFile.from(url: Constants.Urls.StableBuildCaskFile) else { + return XCTFail("The remote CaskFile could not be parsed, check the log for more info") + } + + XCTAssertTrue(caskFile.properties.keys.contains("version")) + XCTAssertTrue(caskFile.properties.keys.contains("homepage")) + XCTAssertTrue(caskFile.properties.keys.contains("url")) + XCTAssertTrue(caskFile.properties.keys.contains("appcast")) + } + +} diff --git a/tests/unit/Test Files/brew/phpmon-dev.rb b/tests/unit/Test Files/brew/phpmon-dev.rb new file mode 100644 index 0000000..177d11d --- /dev/null +++ b/tests/unit/Test Files/brew/phpmon-dev.rb @@ -0,0 +1,14 @@ +cask 'phpmon-dev' do + depends_on formula: 'gnu-sed' + + version '5.7.2_1035' + sha256 '1cb147bd1b1fbd52971d90dff577465b644aee7c878f15ede57f46e8f217067a' + + url 'https://github.com/nicoverbruggen/phpmon/releases/download/v5.7.2/phpmon-dev.zip' + appcast 'https://github.com/nicoverbruggen/phpmon/releases.atom' + name 'PHP Monitor DEV' + homepage 'https://phpmon.app' + + app 'PHP Monitor DEV.app', target: "PHP Monitor DEV.app" + end + \ No newline at end of file diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift deleted file mode 100644 index 463635e..0000000 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// AppUpdaterCheckTest.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 10/05/2022. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class AppUpdaterCheckTest: XCTestCase { - - func test_can_retrieve_version_from_cask() async { - let caskVersion = await AppUpdateChecker.retrieveVersionFromCask() - - let version = VersionExtractor.from(caskVersion) - - XCTAssertNotNil(version) - } - - func test_tagged_release_omits_zero_patch() { - let version = AppVersion.from("3.5.0_333")! - - XCTAssertEqual(version.tagged, "3.5") - XCTAssertEqual(version.version, "3.5.0") - } - - func test_tagged_release_doesnt_omit_non_zero_patch() { - let version = AppVersion.from("3.5.1_333")! - - XCTAssertEqual(version.tagged, "3.5.1") - XCTAssertEqual(version.version, "3.5.1") - } - - func test_tag_truncation_does_not_affect_major_versions() { - var version = AppVersion.from("5.0_333")! - - XCTAssertEqual(version.tagged, "5.0") - XCTAssertEqual(version.version, "5.0") - - version = AppVersion.from("5.0.0_333")! - - XCTAssertEqual(version.tagged, "5.0") - XCTAssertEqual(version.version, "5.0.0") - } - -} diff --git a/tests/unit/Versions/AppVersionTest.swift b/tests/unit/Versions/AppVersionTest.swift index c2ac087..2993ee7 100644 --- a/tests/unit/Versions/AppVersionTest.swift +++ b/tests/unit/Versions/AppVersionTest.swift @@ -28,7 +28,7 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("600", version?.build) + XCTAssertEqual(600, version?.build) XCTAssertEqual(nil, version?.suffix) } @@ -46,7 +46,7 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("870", version?.build) + XCTAssertEqual(870, version?.build) XCTAssertEqual("dev", version?.suffix) } @@ -55,8 +55,48 @@ class AppVersionTest: XCTestCase { XCTAssertNotNil(version) XCTAssertEqual("1.0.0", version?.version) - XCTAssertEqual("870", version?.build) + XCTAssertEqual(870, version?.build) XCTAssertEqual("dev", version?.suffix) } + func test_tagged_release_omits_zero_patch() { + let version = AppVersion.from("3.5.0_333")! + + XCTAssertEqual(version.tagged, "3.5") + XCTAssertEqual(version.version, "3.5.0") + } + + func test_tagged_release_doesnt_omit_non_zero_patch() { + let version = AppVersion.from("3.5.1_333")! + + XCTAssertEqual(version.tagged, "3.5.1") + XCTAssertEqual(version.version, "3.5.1") + } + + func test_tag_truncation_does_not_affect_major_versions() { + var version = AppVersion.from("5.0_333")! + + XCTAssertEqual(version.tagged, "5.0") + XCTAssertEqual(version.version, "5.0") + + version = AppVersion.from("5.0.0_333")! + + XCTAssertEqual(version.tagged, "5.0") + XCTAssertEqual(version.version, "5.0.0") + } + + func test_can_compare_version_numbers() { + // Build is newer + XCTAssertTrue(AppVersion.from("5.0_101")! > AppVersion.from("5.0_100")!) + + // Version and build is the same + XCTAssertFalse(AppVersion.from("5.0.0_100")! > AppVersion.from("5.0_100")!) + + // Version is newer + XCTAssertTrue(AppVersion.from("5.1_100")! > AppVersion.from("5.0_100")!) + + // Build is older + XCTAssertFalse(AppVersion.from("5.0_101")! > AppVersion.from("5.0_102")!) + } + } From 20291bf034b43068ee369c9f612e5a26596f5121 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 5 Feb 2023 18:51:49 +0100 Subject: [PATCH 04/25] =?UTF-8?q?=F0=9F=93=9D=20Update=20README=20about=20?= =?UTF-8?q?new=20updater?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e467e1a..abd66dd 100644 --- a/README.md +++ b/README.md @@ -43,22 +43,24 @@ valet install valet trust ``` -Once that's done, you can install PHP Monitor via Homebrew (recommended), or (alternatively) you may download the latest release on GitHub. +#### Manual installation (first time only) -To install via Homebrew, run: +Once that's done, you can [download the latest release](https://github.com/nicoverbruggen/phpmon/releases/latest), unzip it and place it in `/Applications`. + +#### Installation via Homebrew + +If you prefer to install the app via Homebrew, you can also do this: ```sh brew tap nicoverbruggen/homebrew-cask brew install --cask phpmon ``` -To upgrade your existing installation, run: +## ⬆️ How to update -```sh -brew upgrade phpmon -``` +The recommended method of updating your app to the latest version is to use **the built-in updater**. -(You may need to run `brew update` or `brew update-reset` first in order to update the cask file if you ran a Homebrew operation recently.) +If that doesn't work or you prefer Homebrew, you can also upgrade via those methods. ## ⚡️ Launchers (Alfred, Raycast) From cd5cbccb041a118786ab844475758aad360cd2cd Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 6 Feb 2023 19:09:14 +0100 Subject: [PATCH 05/25] =?UTF-8?q?=F0=9F=90=9B=20Fix=20generated=20script?= =?UTF-8?q?=20(#231)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Actions.swift | 19 ++++++++++--------- phpmon/Common/Core/Homebrew.swift | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/phpmon/Common/Core/Actions.swift b/phpmon/Common/Core/Actions.swift index 86408df..8abc354 100644 --- a/phpmon/Common/Core/Actions.swift +++ b/phpmon/Common/Core/Actions.swift @@ -13,21 +13,21 @@ class Actions { // MARK: - Services public static func restartPhpFpm() async { - await brew("services restart \(Homebrew.Formulae.php.name)", sudo: Homebrew.Formulae.php.elevated) + await brew("services restart \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) } public static func restartNginx() async { - await brew("services restart \(Homebrew.Formulae.nginx.name)", sudo: Homebrew.Formulae.nginx.elevated) + await brew("services restart \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) } public static func restartDnsMasq() async { - await brew("services restart \(Homebrew.Formulae.dnsmasq.name)", sudo: Homebrew.Formulae.dnsmasq.elevated) + await brew("services restart \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) } public static func stopValetServices() async { - await brew("services stop \(Homebrew.Formulae.php.name)", sudo: Homebrew.Formulae.php.elevated) - await brew("services stop \(Homebrew.Formulae.nginx.name)", sudo: Homebrew.Formulae.nginx.elevated) - await brew("services stop \(Homebrew.Formulae.dnsmasq.name)", sudo: Homebrew.Formulae.dnsmasq.elevated) + await brew("services stop \(Homebrew.Formulae.php)", sudo: Homebrew.Formulae.php.elevated) + await brew("services stop \(Homebrew.Formulae.nginx)", sudo: Homebrew.Formulae.nginx.elevated) + await brew("services stop \(Homebrew.Formulae.dnsmasq)", sudo: Homebrew.Formulae.dnsmasq.elevated) } public static func fixHomebrewPermissions() throws { @@ -54,9 +54,10 @@ class Actions { + " && " + cellarCommands.joined(separator: " && ") - let appleScript = NSAppleScript( - source: "do shell script \"\(script)\" with administrator privileges" - ) + let source = "do shell script \"\(script)\" with administrator privileges" + + Log.perf(source) + let appleScript = NSAppleScript(source: source) let eventResult: NSAppleEventDescriptor? = appleScript?.executeAndReturnError(nil) diff --git a/phpmon/Common/Core/Homebrew.swift b/phpmon/Common/Core/Homebrew.swift index c570f2a..de96a64 100644 --- a/phpmon/Common/Core/Homebrew.swift +++ b/phpmon/Common/Core/Homebrew.swift @@ -36,10 +36,14 @@ class Homebrew { } } -class HomebrewFormula: Equatable, Hashable { +class HomebrewFormula: Equatable, Hashable, CustomStringConvertible { let name: String let elevated: Bool + var description: String { + return name + } + init(_ name: String, elevated: Bool = true) { self.name = name self.elevated = elevated From 9a7575790aca9aff3bebcd3e9727fe70009e21c0 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 6 Feb 2023 19:12:34 +0100 Subject: [PATCH 06/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index edc6837..342ed5c 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2857,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1060; + CURRENT_PROJECT_VERSION = 1062; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2886,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1060; + CURRENT_PROJECT_VERSION = 1062; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3114,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1060; + CURRENT_PROJECT_VERSION = 1062; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3224,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1060; + CURRENT_PROJECT_VERSION = 1062; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From ac60c66bb93462706dd4b6aa23e6ec499bd9252f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 6 Feb 2023 19:33:41 +0100 Subject: [PATCH 07/25] =?UTF-8?q?=F0=9F=90=9B=20Add=20missing=20strings=20?= =?UTF-8?q?for=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- phpmon/Localizable.strings | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 342ed5c..a92fb25 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2857,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1062; + CURRENT_PROJECT_VERSION = 1063; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2886,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1062; + CURRENT_PROJECT_VERSION = 1063; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3114,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1062; + CURRENT_PROJECT_VERSION = 1063; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3224,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1062; + CURRENT_PROJECT_VERSION = 1063; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 660ffea..4aa07bc 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -355,6 +355,9 @@ This has no effect on other terminals, only for the particular terminal session "notification.preset_reverted_title" = "Preset reverted"; "notification.preset_reverted_desc" = "The last preset you applied has been undone. Your previous configuration is now active."; +"notification.phpmon_updated.title" = "PHP Monitor has been updated!"; +"notification.phpmon_updated.desc" = "You are now running PHP Monitor v%@."; + // Composer Update "alert.composer_missing.title" = "Composer not found!"; "alert.composer_missing.subtitle" = "PHP Monitor could not find Composer. Make sure that Composer is installed and try again."; From 7285d24ef34175b72ea0ee013dc2e9237c7fa682 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 7 Feb 2023 22:02:34 +0100 Subject: [PATCH 08/25] =?UTF-8?q?=F0=9F=91=8C=20Improve=20first=20launch?= =?UTF-8?q?=20onboarding=20experience?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Menu/MainMenu+Startup.swift | 4 ++-- .../Domain/Onboarding/OnboardingWindowController.swift | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index a83fe73..c8c5d4e 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -110,9 +110,9 @@ extension MainMenu { Task { @MainActor in OnboardingWindowController.show() } + } else { + await AppUpdater().checkForUpdates(interactive: false) } - - await AppUpdater().checkForUpdates(interactive: false) } // Check if the linked version has changed between launches of phpmon diff --git a/phpmon/Domain/Onboarding/OnboardingWindowController.swift b/phpmon/Domain/Onboarding/OnboardingWindowController.swift index 5b65200..3ac430b 100644 --- a/phpmon/Domain/Onboarding/OnboardingWindowController.swift +++ b/phpmon/Domain/Onboarding/OnboardingWindowController.swift @@ -42,4 +42,13 @@ class OnboardingWindowController: PMWindowController { NSApp.activate(ignoringOtherApps: true) } + + override func close() { + super.close() + + // Search for updates after closing the window + if Stats.successfulLaunchCount == 1 { + Task { await AppUpdater().checkForUpdates(interactive: false) } + } + } } From b08912ce1188455ce68ed05e6a1764fe273fbc1e Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 10 Feb 2023 19:31:07 +0100 Subject: [PATCH 09/25] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20wildcar?= =?UTF-8?q?d=20constraints=20(#224)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PhpVersionNumberCollection.swift | 17 +++++++ .../PHP/PHP Version/VersionNumber.swift | 36 +++++++++----- .../PHP/Switcher/InternalSwitcher.swift | 1 - .../unit/Versions/PhpVersionNumberTest.swift | 47 +++++++++++++++++++ 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift b/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift index 029e262..3a408c5 100644 --- a/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift +++ b/phpmon/Common/PHP/PHP Version/PhpVersionNumberCollection.swift @@ -35,6 +35,7 @@ public struct PhpVersionNumberCollection: Equatable { - Parameter strict: Whether the patch version check is strict. See more below. The strict mode does not matter if a patch version is provided for all versions in the collection. + It also does not matter for certain comparisons (e.g. when dealing with wildcards). Strict mode assumes that any PHP version lacking precise patch information, e.g. inferred from Homebrew corresponds to the .0 patch version of that version. The default, which is imprecise, @@ -45,6 +46,7 @@ public struct PhpVersionNumberCollection: Equatable { Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in strict mode only 8.1.? will be considered valid (8.0 translates to 8.0.0 and as such is older than 8.0.1, 8.1.0 is OK). + When checking against actual PHP versions installed by the user (with patch precision), use strict mode. @@ -52,11 +54,26 @@ public struct PhpVersionNumberCollection: Equatable { Given versions 8.0.? and 8.1.?, but the requirement is ^8.0.1, in non-strict mode version 8.0 is assumed to be equal to version 8.0.999, which is actually fine if 8.0.1 is the required version. + In non-strict mode, the patch version is ignored for regular version checks (no caret / tilde). If checking compatibility with general Homebrew versions of PHP, do NOT use strict mode, since the patch version there is not used. (The formula php@8.0 suffices for ^8.0.1.) */ public func matching(constraint: String, strict: Bool = false) -> [VersionNumber] { + if constraint == "*" { + return self.versions + } + + if let version = VersionNumber.make(from: constraint, type: .wildCardPatch) { + // Wildcard for patch (e.g. "7.4.*") must match major and minor (any patch) + return self.versions.filter { $0.hasSameMajorAndMinor(version) } + } + + if let version = VersionNumber.make(from: constraint, type: .wildCardMinor) { + // Strict constraint (e.g. "7.*") -> must only match major (any patch, minor) + return self.versions.filter { $0.isSameMajorVersionAs(version) } + } + if let version = VersionNumber.make(from: constraint, type: .versionOnly) { // Strict constraint (e.g. "7.0") -> returns specific version return self.versions.filter { $0.isSameAs(version, strict) } diff --git a/phpmon/Common/PHP/PHP Version/VersionNumber.swift b/phpmon/Common/PHP/PHP Version/VersionNumber.swift index b01e6c0..fc400ec 100644 --- a/phpmon/Common/PHP/PHP Version/VersionNumber.swift +++ b/phpmon/Common/PHP/PHP Version/VersionNumber.swift @@ -39,6 +39,8 @@ public struct VersionNumber: Equatable, Hashable { public enum MatchType: String { case versionOnly = #"^(?\d+).(?\d+).?(?\d+)?\z"# + case wildCardPatch = #"^(?\d+).(?\d+).?(?\*)?\z"# + case wildCardMinor = #"^(?\d+).(?\*)?\z"# case caretVersionRange = #"^\^(?\d+).(?\d+).?(?\d+)?\z"# case tildeVersionRange = #"^~(?\d+).(?\d+).?(?\d+)?\z"# case greaterThanOrEqual = #"^>=(?\d+).(?\d+).?(?\d+)?\z"# @@ -64,21 +66,25 @@ public struct VersionNumber: Equatable, Hashable { range: NSRange(location: 0, length: versionString.count) ).first - if match != nil { - let major = Int( - versionString[Range(match!.range(withName: "major"), in: versionString)!] - )! - let minor = Int( - versionString[Range(match!.range(withName: "minor"), in: versionString)!] - )! - var patch: Int? - if let minorRange = Range(match!.range(withName: "patch"), in: versionString) { - patch = Int(versionString[minorRange]) - } - return Self(major: major, minor: minor, patch: patch) + guard let match else { return nil } + + let major = Int(versionString[Range(match.range(withName: "major"), in: versionString)!])! + var minor: Int = 0 + var patch: Int? + + if let minorRange = Range(match.range(withName: "minor"), in: versionString) { + let value = versionString[minorRange] as String + // Zero is the fallback if a wildcard was used + minor = Int(value) ?? 0 } - return nil + if let patchRange = Range(match.range(withName: "patch"), in: versionString) { + let value = versionString[patchRange] as String + // nil is the fallback if a wildcard was used + patch = Int(value) ?? nil + } + + return Self(major: major, minor: minor, patch: patch) } // MARK: Comparison Logic @@ -93,6 +99,10 @@ public struct VersionNumber: Equatable, Hashable { && (strict ? self.patch(strict, version) == version.patch(strict) : true) } + internal func hasSameMajorAndMinor(_ version: VersionNumber) -> Bool { + return self.major == version.major && self.minor == version.minor + } + internal func isNewerThan(_ version: VersionNumber, _ strict: Bool) -> Bool { return ( self.major > version.major || diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 9526b93..5b465aa 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -22,7 +22,6 @@ class InternalSwitcher: PhpSwitcher { */ func performSwitch(to version: String) async { Log.info("Switching to \(version), unlinking all versions...") - let versions = getVersionsToBeHandled(version) await withTaskGroup(of: String.self, body: { group in diff --git a/tests/unit/Versions/PhpVersionNumberTest.swift b/tests/unit/Versions/PhpVersionNumberTest.swift index e2744b0..d12f3dc 100644 --- a/tests/unit/Versions/PhpVersionNumberTest.swift +++ b/tests/unit/Versions/PhpVersionNumberTest.swift @@ -44,6 +44,53 @@ class PhpVersionNumberTest: XCTestCase { } } + func test_can_parse_wildcard() throws { + let version = VersionNumber.make(from: "7.*", type: .wildCardMinor) + XCTAssertNotNil(version) + XCTAssertEqual(version!.major, 7) + XCTAssertEqual(version!.minor, 0) + } + + + func test_can_check_wildcard_version_constraint() throws { + // Wildcard for patch only + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.3.9"]) + .matching(constraint: "7.3.*", strict: false), + PhpVersionNumberCollection + .make(from: ["7.3.10", "7.3.9"]).all + ) + + // Wildcard for minor + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["8.0.0", "7.4.10", "7.3.10", "7.3.9"]) + .matching(constraint: "7.*", strict: false), + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.3.9"]).all + ) + + // Full wildcard + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]) + .matching(constraint: "*", strict: false), + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all + ) + } + + func test_can_check_any_version_constraint() throws { + XCTAssertEqual( + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]) + .matching(constraint: "*", strict: false), + PhpVersionNumberCollection + .make(from: ["7.4.10", "7.3.10", "7.2.10", "7.1.10", "7.0.10"]).all + ) + } + func test_can_check_fixed_constraints() throws { XCTAssertEqual( PhpVersionNumberCollection From 21a1d6576ebe36e8d7f93037b18cf95e67f3dc9b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 10 Feb 2023 19:36:42 +0100 Subject: [PATCH 10/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index a92fb25..840231e 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2857,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1063; + CURRENT_PROJECT_VERSION = 1064; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2886,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1063; + CURRENT_PROJECT_VERSION = 1064; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3114,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1063; + CURRENT_PROJECT_VERSION = 1064; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3224,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1063; + CURRENT_PROJECT_VERSION = 1064; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From 5dffbf57d1c90b1aa75ecd1ffbfee240c2b5cf7a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 11 Feb 2023 20:46:05 +0100 Subject: [PATCH 11/25] =?UTF-8?q?=F0=9F=91=8C=20Do=20not=20load=20identity?= =?UTF-8?q?=20asynchronously?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Common/Core/Paths.swift | 10 +++------- phpmon/Common/Helpers/System.swift | 24 ++++++++++++++++++++++++ phpmon/Domain/App/AppDelegate.swift | 1 - 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/phpmon/Common/Core/Paths.swift b/phpmon/Common/Core/Paths.swift index 3c25bb4..822ed4d 100644 --- a/phpmon/Common/Core/Paths.swift +++ b/phpmon/Common/Core/Paths.swift @@ -16,16 +16,12 @@ public class Paths { public static let shared = Paths() internal var baseDir: Paths.HomebrewDir - - private var userName: String! = nil + private var userName: String init() { baseDir = App.architecture != "x86_64" ? .opt : .usr - } - - public func loadUser() async { - let output = await Shell.pipe("id -un").out - userName = String(output.split(separator: "\n")[0]) + userName = identity() + Log.info("[ID] The current username is `\(userName)`.") } public func detectBinaryPaths() { diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift index bd54915..d89b484 100644 --- a/phpmon/Common/Helpers/System.swift +++ b/phpmon/Common/Helpers/System.swift @@ -27,6 +27,30 @@ public func system(_ command: String) -> String { return output } +/** Same as the `system` command, but does not return the output. */ public func system_quiet(_ command: String) { _ = system(command) } + +/** + Retrieves the username for the currently signed in user via `/usr/bin/id`. + This cannot fail or the application will crash. + */ +public func identity() -> String { + let task = Process() + task.launchPath = "/usr/bin/id" + task.arguments = ["-un"] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + + guard let output = String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), + encoding: String.Encoding.utf8 + ) else { + fatalError("Could not retrieve username via `id -un`!") + } + + return output.trimmingCharacters(in: .whitespacesAndNewlines) +} diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index 27eba8e..37be68d 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -111,7 +111,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele // Make sure notifications will work setupNotifications() Task { // Make sure the menu performs its initial checks - await paths.loadUser() await menu.startup() } } From 9c6a21008a4d9bac679611e183a062b5cdcb2002 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 13 Feb 2023 17:30:01 +0100 Subject: [PATCH 12/25] =?UTF-8?q?=F0=9F=90=9B=20Adjusted=20for=20new=20Hom?= =?UTF-8?q?ebrew=20JSON=20output=20(#235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/PHP/Homebrew/HomebrewPackage.swift | 2 - phpmon/Domain/App/EnvironmentManager.swift | 3 +- tests/unit/Parsers/HomebrewPackageTest.swift | 5 +- tests/unit/Test Files/brew/brew-formula.json | 143 ++++++++++-------- 4 files changed, 85 insertions(+), 68 deletions(-) diff --git a/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift b/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift index 100a6a3..b250c80 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewPackage.swift @@ -8,8 +8,6 @@ import Foundation struct HomebrewPackage: Decodable { - - let name: String let full_name: String let aliases: [String] let installed: [HomebrewInstalled] diff --git a/phpmon/Domain/App/EnvironmentManager.swift b/phpmon/Domain/App/EnvironmentManager.swift index 9275084..fa527d6 100644 --- a/phpmon/Domain/App/EnvironmentManager.swift +++ b/phpmon/Domain/App/EnvironmentManager.swift @@ -17,7 +17,7 @@ public class EnvironmentManager { // Failure condition #1: does not contain Laravel Valet if !output.contains("Laravel Valet") { - return true + return false } // Extract the version number @@ -25,7 +25,6 @@ public class EnvironmentManager { // Get the actual version return Valet.shared.version == nil - }() // returns true if none of the failure conditions are met } } diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift index a622070..a8a8916 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/Parsers/HomebrewPackageTest.swift @@ -23,11 +23,10 @@ class HomebrewPackageTest: XCTestCase { [HomebrewPackage].self, from: json.data(using: .utf8)! ).first! - XCTAssertEqual(package.name, "php") XCTAssertEqual(package.full_name, "php") - XCTAssertEqual(package.aliases.first!, "php@8.1") + XCTAssertEqual(package.aliases.first!, "php@8.2") XCTAssertEqual(package.installed.contains(where: { installed in - installed.version.starts(with: "8.1") + installed.version.starts(with: "8.2") }), true) } diff --git a/tests/unit/Test Files/brew/brew-formula.json b/tests/unit/Test Files/brew/brew-formula.json index 1029156..895492a 100644 --- a/tests/unit/Test Files/brew/brew-formula.json +++ b/tests/unit/Test Files/brew/brew-formula.json @@ -1,69 +1,77 @@ [ { - "name": "php", "full_name": "php", "tap": "homebrew/core", "oldname": null, "aliases": [ - "php@8.1" + "php@8.2" ], "versioned_formulae": [ + "php@8.1", "php@8.0", - "php@7.4", - "php@7.3", - "php@7.2" + "php@7.4" ], "desc": "General-purpose scripting language", "license": "PHP-3.01", "homepage": "https://www.php.net/", "versions": { - "stable": "8.1.10", + "stable": "8.2.2", "head": "HEAD", "bottle": true }, "urls": { "stable": { - "url": "https://www.php.net/distributions/php-8.1.10.tar.xz", + "url": "https://www.php.net/distributions/php-8.2.2.tar.xz", "tag": null, - "revision": null + "revision": null, + "checksum": "bdc4aa38e652bac86039601840bae01c0c3653972eaa6f9f93d5f71953a7ee33" + }, + "head": { + "url": "https://github.com/php/php-src.git", + "branch": "master" } }, - "revision": 1, + "revision": 0, "version_scheme": 0, "bottle": { "stable": { "rebuild": 0, "root_url": "https://ghcr.io/v2/homebrew/core", "files": { + "arm64_ventura": { + "cellar": "/opt/homebrew/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:ad2e6a6f1cdc65c22b39bd607cbb7305958951cf58ee87d5060717be5a8b5a45", + "sha256": "ad2e6a6f1cdc65c22b39bd607cbb7305958951cf58ee87d5060717be5a8b5a45" + }, "arm64_monterey": { "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c", - "sha256": "dcee33c9f445db3026a7e867805eb8f6d82e9e5599599b8c6cd8645475f7961c" + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:27069c973e63f38a3cb4fad1c7a2e17853bcffe318c8a957ff96a1026dff0cac", + "sha256": "27069c973e63f38a3cb4fad1c7a2e17853bcffe318c8a957ff96a1026dff0cac" }, "arm64_big_sur": { "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e", - "sha256": "e0590064cd32f2baa4102fa49c80056f3886a0a89aec0589d0134ecbf0e7923e" + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:ceef280bcd57e5f794ae59cc75e83d407c9704aa3d238b282bda52cbc644d0dd", + "sha256": "ceef280bcd57e5f794ae59cc75e83d407c9704aa3d238b282bda52cbc644d0dd" + }, + "ventura": { + "cellar": "/usr/local/Cellar", + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:22f733b7b0b0ed95cd6b0a1534b9eca4cf63fe54647394c3f7e7ac019eb019ff", + "sha256": "22f733b7b0b0ed95cd6b0a1534b9eca4cf63fe54647394c3f7e7ac019eb019ff" }, "monterey": { "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c", - "sha256": "62481320613b19c6ff310bf6ed50c7d2a2253cdbf403af12ec97bccd8a97a84c" + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:9ff8f5e1df5e849567cdb2ddea6d3c2a2b9cae024842c9ac65b35a01657bfc37", + "sha256": "9ff8f5e1df5e849567cdb2ddea6d3c2a2b9cae024842c9ac65b35a01657bfc37" }, "big_sur": { "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03", - "sha256": "b34d96f7aad3c580a7cbdaadb8054fb9b6872111a5eec8e1bcb4a529970c8e03" - }, - "catalina": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76", - "sha256": "cc0b85dcfdd60e1d8d7fa74c9f53be5d249d068835dbc7a81edacb7a076b6c76" + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:11fd1ea6da8ef728b7cacd4da8a51ed125069595abf4e37ae1552d418560c5fb", + "sha256": "11fd1ea6da8ef728b7cacd4da8a51ed125069595abf4e37ae1552d418560c5fb" }, "x86_64_linux": { "cellar": "/home/linuxbrew/.linuxbrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae", - "sha256": "b934a5a4ad2d29b629f83962b57f638a654801d1ba21ba659a42da2e5afe3fae" + "url": "https://ghcr.io/v2/homebrew/core/php/blobs/sha256:baaa41e60f9e8125fe8f549d4813a8476a8947a1f10d7817a2ee36d8baa625f3", + "sha256": "baaa41e60f9e8125fe8f549d4813a8476a8947a1f10d7817a2ee36d8baa625f3" } } } @@ -127,34 +135,35 @@ "conflicts_with": [ ], - "caveats": "To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n \n SetHandler application/x-httpd-php\n \n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.1/\n", + "caveats": "To enable PHP in Apache add the following to httpd.conf and restart Apache:\n LoadModule php_module $(brew --prefix)/opt/php/lib/httpd/modules/libphp.so\n\n \n SetHandler application/x-httpd-php\n \n\nFinally, check DirectoryIndex includes index.php\n DirectoryIndex index.php index.html\n\nThe php.ini and php-fpm.ini file can be found in:\n $(brew --prefix)/etc/php/8.2/\n", "installed": [ { - "version": "8.1.10_1", + "version": "8.2.2", "used_options": [ ], "built_as_bottle": true, "poured_from_bottle": true, + "time": 1675654665, "runtime_dependencies": [ { "full_name": "apr", - "version": "1.7.0", + "version": "1.7.2", "declared_directly": true }, { "full_name": "ca-certificates", - "version": "2022-07-19", + "version": "2023-01-10", "declared_directly": false }, { "full_name": "openssl@1.1", - "version": "1.1.1q", + "version": "1.1.1s", "declared_directly": true }, { "full_name": "apr-util", - "version": "1.6.1", + "version": "1.6.3", "declared_directly": true }, { @@ -182,24 +191,24 @@ "version": "1.0.9", "declared_directly": false }, - { - "full_name": "gettext", - "version": "0.21", - "declared_directly": true - }, { "full_name": "libunistring", - "version": "1.0", + "version": "1.1", "declared_directly": false }, + { + "full_name": "gettext", + "version": "0.21.1", + "declared_directly": true + }, { "full_name": "libidn2", - "version": "2.3.3", + "version": "2.3.4", "declared_directly": false }, { "full_name": "libnghttp2", - "version": "1.49.0", + "version": "1.51.0", "declared_directly": false }, { @@ -224,7 +233,7 @@ }, { "full_name": "xz", - "version": "5.2.6", + "version": "5.4.1", "declared_directly": false }, { @@ -234,7 +243,7 @@ }, { "full_name": "curl", - "version": "7.85.0", + "version": "7.87.0", "declared_directly": true }, { @@ -249,12 +258,12 @@ }, { "full_name": "freetds", - "version": "1.3.13", + "version": "1.3.17", "declared_directly": true }, { "full_name": "libpng", - "version": "1.6.37", + "version": "1.6.39", "declared_directly": false }, { @@ -264,12 +273,12 @@ }, { "full_name": "fontconfig", - "version": "2.14.0", + "version": "2.14.2", "declared_directly": false }, { "full_name": "jpeg-turbo", - "version": "2.1.4", + "version": "2.1.5", "declared_directly": false }, { @@ -278,13 +287,13 @@ "declared_directly": false }, { - "full_name": "imath", - "version": "3.1.5", + "full_name": "highway", + "version": "1.0.3", "declared_directly": false }, { - "full_name": "openexr", - "version": "3.1.5", + "full_name": "imath", + "version": "3.1.6", "declared_directly": false }, { @@ -292,14 +301,24 @@ "version": "4.4.0", "declared_directly": false }, + { + "full_name": "little-cms2", + "version": "2.14", + "declared_directly": false + }, + { + "full_name": "openexr", + "version": "3.1.5", + "declared_directly": false + }, { "full_name": "webp", - "version": "1.2.4", + "version": "1.3.0", "declared_directly": false }, { "full_name": "jpeg-xl", - "version": "0.6.1", + "version": "0.8.1", "declared_directly": false }, { @@ -309,12 +328,12 @@ }, { "full_name": "aom", - "version": "3.4.0", + "version": "3.5.0", "declared_directly": false }, { "full_name": "libavif", - "version": "0.10.1", + "version": "0.11.1", "declared_directly": false }, { @@ -329,17 +348,17 @@ }, { "full_name": "icu4c", - "version": "71.1", + "version": "72.1", "declared_directly": true }, { "full_name": "krb5", - "version": "1.20", + "version": "1.20.1", "declared_directly": true }, { "full_name": "libpq", - "version": "14.5", + "version": "15.1", "declared_directly": true }, { @@ -359,17 +378,17 @@ }, { "full_name": "pcre2", - "version": "10.40", + "version": "10.42", "declared_directly": true }, { "full_name": "readline", - "version": "8.1.2", + "version": "8.2.1", "declared_directly": false }, { "full_name": "sqlite", - "version": "3.39.2", + "version": "3.40.1", "declared_directly": true }, { @@ -382,7 +401,7 @@ "installed_on_request": true } ], - "linked_keg": "8.1.10_1", + "linked_keg": "8.2.2", "pinned": false, "outdated": false, "deprecated": false, @@ -390,6 +409,8 @@ "deprecation_reason": null, "disabled": false, "disable_date": null, - "disable_reason": null + "disable_reason": null, + "tap_git_head": "0bbb89420e74756a5a5c145ed7efa4a32f7e7e7c" } - ] + +] From ec49257bccd815564d98d71ef5bf30e1ad74d5d5 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 13 Feb 2023 17:43:52 +0100 Subject: [PATCH 13/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- phpmon/Info.plist | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 840231e..2707f6a 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2857,7 +2857,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1064; + CURRENT_PROJECT_VERSION = 1065; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2886,7 +2886,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1064; + CURRENT_PROJECT_VERSION = 1065; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3114,7 +3114,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1064; + CURRENT_PROJECT_VERSION = 1065; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3224,7 +3224,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1064; + CURRENT_PROJECT_VERSION = 1065; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; diff --git a/phpmon/Info.plist b/phpmon/Info.plist index 7918ee3..fa7a4d3 100644 --- a/phpmon/Info.plist +++ b/phpmon/Info.plist @@ -40,7 +40,7 @@ LSUIElement NSHumanReadableCopyright - Copyright © 2019-2022 Nico Verbruggen. All rights reserved. + Copyright © 2019-2023 Nico Verbruggen. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass From 857cba9f458e5b5ecf1bf270bee28ac70f9ad5ef Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 15 Feb 2023 19:30:55 +0100 Subject: [PATCH 14/25] =?UTF-8?q?=E2=9C=A8=20On=20macOS=2013=20and=20newer?= =?UTF-8?q?,=20add=20"Start=20at=20login"=20(#210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 10 +++ phpmon/Common/Helpers/LoginItemManager.swift | 25 ++++++ phpmon/Domain/Preferences/PrefsVC.swift | 6 +- .../Views/CheckboxPreferenceView.swift | 77 ++++++++++++++++--- .../Views/CheckboxPreferenceView.xib | 4 +- phpmon/Localizable.strings | 4 + 6 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 phpmon/Common/Helpers/LoginItemManager.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 2707f6a..9508892 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -481,6 +481,10 @@ 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 */; }; + C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; + C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; + C47DF1B1299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; + C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */; }; C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; @@ -857,6 +861,7 @@ C47699EE28A2F2A30060FEB8 /* WarningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningManager.swift; sourceTree = ""; }; C47699F028A2F3150060FEB8 /* Warning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warning.swift; sourceTree = ""; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; + C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginItemManager.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = ""; }; @@ -1440,6 +1445,7 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, C4D3660A29113F20006BD146 /* System.swift */, C4D36614291160A1006BD146 /* WIP.swift */, + C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */, ); path = Helpers; sourceTree = ""; @@ -2016,6 +2022,7 @@ files = ( C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */, C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */, + C47DF1AF299D5A3B0007055D /* LoginItemManager.swift in Sources */, C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */, C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, @@ -2224,6 +2231,7 @@ C4D36617291160A1006BD146 /* WIP.swift in Sources */, C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */, C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */, + C47DF1B1299D5A3B0007055D /* LoginItemManager.swift in Sources */, C471E85928F9BB650021E251 /* DomainListPhpCell.swift in Sources */, C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */, C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */, @@ -2355,6 +2363,7 @@ C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */, C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */, + C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */, C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */, C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */, C471E89A28F9BB8F0021E251 /* DomainScanner.swift in Sources */, @@ -2565,6 +2574,7 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */, + C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, C45B914A295607F400F4EC78 /* Service.swift in Sources */, C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, diff --git a/phpmon/Common/Helpers/LoginItemManager.swift b/phpmon/Common/Helpers/LoginItemManager.swift new file mode 100644 index 0000000..bea1aa1 --- /dev/null +++ b/phpmon/Common/Helpers/LoginItemManager.swift @@ -0,0 +1,25 @@ +// +// LoginItemManager.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 15/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import AppKit +import ServiceManagement + +@available(macOS 13.0, *) +class LoginItemManager { + func loginItemIsEnabled() -> Bool { + return SMAppService.mainApp.status == .enabled + } + + func disableLoginItem() { + try? SMAppService.mainApp.unregister() + } + + func enableLoginItem() { + try? SMAppService.mainApp.register() + } +} diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 4bb5ab4..87594c7 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -231,7 +231,7 @@ class GeneralPreferencesVC: GenericPreferenceVC { // MARK: - Lifecycle public static func fromStoryboard() -> GenericPreferenceVC { - let vc = NSStoryboard(name: "Main", bundle: nil) + var vc = NSStoryboard(name: "Main", bundle: nil) .instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC vc.views = [ @@ -243,6 +243,10 @@ class GeneralPreferencesVC: GenericPreferenceVC { vc.getAutomaticUpdateCheckPV() ] + if #available(macOS 13, *) { + vc.views.append(CheckboxPreferenceView.makeLoginItemView()) + } + return vc } } diff --git a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift index d70c7f2..eee6b21 100644 --- a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift +++ b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift @@ -10,18 +10,12 @@ import Foundation import Cocoa class CheckboxPreferenceView: NSView, XibLoadable { - @IBOutlet weak var labelSection: NSTextField! @IBOutlet weak var labelDescription: NSTextField! @IBOutlet weak var buttonCheckbox: NSButton! var action: (() -> Void)! - - var preference: PreferenceName! { - didSet { - self.buttonCheckbox.state = Preferences.isEnabled(self.preference) ? .on : .off - } - } + var behavior: CheckboxPreferenceViewBehavior! static func make( sectionText: String, @@ -31,17 +25,78 @@ class CheckboxPreferenceView: NSView, XibLoadable { action: @escaping () -> Void ) -> NSView { let view = Self.createFromXib()! + view.behavior = CheckboxPreferenceBehavior( + button: view.buttonCheckbox, + preference: preference + ) view.labelSection.stringValue = sectionText view.labelDescription.stringValue = descriptionText view.buttonCheckbox.title = checkboxText - view.preference = preference view.action = action return view } - @IBAction func toggled(_ sender: Any) { - Preferences.update(self.preference, value: buttonCheckbox.state == .on) - self.action() + @available(macOS 13.0, *) + static func makeLoginItemView() -> NSView { + let view = Self.createFromXib()! + view.behavior = CheckboxLaunchItemBehavior(button: view.buttonCheckbox) + view.labelSection.stringValue = "prefs.startup".localized + view.labelDescription.stringValue = "prefs.auto_start_desc".localized + view.buttonCheckbox.title = "prefs.auto_start_title".localized + view.action = {} + return view } + @IBAction func toggled(_ sender: Any) { + self.behavior.toggled(checked: buttonCheckbox.state == .on) + self.action() + } +} + +protocol CheckboxPreferenceViewBehavior { + func toggled(checked: Bool) +} + +class CheckboxPreferenceBehavior: CheckboxPreferenceViewBehavior { + var button: NSButton + var preference: PreferenceName { + didSet { + button.state = Preferences.isEnabled(self.preference) ? .on : .off + } + } + + init(button: NSButton, preference: PreferenceName) { + self.button = button + self.preference = preference + } + + public func toggled(checked: Bool) { + Preferences.update(self.preference, value: checked) + } +} + +@available(macOS 13.0, *) +class CheckboxLaunchItemBehavior: CheckboxPreferenceViewBehavior { + var manager = LoginItemManager() + var button: NSButton + + init(button: NSButton) { + self.button = button + + if manager.loginItemIsEnabled() { + self.button.state = .on + } else { + self.button.state = .off + } + } + + public func toggled(checked: Bool) { + if checked { + self.manager.enableLoginItem() + } else { + self.manager.disableLoginItem() + } + + self.button.state = self.manager.loginItemIsEnabled() ? .on : .off + } } diff --git a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.xib b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.xib index 589e766..42fd99a 100644 --- a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.xib +++ b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.xib @@ -1,8 +1,8 @@ - + - + diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 4aa07bc..991894d 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -254,6 +254,10 @@ This has no effect on other terminals, only for the particular terminal session "prefs.notifications" = "Notifications:"; "prefs.warnings" = "Warnings:"; "prefs.menu_contents" = "Features in Menu:"; +"prefs.startup" = "Startup:"; + +"prefs.auto_start_desc" = "Automatically starts PHP Monitor when you log into your Mac."; +"prefs.auto_start_title" = "Start PHP Monitor at login"; "prefs.icon_options.php" = "Display PHP Icon"; "prefs.icon_options.elephant" = "Display Elephant Icon"; From f82a2120f7b4a9c4d03063c7b082f1e3403f71d6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 15 Feb 2023 20:32:37 +0100 Subject: [PATCH 15/25] =?UTF-8?q?=F0=9F=94=A7=20Add=20display=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9508892..4e876d1 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2873,6 +2873,7 @@ DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -2902,6 +2903,7 @@ DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -3129,6 +3131,7 @@ DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -3239,6 +3242,7 @@ DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", From c93f04790908d1e28f452b66224b2d300138b541 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Wed, 15 Feb 2023 20:32:56 +0100 Subject: [PATCH 16/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 4e876d1..e15ecc6 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2867,7 +2867,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1065; + CURRENT_PROJECT_VERSION = 1070; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2897,7 +2897,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1065; + CURRENT_PROJECT_VERSION = 1070; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3126,7 +3126,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1065; + CURRENT_PROJECT_VERSION = 1070; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3237,7 +3237,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1065; + CURRENT_PROJECT_VERSION = 1070; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From c7c5311ff94c8b4ca6991ba19f4c76d4ef8c0863 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 17 Feb 2023 17:19:56 +0100 Subject: [PATCH 17/25] =?UTF-8?q?=F0=9F=90=9B=20Ensure=20checkbox=20shows?= =?UTF-8?q?=20correct=20initial=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Preferences/PrefsVC.swift | 2 +- .../Preferences/Views/CheckboxPreferenceView.swift | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 87594c7..8091174 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -231,7 +231,7 @@ class GeneralPreferencesVC: GenericPreferenceVC { // MARK: - Lifecycle public static func fromStoryboard() -> GenericPreferenceVC { - var vc = NSStoryboard(name: "Main", bundle: nil) + let vc = NSStoryboard(name: "Main", bundle: nil) .instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC vc.views = [ diff --git a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift index eee6b21..a5e8f3e 100644 --- a/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift +++ b/phpmon/Domain/Preferences/Views/CheckboxPreferenceView.swift @@ -59,15 +59,12 @@ protocol CheckboxPreferenceViewBehavior { class CheckboxPreferenceBehavior: CheckboxPreferenceViewBehavior { var button: NSButton - var preference: PreferenceName { - didSet { - button.state = Preferences.isEnabled(self.preference) ? .on : .off - } - } + var preference: PreferenceName init(button: NSButton, preference: PreferenceName) { - self.button = button self.preference = preference + self.button = button + self.button.state = Preferences.isEnabled(self.preference) ? .on : .off } public func toggled(checked: Bool) { From 15d75a7f983ccab942e6b857a0b1b3ad3eb33ade Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 17 Feb 2023 17:21:13 +0100 Subject: [PATCH 18/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index e15ecc6..93a9ec5 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2867,7 +2867,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1070; + CURRENT_PROJECT_VERSION = 1071; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2897,7 +2897,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1070; + CURRENT_PROJECT_VERSION = 1071; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3126,7 +3126,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1070; + CURRENT_PROJECT_VERSION = 1071; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3237,7 +3237,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1070; + CURRENT_PROJECT_VERSION = 1071; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From cc6324b692ac4923b5e4da2ae0861b90a80d4b21 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 20 Feb 2023 18:13:51 +0100 Subject: [PATCH 19/25] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20PHP=20Guard=20change?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Notice/BetterAlert.swift | 1 + phpmon/Domain/Preferences/Stats.swift | 74 ++++++++++++++------------ 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index 33ad04c..1f2ccdc 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -9,6 +9,7 @@ import Foundation import Cocoa +@MainActor class BetterAlert { var windowController: NSWindowController! diff --git a/phpmon/Domain/Preferences/Stats.swift b/phpmon/Domain/Preferences/Stats.swift index a861d03..defb163 100644 --- a/phpmon/Domain/Preferences/Stats.swift +++ b/phpmon/Domain/Preferences/Stats.swift @@ -142,42 +142,50 @@ class Stats { } public static func evaluateLastLinkedPhpVersion() { - let currentVersion = PhpEnv.phpInstall.version.short + let currentVersion = PhpEnv.phpInstall.version?.short ?? "" let previousVersion = Stats.lastGlobalPhpVersion - // Save the PHP version that is currently in use (only if unknown) - if Stats.lastGlobalPhpVersion == "" { + if currentVersion == "" { + return Log.warn(" PHP Guard is unable to determine the current PHP version!") + } + Log.info(" The currently linked version of PHP is: \(currentVersion).") + + if previousVersion == "" { Stats.persistCurrentGlobalPhpVersion(version: currentVersion) - Log.info("Persisting the currently linked PHP version (first time only).") - } else { - Log.info("Previously, the globally linked PHP version was: \(previousVersion).") - if previousVersion != currentVersion { - Log.info("Currently, that version is: \(currentVersion). This is a mismatch.") - Task { @MainActor in - BetterAlert() - .withInformation( - title: "startup.version_mismatch.title".localized, - subtitle: "startup.version_mismatch.subtitle".localized( - currentVersion, - previousVersion - ), - description: "startup.version_mismatch.desc".localized() - ) - .withPrimary(text: "startup.version_mismatch.button_switch_back".localized( - previousVersion - ), action: { alert in - alert.close(with: .OK) - Task { MainMenu.shared.switchToAnyPhpVersion(previousVersion) } - }) - .withTertiary(text: "startup.version_mismatch.button_stay".localized( - currentVersion - ), action: { alert in - Stats.persistCurrentGlobalPhpVersion(version: currentVersion) - alert.close(with: .OK) - }) - .show() - } - } + return Log.warn(" PHP Guard is saving the currently linked PHP version (first time only).") + } + Log.info(" Previously, the globally linked PHP version was: \(previousVersion).") + + if previousVersion == currentVersion { + return Log.info(" PHP Guard did not notice any changes in the linked PHP version.") + } + + // At this point, the version is *not* a match + Log.info(" PHP Guard noticed a different PHP version. An alert will be displayed!") + + Task { @MainActor in + BetterAlert() + .withInformation( + title: "startup.version_mismatch.title".localized, + subtitle: "startup.version_mismatch.subtitle".localized( + currentVersion, + previousVersion + ), + description: "startup.version_mismatch.desc".localized() + ) + .withPrimary(text: "startup.version_mismatch.button_switch_back".localized( + previousVersion + ), action: { alert in + alert.close(with: .OK) + Task { MainMenu.shared.switchToAnyPhpVersion(previousVersion) } + }) + .withTertiary(text: "startup.version_mismatch.button_stay".localized( + currentVersion + ), action: { alert in + Stats.persistCurrentGlobalPhpVersion(version: currentVersion) + alert.close(with: .OK) + }) + .show() } } } From 2f47610c8504367b99e553523a7b8e9d5d64549b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 21 Feb 2023 02:24:49 +0100 Subject: [PATCH 20/25] =?UTF-8?q?=F0=9F=94=A7=20Use=20macOS=2012.4=20deplo?= =?UTF-8?q?yment=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 16 ++++++++-------- README.md | 2 +- SECURITY.md | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 93a9ec5..22cf1f7 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2792,7 +2792,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2849,7 +2849,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -2879,7 +2879,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2909,7 +2909,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3108,7 +3108,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -3137,7 +3137,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME) DEV"; @@ -3218,7 +3218,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3248,7 +3248,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 5.8; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/README.md b/README.md index abd66dd..6822683 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ You can also add new domains as links, isolate sites, manage various services, a PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs. * Your user account can administer your computer (required for some functionality, e.g. certificate generation) -* macOS 11 Big Sur or later +* macOS 12.4 or later (Monterey and Ventura are supported) * Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew` * Homebrew `php` formula is installed * Laravel Valet (works with Valet v2, v3 and v4) diff --git a/SECURITY.md b/SECURITY.md index 2255dca..8a7f308 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- -| 5.7 | ✅ Universal binary | ✅ Yes | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.1-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended
2.16.2 minimum | +| 5.8 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)
Ventura (13.0+) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x)
PHP 7.1-PHP 8.2 (w/ Valet 4.x*) | 3.0 or higher recommended
2.16.2 minimum | (*) Preliminary listing. Valet 4 hasn't been released yet and the versions of PHP Valet can work with might still change. @@ -16,6 +16,7 @@ These versions of PHP Monitor are no longer supported, but if you’re using an | Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version | | ------- | ------------- | ------------------ | ----- | ----- | ----- | ---- +| 5.7 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | | 5.6 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0)
Ventura (13.0)* | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)
PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended
2.16.2 minimum | | 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 | | 4.0 | ✅ Universal binary | ❌ | Big Sur (11.0)
Monterey (12.0) | macOS 10.14+ | PHP 5.6—PHP 8.2 | 2.13 | From 7c08a2fdfeae0243be3cd1dde640ddc5cdd5e9eb Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 24 Feb 2023 19:36:12 +0100 Subject: [PATCH 21/25] =?UTF-8?q?=F0=9F=91=8C=20Improve=20PHP=20Doctor=20w?= =?UTF-8?q?/=20async=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This fixes an issue with PHP Doctor's "Scan Again" button not working. - This also adds a new check which verifies if "php.ini", "php-fpm.conf" and "php-fpm.d/valet-fpm.conf" exist (all required files). --- PHP Monitor.xcodeproj/project.pbxproj | 12 +++++ .../SwiftUI/Warning/WarningListView.swift | 18 ++++--- .../Domain/SwiftUI/Warning/WarningView.swift | 2 +- .../Warnings/Services/PhpConfigChecker.swift | 39 +++++++++++++++ phpmon/Domain/Warnings/Warning.swift | 22 +++++++-- phpmon/Domain/Warnings/WarningManager.swift | 47 +++++++++++++------ phpmon/Localizable.strings | 7 +++ 7 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 phpmon/Domain/Warnings/Services/PhpConfigChecker.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 22cf1f7..4087eb3 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -112,6 +112,7 @@ C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; }; C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; }; C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */; }; + C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; }; C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* DomainListNameCell.swift */; }; C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; @@ -811,6 +812,7 @@ C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = ""; }; C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackageTest.swift; sourceTree = ""; }; + C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigChecker.swift; sourceTree = ""; }; C44067F427E2582B0045BD4E /* DomainListNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListNameCell.swift; sourceTree = ""; }; C44067F627E258410045BD4E /* DomainListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListPhpCell.swift; sourceTree = ""; }; C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTypeCell.swift; sourceTree = ""; }; @@ -1219,6 +1221,7 @@ C422DDAB28A2DAA100CEAC97 /* Warnings */ = { isa = PBXGroup; children = ( + C43FDBE729A9329A003D85EC /* Services */, C47699F028A2F3150060FEB8 /* Warning.swift */, C47699EE28A2F2A30060FEB8 /* WarningManager.swift */, C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */, @@ -1244,6 +1247,14 @@ path = Warning; sourceTree = ""; }; + C43FDBE729A9329A003D85EC /* Services */ = { + isa = PBXGroup; + children = ( + C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */, + ); + path = Services; + sourceTree = ""; + }; C44067F327E256560045BD4E /* Cells */ = { isa = PBXGroup; children = ( @@ -2155,6 +2166,7 @@ C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */, C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */, C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, + C43FDBE929A932B0003D85EC /* PhpConfigChecker.swift in Sources */, C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */, C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */, C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */, diff --git a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift index 0c24b8b..7ccd9e8 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningListView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningListView.swift @@ -9,10 +9,14 @@ import SwiftUI struct WarningListView: View { - @State var warnings: [Warning] + @ObservedObject var warningManager: WarningManager init(empty: Bool = false) { - self.warnings = empty ? [] : WarningManager.shared.warnings + if empty { + WarningManager.shared.warnings = [] + } + + warningManager = WarningManager.shared } var body: some View { @@ -40,7 +44,6 @@ struct WarningListView: View { Button("warnings.refresh.button".localizedForSwiftUI) { Task { // Reload warnings await WarningManager.shared.checkEnvironment() - self.warnings = WarningManager.shared.warnings } } Text("warnings.refresh.button.description".localizedForSwiftUI) @@ -51,14 +54,14 @@ struct WarningListView: View { List { VStack(alignment: .leading, spacing: 0) { - if warnings.isEmpty { + if warningManager.warnings.isEmpty { NoWarningsView() } else { - ForEach(warnings) { warning in + ForEach(warningManager.warnings) { warning in Group { WarningView( title: warning.title, - paragraphs: warning.paragraphs, + paragraphs: warning.paragraphs(), documentationUrl: warning.url ) .fixedSize(horizontal: false, vertical: true) @@ -67,7 +70,8 @@ struct WarningListView: View { }.padding(5) } } - }.frame(minHeight: 0, maxHeight: .infinity).padding(5) + } + .frame(minHeight: 0, maxHeight: .infinity).padding(5) } .listRowInsets(EdgeInsets()) .listStyle(.plain) diff --git a/phpmon/Domain/SwiftUI/Warning/WarningView.swift b/phpmon/Domain/SwiftUI/Warning/WarningView.swift index 446e4f5..1fa4241 100644 --- a/phpmon/Domain/SwiftUI/Warning/WarningView.swift +++ b/phpmon/Domain/SwiftUI/Warning/WarningView.swift @@ -26,7 +26,7 @@ struct WarningView: View { Text(title.localizedForSwiftUI) .fontWeight(.bold) ForEach(paragraphs, id: \.self) { paragraph in - Text(paragraph.localizedForSwiftUI) + Text(paragraph) .font(.system(size: 13)) } } diff --git a/phpmon/Domain/Warnings/Services/PhpConfigChecker.swift b/phpmon/Domain/Warnings/Services/PhpConfigChecker.swift new file mode 100644 index 0000000..656959f --- /dev/null +++ b/phpmon/Domain/Warnings/Services/PhpConfigChecker.swift @@ -0,0 +1,39 @@ +// +// PhpConfigChecker.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 24/02/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class PhpConfigChecker { + + public static var shared = PhpConfigChecker() + + var missing: [String] = [] + + public func check() { + missing = [] + + let shouldExist = [ + "php.ini", + "php-fpm.conf", + "php-fpm.d/valet-fpm.conf" + ] + + for version in PhpEnv.shared.availablePhpVersions { + for file in shouldExist { + let fullFilePath = Paths.etcPath.appending("/php/\(version)/\(file)") + if !FileSystem.fileExists(fullFilePath) { + missing.append(fullFilePath) + } + } + } + + if !missing.isEmpty { + Log.warn("The following config file(s) were missing: \(missing)") + } + } +} diff --git a/phpmon/Domain/Warnings/Warning.swift b/phpmon/Domain/Warnings/Warning.swift index 8bf3ec2..cb9cd2f 100644 --- a/phpmon/Domain/Warnings/Warning.swift +++ b/phpmon/Domain/Warnings/Warning.swift @@ -8,19 +8,27 @@ import Foundation -struct Warning: Identifiable { +struct Warning: Identifiable, Hashable { var id = UUID() let command: () async -> Bool let name: String let title: String - let paragraphs: [String] + let paragraphs: () -> [String] let url: String? + /** + - Parameters: + - command: The command that, if it returns true, means that a warning applies + - name: The internal name or description for this warning + - title: The title displayed for the user + - paragraphs: The main body of text displayed for the user + - url: The URL that one can navigate to for more information (if applicable) + */ init( command: @escaping () async -> Bool, name: String, title: String, - paragraphs: [String], + paragraphs: @escaping () -> [String], url: String? ) { self.command = command @@ -33,4 +41,12 @@ struct Warning: Identifiable { public func applies() async -> Bool { return await self.command() } + + public static func == (lhs: Warning, rhs: Warning) -> Bool { + return lhs.hashValue == rhs.hashValue + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } } diff --git a/phpmon/Domain/Warnings/WarningManager.swift b/phpmon/Domain/Warnings/WarningManager.swift index d001dab..da9d8fd 100644 --- a/phpmon/Domain/Warnings/WarningManager.swift +++ b/phpmon/Domain/Warnings/WarningManager.swift @@ -9,9 +9,9 @@ import Foundation import Cocoa -class WarningManager { +class WarningManager: ObservableObject { - static var shared = WarningManager() + static var shared: WarningManager = WarningManager() init() { if isRunningSwiftUIPreview { @@ -26,8 +26,8 @@ class WarningManager { .trimmingCharacters(in: .whitespacesAndNewlines) == "1" }, name: "Running PHP Monitor with Rosetta on M1", - title: "warnings.arm_compatibility.title", - paragraphs: ["warnings.arm_compatibility.description"], + title: "warnings.arm_compatibility.title".localized, + paragraphs: { return ["warnings.arm_compatibility.description".localized] }, url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-and-Apple-Silicon" ), Warning( @@ -36,13 +36,27 @@ class WarningManager { !FileSystem.isWriteableFile("/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" - ], + title: "warnings.helper_permissions.title".localized, + paragraphs: { return [ + "warnings.helper_permissions.description".localized, + "warnings.helper_permissions.unavailable".localized, + "warnings.helper_permissions.symlink".localized + ] }, url: "https://github.com/nicoverbruggen/phpmon/wiki/PHP-Monitor-helper-binaries" + ), + Warning( + command: { + PhpConfigChecker.shared.check() + return !PhpConfigChecker.shared.missing.isEmpty + }, + name: "Your PHP installation is missing configuration files", + title: "warnings.files_missing.title".localized, + paragraphs: { return [ + "warnings.files_missing.description".localized( + PhpConfigChecker.shared.missing.joined(separator: "\n• ") + ) + ] }, + url: nil ) ] @@ -60,11 +74,11 @@ class WarningManager { 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 + Task { @MainActor in + self.warnings = self.evaluations + } } else { // Otherwise, loop over the actual evaluations and list the warnings await loopOverEvaluations() @@ -74,9 +88,14 @@ class WarningManager { } private func loopOverEvaluations() async { + Task { @MainActor in + self.warnings = [] + } for check in self.evaluations where await check.applies() { Log.info("[DOCTOR] \(check.name) (!)") - self.warnings.append(check) + Task { @MainActor in + self.warnings.append(check) + } continue } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 991894d..12bb209 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -661,6 +661,13 @@ COMMON TROUBLESHOOTING TIPS "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.files_missing.title" = "Your PHP installation is lacking required configuration files"; +"warnings.files_missing.description" = "The following files normally exist on a functional system: + +• %@ + +When files like these are missing, it's recommended to reinstall the appropriate PHP version(s) via Homebrew again, which should restore the configuration files that are missing. Missing configuration files can be the reason why you get '502 Bad Gateway' errors, even after running Fix My Valet."; + "warnings.none" = "There are no recommendations available for you right now. You're all good!"; // ONBOARDING From cb504cc3f00d3bd53ca29074d03667acb5a4f389 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 24 Feb 2023 19:37:41 +0100 Subject: [PATCH 22/25] =?UTF-8?q?=F0=9F=94=A7=20Bump=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 4087eb3..3ae8197 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -2879,7 +2879,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1071; + CURRENT_PROJECT_VERSION = 1074; DEAD_CODE_STRIPPING = YES; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; @@ -2909,7 +2909,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1071; + CURRENT_PROJECT_VERSION = 1074; DEAD_CODE_STRIPPING = YES; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; @@ -3138,7 +3138,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1071; + CURRENT_PROJECT_VERSION = 1074; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -3249,7 +3249,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1071; + CURRENT_PROJECT_VERSION = 1074; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; From 715b674929adfafe61fd3e5c9bacf5a99af6a768 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sun, 26 Feb 2023 14:45:15 +0100 Subject: [PATCH 23/25] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 6 ++++++ phpmon/Domain/App/AppVersion.swift | 2 +- phpmon/Domain/Integrations/Homebrew/CaskFile.swift | 3 +++ tests/Shared/TestableConfigurations.swift | 2 +- tests/unit/Versions/ValetVersionExtractorTest.swift | 6 ------ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 3ae8197..6ade720 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -710,6 +710,9 @@ 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 */; }; + C4FD87A529AB98720002D701 /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; }; + C4FD87A629AB98730002D701 /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; }; + C4FD87A729AB98730002D701 /* PhpConfigChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */; }; C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; /* End PBXBuildFile section */ @@ -2221,6 +2224,7 @@ C471E84328F9BB650021E251 /* App.swift in Sources */, C4E2E85E28FC282B003B070C /* TestableConfiguration.swift in Sources */, C491997D29901DF7001F3A21 /* CaskFile.swift in Sources */, + C4FD87A629AB98730002D701 /* PhpConfigChecker.swift in Sources */, C45E2A7529199248005C7CFD /* InternalSwitcherTest.swift in Sources */, C471E84428F9BB650021E251 /* App+ActivationPolicy.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, @@ -2370,6 +2374,7 @@ C471E89028F9BB8F0021E251 /* AlertableError.swift in Sources */, C471E89128F9BB8F0021E251 /* Errors.swift in Sources */, C471E89228F9BB8F0021E251 /* Alert.swift in Sources */, + C4FD87A529AB98720002D701 /* PhpConfigChecker.swift in Sources */, C471E89328F9BB8F0021E251 /* Application.swift in Sources */, C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */, C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */, @@ -2618,6 +2623,7 @@ C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */, C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */, + C4FD87A729AB98730002D701 /* PhpConfigChecker.swift in Sources */, C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */, C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, diff --git a/phpmon/Domain/App/AppVersion.swift b/phpmon/Domain/App/AppVersion.swift index 5d020af..5bd3303 100644 --- a/phpmon/Domain/App/AppVersion.swift +++ b/phpmon/Domain/App/AppVersion.swift @@ -15,7 +15,7 @@ class AppVersion: Comparable { init(version: String, build: String?, suffix: String? = nil) { self.version = version - self.build = Int(build ?? "0") + self.build = build == nil ? nil : Int(build!) self.suffix = suffix } diff --git a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift index c2ddf0b..a929b36 100644 --- a/phpmon/Domain/Integrations/Homebrew/CaskFile.swift +++ b/phpmon/Domain/Integrations/Homebrew/CaskFile.swift @@ -40,6 +40,9 @@ struct CaskFile { let lines = string.split(separator: "\n") .filter { $0 != "" } + .map { line in + return line.trimmingCharacters(in: .whitespacesAndNewlines) + } if lines.count < 4 { Log.err("The CaskFile is <4 lines long, which is too short") diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index fab4d04..e0ae3ec 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -138,7 +138,7 @@ class TestableConfigurations { : .instant(ShellStrings.shared.brewServicesAsRoot), "/opt/homebrew/bin/brew services info --all --json" : .instant(ShellStrings.shared.brewServicesAsUser), - "curl -s --max-time 5 '\(Constants.Urls.StableBuildCaskFile.absoluteString)' | grep version" + "curl -s --max-time 10 '\(Constants.Urls.DevBuildCaskFile.absoluteString)'" : .instant("version '5.6.2_976'"), "/opt/homebrew/bin/brew unlink php" : .delayed(0.2, "OK"), diff --git a/tests/unit/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift index 769e692..cb294cc 100644 --- a/tests/unit/Versions/ValetVersionExtractorTest.swift +++ b/tests/unit/Versions/ValetVersionExtractorTest.swift @@ -37,10 +37,4 @@ class ValetVersionExtractorTest: XCTestCase { XCTAssertEqual(version.major, 3) } - - func test_can_determine_valet_version() async { - let version = await valet("--version", sudo: false) - XCTAssert(version.contains("Laravel Valet 2") || version.contains("Laravel Valet 3")) - } - } From a07881a98747318be156d4037020ebbe9eb2beed Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 27 Feb 2023 19:04:50 +0100 Subject: [PATCH 24/25] =?UTF-8?q?=F0=9F=91=8C=20Fix=20copy=20of=20missing?= =?UTF-8?q?=20config=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 12bb209..04260a2 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -661,8 +661,8 @@ COMMON TROUBLESHOOTING TIPS "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.files_missing.title" = "Your PHP installation is lacking required configuration files"; -"warnings.files_missing.description" = "The following files normally exist on a functional system: +"warnings.files_missing.title" = "Your PHP installation is missing important required configuration files."; +"warnings.files_missing.description" = "The following key configuration files should exist after installing PHP: • %@ From f274e9e0040903f9b10d7df38651c8db54fab2db Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Mon, 27 Feb 2023 19:08:04 +0100 Subject: [PATCH 25/25] =?UTF-8?q?=F0=9F=91=8C=20Updated=20IAP=20for=20acce?= =?UTF-8?q?ssing=20formulae.brew.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ phpmon/IAP/InternetAccessPolicy.plist | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6822683..38173ab 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,8 @@ This problem is usually resolved by upgrading Valet and running `valet install` composer global update valet install + +If you are seeing a 502 (Bad Gateway) error after about 30 seconds or so, your request is likely timing out. You may need to solve a performance issue with your own code. diff --git a/phpmon/IAP/InternetAccessPolicy.plist b/phpmon/IAP/InternetAccessPolicy.plist index 5d5a97c..bdcfa16 100644 --- a/phpmon/IAP/InternetAccessPolicy.plist +++ b/phpmon/IAP/InternetAccessPolicy.plist @@ -10,6 +10,22 @@ https://github.com/nicoverbruggen/phpmon Connections + + IsIncoming + + Host + github.com, api.github.com + NetworkProtocol + TCP + Port + 443 + Relevance + Essential + Purpose + PHP Monitor directly invokes Homebrew which contacts GitHub. This happens when PHP Monitor asks for more information about the PHP formula to determine which version of PHP you've got running. + DenyConsequences + If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail. + IsIncoming @@ -46,15 +62,15 @@ IsIncoming Host - github.com, api.github.com + formulae.brew.sh NetworkProtocol TCP Port - 443 + 80, 443 Relevance Essential Purpose - PHP Monitor directly invokes Homebrew which contacts GitHub. This happens when PHP Monitor asks for more information about the PHP formula to determine which version of PHP you've got running. + PHP Monitor directly invokes Homebrew which contacts the Homebrew API. This happens when PHP Monitor asks for more information about the PHP formula to determine which version of PHP you've got running. DenyConsequences If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail.