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

Compare commits

...

62 Commits
v7.0.5 ... main

Author SHA1 Message Date
80bcbd085e 🚀 Version 25.07 2025-07-28 11:23:01 +02:00
ca65fca77d 🐛 Fix issue with securing domains
If you serve a single folder locally multiple times, e.g. as
`cdn.mydomain.test` and `mydomain.test`, securing would fail
for domain that came alphabetically last.

This has been resolved if you are running Valet 3 or newer by
leveraging the `valet secure $domain` syntax.
2025-07-28 11:22:23 +02:00
96975f8e57 ♻️ Clean up startup timers 2025-07-24 12:32:22 +02:00
729c1e8f2f Add driver to main menu 2025-07-23 17:24:54 +02:00
e94377ebb1 🐛 Prevent timeout message from showing incorrectly 2025-07-23 16:49:54 +02:00
769779970b 🔧 Bump build for EAP 2025-06-27 21:05:19 +02:00
2a27989a96 Detect Tempest framework 2025-06-27 20:59:26 +02:00
79430f7581 🚀 Version 25.06 2025-06-27 19:35:10 +02:00
76f585a50e 🍱 Updated icon asset 2025-06-21 20:38:36 +02:00
1bbc77967b 🍱 New EAP icon for new build 2025-06-20 16:00:58 +02:00
1e4e2afe68 🔧 Bump build 2025-06-20 15:21:14 +02:00
3caf90b0cd 🔧 Update schemas 2025-06-20 15:19:57 +02:00
445acffa4c 🍱 Further design tweaks 2025-06-20 12:56:58 +02:00
e90c068e95 🍱 Tweak design of icon, unify 2025-06-20 12:18:57 +02:00
c9428c300c 🍱 Apply fill to icon 2025-06-20 11:41:02 +02:00
807d9c55a4 🔧 Bump build for EAP 2025-06-19 13:29:52 +02:00
ca167537cf 🍱 Add icons to context menu 2025-06-19 13:28:58 +02:00
8ae2031ba5 🍱 Add icons to main menu 2025-06-19 12:50:04 +02:00
ec0ad13ad0 🔧 Bump build for EAP 2025-06-17 12:12:37 +02:00
9988b775c9 🐛 Add spinner back to domain list 2025-06-17 12:01:25 +02:00
8e24851014 🍱 Add icon w/ Icon Composer 2025-06-10 19:45:43 +02:00
c1d90cb909 🔧 Plan for version 25 later this year
PHP Monitor 25 will use a new numbering scheme, following the format:

25.06 = 25 . 06 . 0
        M    m    p

(Minor, minor and patch.)

So what was supposed to be PHP Monitor 7.3 will now be version 25, with
the minor version number decided by the release month.

The cut-off date for PHP Monitor 7.2 is currently 2025-11-30, so I have
time but I'd prefer to have two releases out this year:

- One release migrating to the new numbering scheme
- One release with full support for macOS 26

Since I plan on only doing maintenance releases with no patches except
when bugs pop up, the major/minor version notation combination should
suffice.

With this release, the minimum supported version of macOS will become
macOS Ventura 13.5.

That means that support is dropped for Monterey and Ventura's older
builds. Since Homebrew is also changing with built-in service management
I think it's time to drop support for these older versions of macOS,
which I no longer intend to test for.

Older versions of PHP Monitor will, of course, remain functional, but
since Homebrew is an ever-changing thing, I cannot guarantee nothing
will break in those older versions with the newer API.
2025-06-10 18:33:50 +02:00
4827c4a44b 🚀 Version 7.2 2025-03-22 12:05:54 +01:00
201554b237 🔧 Bump version to 7.2 2025-03-22 11:33:52 +01:00
e4bf6a9655 📝 Bump build, update credits and add sponsors 2025-03-22 11:32:59 +01:00
f9ea654ffc 🔧 Tweak brew output parsing
Improved the accuracy of the brew output. Often, when multiple console
messages were returned, the progress prompt in the PHP version manager
would display the earliest found step, not the latest, thus unfortunately
misrepresenting the progress of the installation steps.

This fixes that by reversing the return order, but also extracts
relevant information from the commands, too, so that contextual info
is now included (for pouring, installing and downloading steps).

(This makes it a little bit more transparent for the end user to find
out what is taking up all this time. I wish that Homebrew was faster,
too, but there's a reason I'm not using statically compiled PHP for
this project. Either way, this is a nice QoL change.)
2025-03-22 11:15:47 +01:00
8677628850 🔧 Bump build for new EAP release 2025-03-01 12:59:09 +01:00
e09b0156df 📝 Update README 2025-03-01 12:58:36 +01:00
41a83b1d91 🔨 Update .gitignore 2025-02-28 13:20:32 +01:00
a9e97b0bc9 🌐 Add Indonesian translations
These translations were added via a locally ran LLM.
If you identify any mistakes, please get in touch!
2025-02-21 13:27:39 +01:00
e4e12799ef 🌐 Add Spanish translations
These translations were added via a locally ran LLM. I know some Spanish, but
if you identify any mistakes, please get in touch!
2025-02-20 19:54:04 +01:00
4e25fffa7d 🌐 Add missing translations 2025-02-20 19:21:25 +01:00
3e319cd50f 🚀 Version 7.1 2024-11-26 15:24:18 +01:00
595dc8c028 Add info about test failures 2024-11-26 15:22:03 +01:00
f7b1679e97 🐛 Serial dispatch queue for test FS 2024-11-26 14:17:06 +01:00
9f1761d68e Checked and updated tests 2024-11-26 13:44:08 +01:00
871480d70c 📝 Updated README.md, SECURITY.md 2024-11-26 12:56:38 +01:00
2b1c1c12f8 Add info button for PHP upgrades 2024-11-25 16:43:19 +01:00
a22346ed35 🐛 Fix issue with formulae upgrades for tap 2024-11-25 14:04:17 +01:00
e3fa34d4f9 🔨 Adjust URL for unavailable PHP (wiki) 2024-11-25 13:40:02 +01:00
3d225ea79f ✍️ Clarify text about upgrading PHP (EN only) 2024-11-22 19:09:10 +01:00
d2cd387c18 Add button that redirects to wiki 2024-11-22 18:23:28 +01:00
48bb782e33 🚧 WIP: Changes related to unavailable formulae 2024-11-22 17:42:28 +01:00
9710ffa8da 🚧 WIP: Handle temporarily unavailable formulae 2024-11-22 13:26:09 +01:00
46408f5ee5 Indicate DEV builds in PHP Version Manager 2024-11-15 16:43:59 +01:00
2c39f1db8b 🔧 Upgrade checks for Xcode 16.1 2024-11-15 16:10:25 +01:00
f20286cbd9 Notify user if startup takes too long (> 30s) 2024-11-15 16:03:44 +01:00
f1fe42e563 Updated constants for PHP 8.4 & 8.5 support
Thankfully, these changes are simple. Before releasing, I will be
testing the new build, though.

Here's what constants I changed, and why:

- Homebrew PHP formulae are now consistently sourced from the
  `shivammathur/php` tap. This should make the transition to new PHP
  releases a little bit easier, but I need to verify this works without
  issues before publishing this update.

- Bumped the PHP formulae cutoff date to Nov 30, 2025.
  At this point, PHP 8.5 should be released.

- Added support for pre-release (daily) versions of PHP 8.5.
2024-11-15 15:22:53 +01:00
94abfe4b49 🐛 Fix crash bug (oops) 2024-10-31 22:51:49 +01:00
9778fd5c7b 🚀 Version 7.0.6 2024-10-31 22:43:03 +01:00
cef19243ee 🔧 Bump build 2024-10-31 21:59:42 +01:00
b319ecab59 👌 Move cut-off date to PHP 8.4 release day 2024-10-31 21:56:13 +01:00
a47b139d92 👌 Cleanup 2024-08-31 22:14:56 +02:00
e026ecf60d ♻️ Various extension list improvements (#274)
Installing and removing extensions now scrolls to the extension afterwards, and animates this. This is done to emphasise that the operation succeeded.
2024-08-31 15:57:08 +02:00
3c0a4a6142 🌐 Updated localization 2024-08-26 15:04:37 +02:00
87ebb20284 ♻️ Use alternate cell identifier for favorite 2024-08-25 16:21:16 +02:00
d60c26c9b2 UserDefaults-backed storage for Favorites 2024-08-25 14:35:11 +02:00
5c9c51f580 Mark domain as favorite (UI only)
Please note that this functionality is currently not persistent.
As such, reloading the domain list will reset any changes you have made.
2024-08-25 13:35:27 +02:00
0c320074da ♻️ Avoid using non-Sendable Timer 2024-08-06 14:15:37 +02:00
e3ea712a99 Ensure all tests run and pass 2024-07-16 18:38:56 +02:00
4db478ca64 👌 Update copyright information 2024-07-16 18:30:56 +02:00
3064a07d69 📦 Use NVAppUpdater and NVAlert packages 2024-07-16 18:29:11 +02:00
141 changed files with 3224 additions and 1012 deletions

4
.gitignore vendored
View File

@ -1,6 +1,4 @@
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

View File

@ -14,6 +14,17 @@ It also automatically runs when you try to build the project. You'll get a warni
swiftlint --fix
```
## 📦 Swift Packages
Starting from PHP Monitor 7.1, the app now uses various first-party package dependencies.
The following package dependencies are in use:
* [`NVAppUpdater`](https://github.com/nicoverbruggen/NVAppUpdater)
* [`NVAlert`](https://github.com/nicoverbruggen/NVAlert)
You may need an internet connection to download these dependencies, or you can also clone the dependencies and include them manually.
## ⚙️ Preferences
You can find the persisted configuration file in `~/Library/Preferences/com.nicoverbruggen.phpmon.plist`
@ -40,6 +51,12 @@ Once you have downloaded this repository, open `PHP Monitor.xcodeproj`, and you
If you'd like to create a production build, choose "Any Mac" as the target and select Product > Archive.
## ✅ Testing
In order to properly test everything, you will want to use the _PHP Monitor DEV_ target. There are unit and UI tests both.
You may sporadically see failures in UI tests due to the following error: `Invalid parameter not satisfying: point.x != INFINITY && point.y != INFINITY`. This seems to be an issue with Xcode that Apple may need to resolve? You can retry the tests in question and they should eventually pass.
## 🚀 Release procedure
1. Merge into `main`

View File

@ -21,6 +21,14 @@
033D45A02B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */; };
033D45A32B0D531D00070080 /* PhpExtensionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */; };
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF5282E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF52A2E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; };
03BFF52C2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03BFF52D2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03BFF52F2E313244007F96FA /* StatusMenu+Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */; };
03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; };
03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E36FE628D9219000636F7F /* ActiveShell.swift */; };
5420395926135DC100FB00FA /* PreferencesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PreferencesVC.swift */; };
@ -64,11 +72,6 @@
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */; };
C406A5F3298AD2CE00B5B85A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C406A5F2298AD2CE00B5B85A /* main.swift */; };
C406A5F7298AD2CF00B5B85A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C406A5F6298AD2CF00B5B85A /* Assets.xcassets */; };
C406A602298AD50D00B5B85A /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C406A601298AD50D00B5B85A /* Updater.swift */; };
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C409349D298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
C409349E298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
C409349F298EE8E900D25014 /* AppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C409349C298EE8E900D25014 /* AppUpdater.swift */; };
@ -295,6 +298,10 @@
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 */; };
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.swift */; };
C46DC7A72C7B5BCA00F19D17 /* Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC7A32C7B55DC00F19D17 /* Favorites.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 */; };
@ -305,6 +312,15 @@
C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; };
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */; };
C46FA98C2822F08F00D78807 /* PhpConfigurationFileTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */; };
C47014FC2C46D31B0069AAE7 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = C47014FB2C46D31B0069AAE7 /* NVAppUpdater */; };
C47014FF2C46D57C0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C47014FE2C46D57C0069AAE7 /* NVAlert */; };
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015032C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015042C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015052C46D7F10069AAE7 /* NVAlertExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47015012C46D6910069AAE7 /* NVAlertExtension.swift */; };
C47015072C46D8180069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C47015062C46D8180069AAE7 /* NVAlert */; };
C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150A2C46D81E0069AAE7 /* NVAlert */; };
C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */ = {isa = PBXBuildFile; productRef = C470150C2C46D83E0069AAE7 /* NVAlert */; };
C4709CA228524B3400088BB8 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; };
C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
@ -392,10 +408,6 @@
C471E81828F9BAE80021E251 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C471E81A28F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */; };
C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */; };
C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */; };
C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* ProjectTypeDetection.swift */; };
@ -720,8 +732,6 @@
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F59298C2D5700DFD82E /* LaunchControl.swift */; };
C4C75F5C298C31C000DFD82E /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C75F5B298C31C000DFD82E /* Utility.swift */; };
C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
@ -918,6 +928,8 @@
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
033D45A22B0D531D00070080 /* PhpExtensionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpExtensionManagerView+Actions.swift"; sourceTree = "<group>"; };
03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.swift"; sourceTree = "<group>"; };
03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = "<group>"; };
03E36FE628D9219000636F7F /* ActiveShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveShell.swift; sourceTree = "<group>"; };
5420395826135DC100FB00FA /* PreferencesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesVC.swift; sourceTree = "<group>"; };
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
@ -946,9 +958,6 @@
C406A5F2298AD2CE00B5B85A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C406A5F6298AD2CF00B5B85A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "phpmon-updater.entitlements"; sourceTree = "<group>"; };
C406A601298AD50D00B5B85A /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlert.swift; sourceTree = "<group>"; };
C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetterAlertVC.swift; sourceTree = "<group>"; };
C409349C298EE8E900D25014 /* AppUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdater.swift; sourceTree = "<group>"; };
C40934A1298EEB2C00D25014 /* CaskFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaskFile.swift; sourceTree = "<group>"; };
C40934A6298EEB8700D25014 /* phpmon-dev.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = "phpmon-dev.rb"; sourceTree = "<group>"; };
@ -1054,12 +1063,14 @@
C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = "<group>"; };
C469E6FD294CF7B200A82AB2 /* FakeValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetProxy.swift; sourceTree = "<group>"; };
C469E702294CFDF700A82AB2 /* DomainsListTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsListTest.swift; sourceTree = "<group>"; };
C46DC7A32C7B55DC00F19D17 /* Favorites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favorites.swift; sourceTree = "<group>"; };
C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellProtocol.swift; sourceTree = "<group>"; };
C46EBC4628DB9644007ACC74 /* RealShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealShell.swift; sourceTree = "<group>"; };
C46EBC4928DB966A007ACC74 /* TestableShell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShell.swift; sourceTree = "<group>"; };
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
C46FA9872822EFDC00D78807 /* PhpConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFile.swift; sourceTree = "<group>"; };
C46FA98A2822F08F00D78807 /* PhpConfigurationFileTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigurationFileTest.swift; sourceTree = "<group>"; };
C47015012C46D6910069AAE7 /* NVAlertExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NVAlertExtension.swift; sourceTree = "<group>"; };
C4709CA128524B3400088BB8 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
C471E79228F9B1D30021E251 /* PHP Monitor.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "PHP Monitor.xctestplan"; sourceTree = "<group>"; };
C471E7AD28F9B4940021E251 /* Feature Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feature Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1084,6 +1095,8 @@
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = "<group>"; };
C4998F092617633900B2526E /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
C49DA9BC2D67AC49006F9CF4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
C49DA9BD2D67B298006F9CF4 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
C49EAA5129B12A5A00AB28FC /* Measurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurements.swift; sourceTree = "<group>"; };
C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+BrewWatch.swift"; sourceTree = "<group>"; };
C4A81CA328C67101008DD9D1 /* PMTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMTableView.swift; sourceTree = "<group>"; };
@ -1120,8 +1133,6 @@
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = "<group>"; };
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; };
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchControl.swift; sourceTree = "<group>"; };
C4C75F5B298C31C000DFD82E /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSystemProtocol.swift; sourceTree = "<group>"; };
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; };
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; };
@ -1198,6 +1209,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47014FC2C46D31B0069AAE7 /* NVAppUpdater in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1205,6 +1217,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47014FF2C46D57C0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1212,6 +1225,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C470150B2C46D81E0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1219,6 +1233,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C470150D2C46D83E0069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1226,6 +1241,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47015072C46D8180069AAE7 /* NVAlert in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1348,24 +1364,12 @@
isa = PBXGroup;
children = (
C406A5F2298AD2CE00B5B85A /* main.swift */,
C406A601298AD50D00B5B85A /* Updater.swift */,
C4C75F5B298C31C000DFD82E /* Utility.swift */,
C4C75F59298C2D5700DFD82E /* LaunchControl.swift */,
C406A5F6298AD2CF00B5B85A /* Assets.xcassets */,
C406A5FB298AD2CF00B5B85A /* phpmon-updater.entitlements */,
);
path = "phpmon-updater";
sourceTree = "<group>";
};
C4080FF827BD955900BF2C6B /* Notice */ = {
isa = PBXGroup;
children = (
C4080FF527BD8C6400BF2C6B /* BetterAlert.swift */,
C4080FF927BD956700BF2C6B /* BetterAlertVC.swift */,
);
path = Notice;
sourceTree = "<group>";
};
C40C5C9E2846A42D00E28255 /* Presets */ = {
isa = PBXGroup;
children = (
@ -1468,7 +1472,6 @@
C41E181722CB61EB0072CF09 /* Domain */ = {
isa = PBXGroup;
children = (
C4080FF827BD955900BF2C6B /* Notice */,
C4AF9F6B275445D300D44ED0 /* Integrations */,
C4B13B1D25C4915000548C3A /* App */,
C4D9ADBD27761084007277F4 /* PHP */,
@ -1789,6 +1792,7 @@
isa = PBXGroup;
children = (
C44DFA852A67090A00B98ED5 /* UI */,
C46DC7A32C7B55DC00F19D17 /* Favorites.swift */,
);
path = "Domain List";
sourceTree = "<group>";
@ -1856,6 +1860,7 @@
C4F361602836BFD9003598CC /* MainMenu+Actions.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */,
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */,
03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */,
C4821C592C2DEDE200357A68 /* AppMenu.swift */,
);
path = Menu;
@ -1944,6 +1949,7 @@
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
03BFF5262E312C39007F96FA /* Startup+Timers.swift */,
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */,
C40FE736282ABA4F00A302C2 /* AppVersion.swift */,
C409349C298EE8E900D25014 /* AppUpdater.swift */,
@ -2235,6 +2241,7 @@
C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */,
C4E2E84928FC1E70003B070C /* DataExtension.swift */,
C4D36619291173EA006BD146 /* DictionaryExtension.swift */,
C47015012C46D6910069AAE7 /* NVAlertExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -2255,6 +2262,9 @@
dependencies = (
);
name = "PHP Monitor Self-Updater";
packageProductDependencies = (
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */,
);
productName = "PHP Monitor Updater";
productReference = C406A5F0298AD2CE00B5B85A /* PHP Monitor Self-Updater.app */;
productType = "com.apple.product-type.application";
@ -2275,6 +2285,7 @@
);
name = "PHP Monitor";
packageProductDependencies = (
C47014FE2C46D57C0069AAE7 /* NVAlert */,
);
productName = phpmon;
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
@ -2294,6 +2305,9 @@
C471E7B228F9B4940021E251 /* PBXTargetDependency */,
);
name = "Feature Tests";
packageProductDependencies = (
C470150A2C46D81E0069AAE7 /* NVAlert */,
);
productName = "Feature Tests";
productReference = C471E7AD28F9B4940021E251 /* Feature Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
@ -2312,6 +2326,9 @@
C471E7C328F9B90F0021E251 /* PBXTargetDependency */,
);
name = "UI Tests";
packageProductDependencies = (
C470150C2C46D83E0069AAE7 /* NVAlert */,
);
productName = "UI Tests";
productReference = C471E7BC28F9B90F0021E251 /* UI Tests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
@ -2331,6 +2348,7 @@
);
name = "Unit Tests";
packageProductDependencies = (
C47015062C46D8180069AAE7 /* NVAlert */,
);
productName = "phpmon-tests";
productReference = C4F7807925D7F84B000DBC97 /* Unit Tests.xctest */;
@ -2344,7 +2362,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1530;
LastUpgradeCheck = 1640;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C406A5EF298AD2CE00B5B85A = {
@ -2378,9 +2396,13 @@
Base,
fr,
"zh-Hans",
es,
id,
);
mainGroup = C41C1B2A22B0097F00E7CF16;
packageReferences = (
C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */,
C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */,
);
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
projectDirPath = "";
@ -2508,10 +2530,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C4C75F5C298C31C000DFD82E /* Utility.swift in Sources */,
C490E3BC29BCA375006D2DE6 /* Measurements.swift in Sources */,
C406A602298AD50D00B5B85A /* Updater.swift in Sources */,
C4C75F5A298C2D5700DFD82E /* LaunchControl.swift in Sources */,
C41F3D08298AED0D0042ACBF /* System.swift in Sources */,
C406A5F3298AD2CE00B5B85A /* main.swift in Sources */,
);
@ -2552,7 +2571,6 @@
C48DDD0D29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */,
C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */,
C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
0309E6672B0D4B2F002AC007 /* BrewExtensionsObservable.swift in Sources */,
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
@ -2583,6 +2601,7 @@
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */,
C4EA3C472BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */,
03BFF5282E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */,
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */,
C456A0C62AA614BD0080144F /* PhpPreference.swift in Sources */,
@ -2635,6 +2654,7 @@
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */,
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */,
03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
03E36FE728D9219000636F7F /* ActiveShell.swift in Sources */,
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */,
@ -2686,11 +2706,11 @@
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
C4415E8D2B0287E90035F520 /* BrewFormulaeObservable.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 */,
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */,
@ -2715,6 +2735,7 @@
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */,
C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2788,6 +2809,7 @@
C4E2E86628FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C471E85F28F9BB650021E251 /* DomainListVC+Actions.swift in Sources */,
C4D5576629C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C47015042C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */,
C4ACE9E329F84EDD00110766 /* PhpGuard.swift in Sources */,
C471E86028F9BB650021E251 /* SelectionVC.swift in Sources */,
C471E86128F9BB650021E251 /* AddSiteVC.swift in Sources */,
@ -2854,7 +2876,9 @@
C471E7FE28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */,
03BFF52F2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */,
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */,
C471E7E728F9BAC20021E251 /* Constants.swift in Sources */,
C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */,
C469E700294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
@ -2879,11 +2903,11 @@
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81C28F9BB250021E251 /* BetterAlert.swift in Sources */,
C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */,
C490E3B929BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */,
C409349F298EE8E900D25014 /* AppUpdater.swift in Sources */,
03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C471E7F228F9BAC70021E251 /* PhpEnvironments.swift in Sources */,
C471E7E628F9BAC20021E251 /* Process.swift in Sources */,
C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */,
@ -2896,7 +2920,6 @@
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79EC829CA474200A483EE /* FakeCommand.swift in Sources */,
C471E7DE28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E81B28F9BB250021E251 /* BetterAlertVC.swift in Sources */,
C471E82928F9BB330021E251 /* Valet.swift in Sources */,
C471E80728F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7D528F9BA8F0021E251 /* TestableConfigurations.swift in Sources */,
@ -3004,6 +3027,7 @@
C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */,
C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */,
C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */,
C46DC7A72C7B5BCA00F19D17 /* Favorites.swift in Sources */,
C471E8C928F9BB8F0021E251 /* PhpDoctorWindowController.swift in Sources */,
C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */,
C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */,
@ -3052,6 +3076,7 @@
C456A0CE2AA6166F0080144F /* BytePhpPreference.swift in Sources */,
C4FD87A829AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C45B9151295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C47015032C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */,
C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */,
C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */,
C471E8EF28F9BB8F0021E251 /* HotKeysController.swift in Sources */,
@ -3069,6 +3094,7 @@
C4611E5A2AEAD2E20010BE24 /* ConfigManagerWindowController.swift in Sources */,
C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */,
C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */,
C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */,
C471E7DF28F9BAAB0021E251 /* RealCommand.swift in Sources */,
@ -3080,6 +3106,7 @@
C471E80228F9BAD40021E251 /* PhpInstallation.swift in Sources */,
C471E81028F9BAE80021E251 /* StringExtension.swift in Sources */,
C48DDD1029C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */,
03BFF52C2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */,
C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */,
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */,
@ -3092,7 +3119,6 @@
C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */,
C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */,
C471E82828F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E81E28F9BB260021E251 /* BetterAlert.swift in Sources */,
C43BCD4729FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C44E985F29B23EBF0059F773 /* UpdateCheckTest.swift in Sources */,
C4513F8E2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
@ -3108,7 +3134,6 @@
C471E7DD28F9BAA30021E251 /* CommandProtocol.swift in Sources */,
C471E7D128F9BA630021E251 /* RealFileSystem.swift in Sources */,
033D459B2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C471E81D28F9BB260021E251 /* BetterAlertVC.swift in Sources */,
C471E82B28F9BB340021E251 /* Valet.swift in Sources */,
C471E80328F9BAD40021E251 /* PhpConfigurationFile.swift in Sources */,
C471E7C928F9BA2F0021E251 /* TestableConfigurations.swift in Sources */,
@ -3165,7 +3190,6 @@
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C485707528BF454F00539B36 /* StatsView.swift in Sources */,
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C485707328BF454300539B36 /* OnboardingView.swift in Sources */,
@ -3213,6 +3237,7 @@
C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */,
C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */,
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
@ -3230,6 +3255,7 @@
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */,
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
03BFF52D2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
C4AFC4B429C4F43300BF4E0D /* HomebrewUpgradableTest.swift in Sources */,
C4E2E84828FC1D93003B070C /* TestableConfigurationTest.swift in Sources */,
C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */,
@ -3241,6 +3267,7 @@
C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */,
C481F79726164A78004FBCFF /* PreferencesVC.swift in Sources */,
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */,
03BFF52A2E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */,
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
@ -3258,7 +3285,6 @@
C40B24F427A310830018C7D2 /* StatusMenu.swift in Sources */,
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
C469E6FF294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
@ -3316,6 +3342,7 @@
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */,
C4463FCD29804BCB007B93D5 /* RCFile.swift in Sources */,
C47015052C46D7F10069AAE7 /* NVAlertExtension.swift in Sources */,
C409349E298EE8E900D25014 /* AppUpdater.swift in Sources */,
C4AF9F7D275454A900D44ED0 /* ValetVersionExtractorTest.swift in Sources */,
C4B56362276AB0A500F12CCB /* VersionExtractorTest.swift in Sources */,
@ -3406,6 +3433,8 @@
C4622F572A7593CB0016F8FB /* pt-PT */,
0336CAAF2B0D0CDA009A1034 /* fr */,
C453874C2BE37FD6002B9C65 /* zh-Hans */,
C49DA9BC2D67AC49006F9CF4 /* es */,
C49DA9BD2D67B298006F9CF4 /* id */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -3427,19 +3456,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3462,19 +3490,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3497,19 +3524,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3532,19 +3558,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -3592,6 +3617,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -3657,6 +3683,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -3685,10 +3712,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor";
@ -3697,8 +3723,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3716,10 +3742,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor";
@ -3728,8 +3753,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3745,7 +3770,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -3764,7 +3788,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -3783,7 +3806,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -3802,7 +3824,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -3821,7 +3842,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -3840,7 +3860,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -3859,7 +3878,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -3878,7 +3896,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -3929,6 +3946,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -3957,10 +3975,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV";
@ -3969,8 +3986,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -3985,7 +4002,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4039,6 +4055,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -4074,10 +4091,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor DEV";
@ -4086,8 +4102,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -4102,7 +4118,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4156,6 +4171,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -4191,10 +4207,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor EAP";
@ -4203,8 +4218,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4227,19 +4242,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4254,7 +4268,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4276,7 +4289,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -4295,7 +4307,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -4346,6 +4357,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -4374,10 +4386,9 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1491;
CURRENT_PROJECT_VERSION = 1565;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor EAP";
@ -4386,8 +4397,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0.5;
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.07;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4410,19 +4421,18 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "PHP Monitor Self-Updater";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023-2025 Nico Verbruggen. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = "com.nicoverbruggen.phpmon-updater";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4437,7 +4447,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4459,7 +4468,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
MACOSX_DEPLOYMENT_TARGET = 12.4;
@ -4478,7 +4486,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 1.0;
@ -4496,7 +4503,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4517,7 +4523,6 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "";
LD_RUNPATH_SEARCH_PATHS = (
@ -4614,6 +4619,53 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicoverbruggen/NVAppUpdater";
requirement = {
branch = main;
kind = branch;
};
};
C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicoverbruggen/NVAlert";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */;
productName = NVAppUpdater;
};
C47014FE2C46D57C0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C47015062C46D8180069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C470150A2C46D81E0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
C470150C2C46D83E0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;
productName = NVAlert;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1640"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1640"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1640"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
LastUpgradeVersion = "1640"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,7 +1,7 @@
> **Note**
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider [sponsoring](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️
<p align="center"><img src="./docs/logo.png" alt="PHP Monitor Logo" width="500px" /></p>
<p align="center"><img src="./docs/logo.svg" alt="PHP Monitor Logo" width="500px" /></p>
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up if you want to use all of the functionality of the app</u> (consult the FAQ below with info about how to set up your environment).
@ -22,7 +22,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 12.4 or later (Monterey, Ventura and Sonoma are supported)
* macOS 13.5 or later
* Homebrew is installed in the default location (`/usr/local/homebrew` or `/opt/homebrew`)
* Homebrew `php` formula is installed
* Optional but recommended: Laravel Valet
@ -84,9 +84,13 @@ Initially, I had an Alfred workflow for this — but it has now been replaced wi
## 🐘 Why not use Laravel Herd?
If you don't need to customize your local PHP setup and just want an easy and ready-to-go environment to start coding, [Laravel Herd](https://herd.laravel.com) is probably more than sufficient for many use cases.
_**Disclaimer**: The author is not affiliated with Laravel or the Laravel team, nor Beyond Code, who maintain Laravel Herd. PHP Monitor is an independent project._
If you need more customization and flexibility I encourage you to consider PHP Monitor in combination with Laravel Valet or some other solution like Docker (with Laravel Sail, for example).
If you don't need to customize your local PHP setup and just want an easy and ready-to-go environment to start coding, [Laravel Herd](https://herd.laravel.com) is probably more than sufficient for many use cases. They also offer paid features that may be useful to you or your team.
At this point, many people enjoy using Herd. However, Herd may not be for everyone, which is why other solutions to run PHP locally exist. If you need more customization and flexibility I encourage you to consider PHP Monitor in combination with Laravel Valet.
If you want to get as close as you can to a real server environment your best bet is probably to use a Docker container. I _highly_ recommend that you try different setups, and use what you like best.
## 🤬 The app won't start?!

View File

@ -2,11 +2,13 @@
## Supported versions
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up).
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Recommended Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 7.0 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 25 | ✅ Universal binary | ✅ Yes | Ventura (13.5+)<br/>Sonoma (14.0+)<br/>Sequoia (15.0+)<br/>Tahoe (26.0+)* | macOS 13.5+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.5 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
(*) Denotes preliminary supported based on the app being built with the latest version of the SDK prior to the release of the latest release of macOS. Please check out the pinned issue for more information.
## Legacy versions
@ -14,6 +16,8 @@ These versions of PHP Monitor are no longer supported, but if youre using an
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Minimum Required Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 7.1 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0+)<br/>Sequoia (15.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.5 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 7.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.2 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.1 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

1
assets/icon-2025.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.25033,-0.175723,0.175723,1.25033,14.4412,107.226)">
<path d="M133.3,83.75L120.4,83.75L120.4,54.437C120.4,52.134 118.465,50.25 116.1,50.25L107.5,50.25C105.135,50.25 103.2,52.134 103.2,54.438L103.2,96.312C103.2,98.616 105.135,100.5 107.5,100.5L133.3,100.5C135.665,100.5 137.6,98.616 137.6,96.312L137.6,87.938C137.6,85.634 135.665,83.75 133.3,83.75ZM335.4,184.25L326.8,184.25L326.8,127.666C326.8,121.019 324.059,114.633 319.221,109.922L265.525,57.63C260.688,52.92 254.13,50.25 247.304,50.25L223.6,50.25L223.6,25.125C223.6,11.254 212.044,0 197.8,0L25.8,0C11.556,0 0,11.254 0,25.125L0,192.625C0,206.496 11.556,217.75 25.8,217.75L34.4,217.75C34.4,245.492 57.513,268 86,268C114.487,268 137.6,245.492 137.6,217.75L206.4,217.75C206.4,245.492 229.512,268 258,268C286.488,268 309.6,245.492 309.6,217.75L335.4,217.75C340.13,217.75 344,213.981 344,209.375L344,192.625C344,188.019 340.13,184.25 335.4,184.25ZM86,242.875C71.756,242.875 60.2,231.621 60.2,217.75C60.2,203.879 71.756,192.625 86,192.625C100.244,192.625 111.8,203.879 111.8,217.75C111.8,231.621 100.244,242.875 86,242.875ZM111.8,150.75C78.529,150.75 51.6,124.526 51.6,92.125C51.6,59.725 78.529,33.5 111.8,33.5C145.071,33.5 172,59.724 172,92.125C172,124.525 145.071,150.75 111.8,150.75ZM258,242.875C243.756,242.875 232.2,231.621 232.2,217.75C232.2,203.879 243.756,192.625 258,192.625C272.244,192.625 283.8,203.879 283.8,217.75C283.8,231.621 272.244,242.875 258,242.875ZM301,134L223.6,134L223.6,75.375L247.304,75.375L301,127.666L301,134Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.96882e-14,321.533,-321.533,1.96882e-14,172.079,-41.4918)"><stop offset="0" style="stop-color:rgb(81,194,251);stop-opacity:1"/><stop offset="0" style="stop-color:rgb(81,194,251);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(28,145,254);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,48 @@
{
"fill" : {
"linear-gradient" : [
"srgb:0.27800,0.58000,0.98800,1.00000",
"srgb:0.27800,0.58000,0.98800,1.00000"
]
},
"groups" : [
{
"blend-mode" : "screen",
"blur-material" : null,
"layers" : [
{
"blend-mode" : "normal",
"fill" : {
"solid" : "srgb:1.00000,0.99038,0.96423,1.00000"
},
"glass" : true,
"image-name" : "phpmon.svg",
"name" : "phpmon",
"position" : {
"scale" : 1.85,
"translation-in-points" : [
10.0234375,
8.21875
]
}
}
],
"lighting" : "individual",
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : true,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

57
docs/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 450 KiB

View File

@ -1,46 +0,0 @@
//
// LaunchControl.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class LaunchControl {
public static func smartRestart(priority: [String]) async {
for appPath in priority {
if FileManager.default.fileExists(atPath: appPath) {
let app = await LaunchControl.startApplication(at: appPath)
if app != nil {
return
}
}
}
}
public static func terminateApplications(bundleIds: [String]) async {
let runningApplications = NSWorkspace.shared.runningApplications
// Terminate all instances found
for id in bundleIds {
if let phpmon = runningApplications.first(where: {
(application) in return application.bundleIdentifier == id
}) {
phpmon.terminate()
}
}
}
public static func startApplication(at path: String) async -> NSRunningApplication? {
await withCheckedContinuation { continuation in
let url = NSURL(fileURLWithPath: path, isDirectory: true) as URL
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: configuration) { phpmon, error in
continuation.resume(returning: phpmon)
}
}
}
}

View File

@ -1,162 +0,0 @@
//
// Updater.swift
// PHP Monitor Updater
//
// Created by Nico Verbruggen on 01/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Cocoa
class Updater: NSObject, NSApplicationDelegate {
var updaterDirectory: String = ""
var manifestPath: String = ""
var manifest: ReleaseManifest! = nil
func applicationDidFinishLaunching(_ aNotification: Notification) {
Task { await self.installUpdate() }
}
func installUpdate() async {
print("PHP MONITOR SELF-UPDATER by Nico Verbruggen")
print("===========================================")
self.updaterDirectory = "~/.config/phpmon/updater"
.replacingOccurrences(of: "~", with: NSHomeDirectory())
print("Updater directory set to: \(self.updaterDirectory)")
self.manifestPath = "\(updaterDirectory)/update.json"
// Fetch the manifest on the local filesystem
let manifest = await parseManifest()!
// Download the latest file
let zipPath = await download(manifest)
// Terminate all instances of PHP Monitor first
await LaunchControl.terminateApplications(bundleIds: [
"com.nicoverbruggen.phpmon.eap",
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
])
// Install the app based on the zip
let appPath = await extractAndInstall(zipPath: zipPath)
// Restart PHP Monitor, this will also close the updater
_ = await LaunchControl.startApplication(at: appPath)
exit(1)
}
func applicationWillTerminate(_ aNotification: Notification) {
exit(1)
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return false
}
private func parseManifest() async -> ReleaseManifest? {
// Read out the correct information from the manifest JSON
print("Checking manifest file at \(manifestPath)...")
do {
let manifestText = try String(contentsOfFile: manifestPath)
manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestText.data(using: .utf8)!)
return manifest
} catch {
print("Parsing the manifest failed (or the manifest file doesn't exist)!")
await Alert.show(description: "The manifest file for a potential update was not found. Please try searching for updates again in PHP Monitor.")
}
return nil
}
private func download(_ manifest: ReleaseManifest) async -> String {
// Remove all zips
system_quiet("rm -rf \(updaterDirectory)/*.zip")
// Download the file (and follow redirects + no output on failure)
system_quiet("cd \"\(updaterDirectory)\" && curl \(manifest.url) -fLO --max-time 20")
// Identify the downloaded file
let filename = system("cd \"\(updaterDirectory)\" && ls | grep .zip")
.trimmingCharacters(in: .whitespacesAndNewlines)
// Ensure the zip exists
if filename.isEmpty {
print("The update has not been downloaded. Sadly, that means that PHP Monitor cannot not updated!")
await Alert.show(description: "The update could not be downloaded, or the file was not correctly written to disk. \n\nPlease try again. \n\n(Note that the download will time-out after 20 seconds, so for slow connections it is recommended to manually download the update.)")
}
// Calculate the checksum for the downloaded file
let checksum = system("openssl dgst -sha256 \"\(updaterDirectory)/\(filename)\" | awk '{print $NF}'")
.trimmingCharacters(in: .whitespacesAndNewlines)
// Compare the checksums
print("""
Comparing checksums...
Expected SHA256: \(manifest.sha256)
Actual SHA256: \(checksum)
""")
// Make sure the checksum matches before we do anything with the file
if checksum != manifest.sha256 {
print("The checksums failed to match. Cancelling!")
await Alert.show(description: "The downloaded update failed checksum validation. Please try again. If this issue persists, there may be an issue with the server and I do not recommend upgrading.")
}
// Return the path to the zip
return "\(updaterDirectory)/\(filename)"
}
private func extractAndInstall(zipPath: String) async -> String {
// Remove the directory that will contain the extracted update
system_quiet("rm -rf \"\(updaterDirectory)/extracted\"")
// Recreate the directory where we will unzip the .app file
system_quiet("mkdir -p \"\(updaterDirectory)/extracted\"")
// Make sure the updater directory exists
var isDirectory: ObjCBool = true
if !FileManager.default.fileExists(atPath: "\(updaterDirectory)/extracted", isDirectory: &isDirectory) {
await Alert.show(description: "The updater directory is missing. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
}
// Unzip the file
system_quiet("unzip \"\(zipPath)\" -d \"\(updaterDirectory)/extracted\"")
// Find the .app file
let app = system("ls \"\(updaterDirectory)/extracted\" | grep .app")
.trimmingCharacters(in: .whitespacesAndNewlines)
print("Finished extracting: \(updaterDirectory)/extracted/\(app)")
// Make sure the file was extracted
if app.isEmpty {
await Alert.show(description: "The downloaded file could not be extracted. The automatic updater will quit. Make sure that ` ~/.config/phpmon/updater` is writeable.")
}
// Remove the original app
print("Removing \(app) before replacing...")
system_quiet("rm -rf \"/Applications/\(app)\"")
// Move the new app in place
system_quiet("mv \"\(updaterDirectory)/extracted/\(app)\" \"/Applications/\(app)\"")
// Remove the zip
system_quiet("rm \"\(zipPath)\"")
// Remove the manifest
system_quiet("rm \"\(manifestPath)\"")
// Write a file that is only written when we upgraded successfully
system_quiet("touch \"\(updaterDirectory)/upgrade.success\"")
// Return the new location of the app
return "/Applications/\(app)"
}
}

View File

@ -1,34 +0,0 @@
//
// Utility.swift
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 02/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class Alert {
public static func show(description: String, shouldExit: Bool = true) async {
await withUnsafeContinuation { continuation in
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = "The app could not be updated."
alert.informativeText = description
alert.addButton(withTitle: "OK")
alert.alertStyle = .critical
alert.runModal()
if shouldExit {
exit(0)
}
continuation.resume()
}
}
}
}
public struct ReleaseManifest: Codable {
let url: String
let sha256: String
}

View File

@ -7,8 +7,17 @@
//
import Cocoa
import NVAppUpdater
let app = NSApplication.shared
let delegate = Updater()
app.delegate = delegate
let delegate = SelfUpdater(
appName: "PHP Monitor",
bundleIdentifiers: [
"com.nicoverbruggen.phpmon.eap",
"com.nicoverbruggen.phpmon.dev",
"com.nicoverbruggen.phpmon"
],
selfUpdaterPath: "~/.config/phpmon/updater"
)
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 499 KiB

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ValetDriverIcon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

View File

@ -18,19 +18,29 @@ struct Constants {
*/
static let MinimumRecommendedValetVersion = "2.16.2"
/**
The amount of seconds that is considered the threshold for
PHP Monitor to mark any given launch as a "slow" launch.
If the startup procedure was slow (or hangs), this message should
be displayed. This is based on an appropriate launch time on a
basic M1 Apple chip, with some margin for slower Intel chips.
*/
static let SlowBootThresholdInterval: TimeInterval = 30.0
/**
PHP Monitor supplies a hardcoded list of PHP packages in its own
PHP Version Manager.
This hardcoded list will expire and will need to be modified when
the cutoff date occurs, which is when the `php` formula will
become PHP 8.4, and a new build will need to be made.
become PHP 8.5, and a new build will need to be made.
If users launch an older version of the app, then a warning
will be displayed to let them know that certain operations
will not work correctly and that they need to update their app.
*/
static let PhpFormulaeCutoffDate = "2024-11-31" // YYYY-MM-DD
static let PhpFormulaeCutoffDate = "2025-11-30" // YYYY-MM-DD
/**
* The PHP versions that are considered pre-release versions.
@ -39,6 +49,7 @@ struct Constants {
*/
static var ExperimentalPhpVersions: Set<String> {
let releaseDates = [
"8.5": Date.fromString(Self.PhpFormulaeCutoffDate),
"8.4": Date.fromString("2024-11-22")
]
@ -72,8 +83,8 @@ struct Constants {
static let DetectedPhpVersions: Set = [
"5.6",
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4"
"8.0", "8.1", "8.2", "8.3", "8.4",
"8.5" // DEV
]
/**
@ -89,14 +100,13 @@ struct Constants {
3: // Valet v3 dropped support for v5.6
[
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
"8.0", "8.1", "8.2", "8.3", "8.4"
],
4: // Valet v4 dropped support for v7.0
[
"7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
"8.0", "8.1", "8.2", "8.3", "8.4",
"8.5" // DEV
]
]
@ -112,6 +122,14 @@ struct Constants {
string: "https://phpmon.app/faq"
)!
static let WikiPhpUnavailable = URL(
string: "https://phpmon.app/php-unavailable"
)!
static let WikiPhpUpgrade = URL(
string: "https://phpmon.app/php-upgrade"
)!
static let DonationPayment = URL(
string: "https://phpmon.app/sponsor/now"
)!

View File

@ -45,7 +45,6 @@ func grepContains(file: String, query: String) async -> Bool {
/**
Attempts to introduce sleep for a particular duration. Use with caution.
Only intended for testing purposes.
*/
func delay(seconds: Double) async {
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))

View File

@ -8,6 +8,6 @@
import Foundation
protocol AlertableError {
public protocol AlertableError {
func getErrorMessageKey() -> String
}

View File

@ -9,6 +9,24 @@
import Cocoa
extension NSMenuItem {
convenience init(
title: String,
action: Selector? = nil,
keyEquivalent: String = "",
keyModifier: NSEvent.ModifierFlags = [],
systemImage: String? = nil,
customImage: String? = nil,
) {
self.init(title: title, action: action, keyEquivalent: keyEquivalent)
self.keyEquivalentModifierMask = keyModifier
if systemImage != nil {
self.image = NSImage(systemSymbolName: systemImage!, accessibilityDescription: "")
}
if customImage != nil {
self.image = NSImage(named: customImage!)
}
}
convenience init(
title: String,
action: Selector? = nil,
@ -26,12 +44,20 @@ extension NSMenuItem {
keyEquivalent: String = "",
keyModifier: NSEvent.ModifierFlags = [],
toolTip: String? = nil,
systemImage: String? = nil,
customImage: String? = nil,
submenu: [NSMenuItem],
target: NSObject? = nil
) {
self.init(title: title, action: nil, keyEquivalent: keyEquivalent)
self.keyEquivalentModifierMask = keyModifier
self.toolTip = toolTip
if systemImage != nil {
self.image = NSImage(systemSymbolName: systemImage!, accessibilityDescription: "")
}
if customImage != nil {
self.image = NSImage(named: customImage!)
}
self.submenu = NSMenu(items: submenu, target: target)
}
}

View File

@ -0,0 +1,23 @@
//
// NVAlertExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/07/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
//
import Foundation
import NVAlert
extension NVAlert {
/**
Shows the modal for a particular error.
*/
@MainActor public static func show(for error: Error & AlertableError) {
let key = error.getErrorMessageKey()
return NVAlert().withInformation(
title: "\(key).title".localized,
subtitle: "\(key).description".localized
).withPrimary(text: "generic.ok".localized).show()
}
}

View File

@ -14,6 +14,8 @@ class PhpInstallation {
var iniFiles: [PhpConfigurationFile] = []
var isPreRelease: Bool = false
var isMissingBinary: Bool = false
var isHealthy: Bool = true
@ -59,6 +61,10 @@ class PhpInstallation {
trimNewlines: false
).trimmingCharacters(in: .whitespacesAndNewlines)
if longVersionString.contains("-dev") {
isPreRelease = true
}
// The parser should always work, or the string has to be very unusual.
// If so, the app SHOULD crash, so that the users report what's up.
versionNumber = try! VersionNumber.parse(longVersionString)

View File

@ -8,9 +8,6 @@
import Foundation
extension Process: @unchecked Sendable {}
extension Timer: @unchecked Sendable {}
class RealShell: ShellProtocol {
/**
The launch path of the terminal in question that is used.
@ -184,25 +181,26 @@ class RealShell: ShellProtocol {
}
return try await withCheckedThrowingContinuation({ continuation in
let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in
let task = Task {
try await Task.sleep(nanoseconds: timeout.nanoseconds)
// Only terminate if the process is still running
if process.isRunning {
process.terminationHandler = nil
process.terminate()
return continuation.resume(throwing: ShellError.timedOut)
continuation.resume(throwing: ShellError.timedOut)
}
}
process.terminationHandler = { [timer, output] process in
timer.invalidate()
process.terminationHandler = { [output] process in
task.cancel()
process.haltListening()
if !output.err.isEmpty {
return continuation.resume(returning: (process, .err(output.err)))
continuation.resume(returning: (process, .err(output.err)))
} else {
continuation.resume(returning: (process, .out(output.out)))
}
return continuation.resume(returning: (process, .out(output.out)))
}
process.launch()
@ -210,3 +208,9 @@ class RealShell: ShellProtocol {
})
}
}
extension TimeInterval {
var nanoseconds: UInt64 {
return UInt64(self * 1_000_000_000)
}
}

View File

@ -18,11 +18,11 @@ class TestableFileSystem: FileSystemProtocol {
self.files = files
// Ensure that each of the ~ characters are replaced with the home directory path
for key in self.files.keys where key.contains("~") {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory)
)
accessQueue.sync {
for (key, value) in files {
let adjustedKey = key.contains("~") ? key.replacingOccurrences(of: "~", with: self.homeDirectory) : key
self.files[adjustedKey] = value
}
}
// Ensure that intermediate directories are created
@ -46,38 +46,49 @@ class TestableFileSystem: FileSystemProtocol {
*/
private(set) var homeDirectory = "/Users/fake"
/**
Serial dispatch queue for ensuring thread-safe access to the `files` dictionary.
*/
private let accessQueue = DispatchQueue(label: "com.testablefilesystem.accessQueue")
// MARK: - Basics
func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
try accessQueue.sync {
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
func writeAtomicallyToFile(_ path: String, content: String) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
try accessQueue.sync {
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.files[path] = .fake(.text, content)
self.files[path] = .fake(.text, content)
}
}
func getStringFromFile(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return file.content ?? ""
return file.content ?? ""
}
}
func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
@ -88,32 +99,36 @@ class TestableFileSystem: FileSystemProtocol {
seek = "\(seek)/"
}
return self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
return accessQueue.sync {
self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
}
}
func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
return try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
if file.type != .symlink {
throw TestableFileSystemError.notSymlink
}
if file.type != .symlink {
throw TestableFileSystemError.notSymlink
}
guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink
}
guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink
}
if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink
}
if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink
}
return pathToSymlink
return pathToSymlink
}
}
// MARK: - Move & Delete Files
@ -122,27 +137,31 @@ class TestableFileSystem: FileSystemProtocol {
let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: path, with: newPath)
)
accessQueue.sync {
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: path, with: newPath)
)
}
}
}
self.files.renameKey(fromKey: path, toKey: newPath)
self.files.renameKey(fromKey: path, toKey: newPath)
}
}
func remove(_ path: String) throws {
// Remove recursively
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
accessQueue.sync {
// Remove recursively
self.files.keys.forEach { key in
if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
}
}
}
self.files.removeValue(forKey: path)
self.files.removeValue(forKey: path)
}
}
// MARK: Attributes
@ -150,11 +169,13 @@ class TestableFileSystem: FileSystemProtocol {
func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
try accessQueue.sync {
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
file.type = .binary
file.type = .binary
}
}
// MARK: - Checks
@ -162,93 +183,107 @@ class TestableFileSystem: FileSystemProtocol {
func isExecutableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return file.type == .binary
return file.type == .binary
}
}
func isWriteableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
return !file.readOnly
return !file.readOnly
}
}
func anyExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
return files.keys.contains(path)
return accessQueue.sync {
files.keys.contains(path)
}
}
func fileExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return [.binary, .symlink, .text].contains(file.type)
return [.binary, .symlink, .text].contains(file.type)
}
}
func directoryExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return [.directory].contains(file.type)
return [.directory].contains(file.type)
}
}
func isSymlink(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return file.type == .symlink
return file.type == .symlink
}
}
func isDirectory(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return accessQueue.sync {
guard let file = files[path] else {
return false
}
return file.type == .directory
return file.type == .directory
}
}
public func printContents() {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
accessQueue.sync {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
}
}
}
private func createIntermediateDirectories(_ path: String) {
let path = path.replacingTildeWithHomeDirectory
let items = path.components(separatedBy: "/")
var preceding = ""
var directoriesToCreate: [String] = []
for item in items {
let key = preceding == "/"
? "/\(item)"
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
let key = preceding == "/" ? "/\(item)" : "\(preceding)/\(item)"
directoriesToCreate.append(key)
preceding = key
}
for key in directoriesToCreate where !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
}
}

View File

@ -16,9 +16,9 @@
<p><b>Do you enjoy using the app? Is it helping you save time?</b> Leave a <a href="https://phpmon.app/github">star on GitHub</a>!</p>
<p><b>Having issues?</b> Consult the <a href="https://phpmon.app/faq">FAQ</a> section, I did my best to ensure everything is documented.</p>
<p><b>Want to support further development of PHP Monitor?</b> You can <a href="https://phpmon.app/sponsor">financially support</a> the continued development of this app.</p>
<p><b>Get the latest on Twitter or Mastodon.</b> Give me a <a href="https://twitter.com/nicoverbruggen">follow on Twitter</a> or <a href="https://phpc.social/@nicoverbruggen">Mastodon</a> to learn about what's brewing and when new updates drop.</p>
<p><b>Get the latest on Bluesky or Mastodon.</b> Give me a <a href="https://bsky.app/profile/nicoverbruggen.be">follow on Bluesky</a> or <a href="https://phpc.social/@nicoverbruggen">Mastodon</a> to learn about what's brewing and when new updates drop.</p>
<p><b>Special thanks</b> to all current and past <a href="https://github.com/sponsors/nicoverbruggen#sponsors"><b>sponsors</b></a> of PHP Monitor, who have helped to make further development of the app possible.</p>
<p><b>Made possible by these GitHub Sponsors</b>: @abdusfauzi, @abicons, @adrolli, @andresayej, @andyunleashed, @anzacorp, @argirisp, @AshPowell, @aurawindsurfing, @awsmug, @barrycarton, @BertvanHoekelen, @calebporzio, @caseyalee, @cgreuling, @cjcox17, @Diewy, @drfraker, @driftingly, @duellsy, @edalzell, @EYOND, @faithfm, @frankmichel, @gwleuverink, @hopkins385, @intrepidws, @jacksleight, @JacobBennett, @jasonvarga, @jeromegamez, @jimmyaldape, @jimmysawczuk, @joetannenbaum, @jolora, @joshuablum, @jpeinelt, @jreviews, @JustSteveKing, @Kajvdh, @KFoobar, @Laravel-Backpack, @leganz, @martinleveille, @mathiasonea, @matthewmnewman, @mcastillo1030, @megabubbletea, @mennen-online, @mike-healy, @mostafakram, @mpociot, @MrMicky-FR, @MrMooky, @murdercode, @nckrtl, @nhedger, @ninjaparade, @ozanuzer, @pepatel, @philbraun, @pickuse2013, @pk-informatics, @Plytas, @rderimay, @rickyjohnston, @rico, @RobertBoes, @runofthemill, @SahinU88, @sdebacker, @sdevore, @shadracnicholas, @simonhamp, @SRWieZ, @stefanbauer, @StriveMedia, @swilla, @Tailcode-Studio, @theutz, @ThomasEnssner, @tillkruss, @timothyrowan, @ttnppedr, @vincent-tarrit, @WheresMarco, @xPand4B, @xuandung38, @yeslandi89, @zackkatz, @zacksmash, @zaherg.<br/>(Some names have been omitted due to their sponsorships being private. Thank you all!)</p>
<p><b>Made possible by these GitHub Sponsors</b>: @abdusfauzi, @abicons, @adibnoh, @adrolli, @andresayej, @andyunleashed, @anzacorp, @argirisp, @ash-jc-allen, @AshPowell, @aurawindsurfing, @awsmug, @barrycarton, @BertvanHoekelen, @calebporzio, @casenxu, @caseyalee, @cgreuling, @cjcox17, @clescuyer, @codelinde, @designhammer, @Diewy, @drfraker, @driftingly, @duellsy, @e9li, @edalzell, @EYOND, @faithfm, @frankmichel, @gekich, @gpluess, @gwleuverink, @hopkins385, @incon, @intrepidws, @israaraujo, @jacksleight, @JacobBennett, @jasonvarga, @jeromegamez, @jimmyaldape, @jimmysawczuk, @joetannenbaum, @jolora, @jorisnoo, @joshuablum, @jpeinelt, @jreviews, @JustSteveKing, @Kajvdh, @KFoobar, @kholisabdullah, @Laravel-Backpack, @leganz, @lucianvacaroiu,@martinleveille, @mathiasonea, @matthewmnewman, @mcastillo1030, @megabubbletea, @megabubbleteam, @mennen-online, @mike-healy, @mostafakram, @mpociot, @MrMicky-FR, @MrMooky, @murdercode, @nckrtl, @nhedger, @ninjaparade, @ozanuzer, @pepatel, @philbraun, @pickuse2013, @pk-informatics, @Plytas, @rastitkac, @rderimay, @renecum, @richardhulbert, @richardtape, @rickyjohnston, @rico, @RobertBoes, @runofthemill, @SahinU88, @sdebacker, @sdevore, @shadracnicholas, @simonhamp, @slaFFik, @spatie, @SRWieZ, @stefanbauer, @stefanzweifel, @StriveMedia, @swilla, @Tailcode-Studio, @theutz, @ThomasEnssner, @tillkruss, @timothyrowan, @ttnppedr, @vincent-tarrit, @vintagesucks, @WheresMarco, @xPand4B, @xuandung38, @yeslandi89, @zackkatz, @zacksmash, @zaherg.<br/>(This is a historical list of sponsors, not current sponsors. Some names have been omitted due to their sponsorships being private. Thank you all!)</p>
<p><b>Localization credits:</b></br>
&dash; English, Dutch</b> by @nicoverbruggen</br>
&dash; Vietnamese</b> by @xuandung38</br>
@ -26,6 +26,8 @@
&dash; Portuguese</b> by @joseborges</br>
&dash; French</b> by @nhedger, @tplesnar</br>
&dash; Chinese</b> by @guanguans</br>
</br>
Other languages are considered experimental, and were generated via a local LLM. If you have feedback or concerns, please don't hesitate to get in touch.
</p>
<br/>
</body>

View File

@ -89,6 +89,9 @@ class App {
/** List of detected (installed) applications that PHP Monitor can work with. */
var detectedApplications: [Application] = []
/** Favorites storage, which keeps track of favorited domains. */
var favorites = Favorites.shared
/** The warning manager, responsible for keeping track of warnings. */
var warnings = WarningManager.shared

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
class AppUpdater {
var caskFile: CaskFile!
@ -72,7 +73,7 @@ class AppUpdater {
: "brew upgrade phpmon"
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "updater.alerts.newer_version_available.title"
.localized(latestVersionOnline.humanReadable),
subtitle: "updater.alerts.newer_version_available.subtitle"
@ -112,7 +113,7 @@ class AppUpdater {
public func presentNoNewerVersionAvailableAlert() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "updater.alerts.is_latest_version.title".localized,
subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion),
description: ""
@ -124,7 +125,7 @@ class AppUpdater {
public func presentCouldNotRetrieveUpdate() {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().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(

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
@ -515,10 +515,10 @@
</objects>
<point key="canvasLocation" x="-374" y="2267"/>
</scene>
<!--Better AlertVC-->
<!--AlertVC-->
<scene sceneID="y9E-bB-wIG">
<objects>
<viewController storyboardIdentifier="noticeVC" id="hkw-9V-NxP" customClass="BetterAlertVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="noticeVC" id="hkw-9V-NxP" customClass="NVAlertVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="UPH-hV-Naz">
<rect key="frame" x="0.0" y="0.0" width="500" height="212"/>
<autoresizingMask key="autoresizingMask"/>
@ -923,6 +923,50 @@ Gw
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
</connections>
</tableCellView>
<tableCellView identifier="domainListNameCellFavorited" wantsLayer="YES" id="Byb-te-u65" customClass="DomainListNameCell" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="69" y="54" width="200" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="aot-FJ-HIk">
<rect key="frame" x="33" y="26" width="145" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="my-domain-name.test" id="LHu-UF-QlC">
<font key="font" metaFont="systemSemibold" size="13"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GNH-l8-oki">
<rect key="frame" x="33" y="12" width="75" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="~/path/to/site" id="LNw-Ju-0Ot">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Wp-DX-An9">
<rect key="frame" x="5" y="4" width="20" height="47"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="Q76-fI-lkW">
<imageReference key="image" image="star.circle.fill" catalog="system" symbolScale="large"/>
</imageCell>
<color key="contentTintColor" name="AccentColor"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="3Wp-DX-An9" firstAttribute="leading" secondItem="Byb-te-u65" secondAttribute="leading" constant="5" id="CTd-ON-loK"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="aot-FJ-HIk" secondAttribute="trailing" constant="20" symbolic="YES" id="Csc-Dy-H4K"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="GNH-l8-oki" secondAttribute="trailing" constant="20" symbolic="YES" id="H10-MG-hCG"/>
<constraint firstItem="GNH-l8-oki" firstAttribute="leading" secondItem="aot-FJ-HIk" secondAttribute="leading" id="Hk0-x3-RyN"/>
<constraint firstItem="3Wp-DX-An9" firstAttribute="top" secondItem="Byb-te-u65" secondAttribute="top" constant="9" id="erH-dR-K7S"/>
<constraint firstItem="aot-FJ-HIk" firstAttribute="top" secondItem="Byb-te-u65" secondAttribute="top" constant="12" id="ktI-fg-qaX"/>
<constraint firstAttribute="bottom" secondItem="3Wp-DX-An9" secondAttribute="bottom" constant="9" id="uyc-26-gZb"/>
<constraint firstItem="aot-FJ-HIk" firstAttribute="leading" secondItem="Byb-te-u65" secondAttribute="leading" constant="35" id="vXE-jj-lLF"/>
<constraint firstItem="GNH-l8-oki" firstAttribute="top" secondItem="aot-FJ-HIk" secondAttribute="bottom" id="wSX-fR-O7a"/>
</constraints>
<connections>
<outlet property="labelPathName" destination="GNH-l8-oki" id="GC1-TA-lIk"/>
<outlet property="labelSiteName" destination="aot-FJ-HIk" id="HdZ-Rh-ua6"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="ENVIRONMENT" width="100" minWidth="100" maxWidth="150" id="hzb-XI-Out">
@ -1092,13 +1136,6 @@ Gw
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="ZiS-Gq-TLQ">
<rect key="frame" x="298" y="150" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="XK3-AR-Oc0"/>
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
</constraints>
</progressIndicator>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="wcV-ed-8Bv">
<rect key="frame" x="113" y="5" width="400" height="300"/>
<constraints>
@ -1106,27 +1143,57 @@ Gw
<constraint firstAttribute="height" constant="300" id="Xpi-Rl-xmb"/>
</constraints>
</customView>
<visualEffectView hidden="YES" blendingMode="behindWindow" material="popover" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="r8h-6t-ZNm">
<rect key="frame" x="263" y="125" width="100" height="80"/>
<subviews>
<progressIndicator wantsLayer="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="ZiS-Gq-TLQ">
<rect key="frame" x="35" y="35" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="XK3-AR-Oc0"/>
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
</constraints>
</progressIndicator>
<textField wantsLayer="YES" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xoy-5Y-WDT">
<rect key="frame" x="15" y="14" width="71" height="13"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="PLEASE WAIT" id="tMX-Ky-caT">
<font key="font" metaFont="system" size="10"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="xoy-5Y-WDT" firstAttribute="top" secondItem="ZiS-Gq-TLQ" secondAttribute="bottom" constant="8" symbolic="YES" id="4pN-Xn-po4"/>
<constraint firstAttribute="width" constant="100" id="Fo3-MY-e5e"/>
<constraint firstItem="xoy-5Y-WDT" firstAttribute="centerX" secondItem="r8h-6t-ZNm" secondAttribute="centerX" id="JPe-3T-uYg"/>
<constraint firstAttribute="height" constant="80" id="hcO-TE-dKr"/>
<constraint firstItem="ZiS-Gq-TLQ" firstAttribute="centerX" secondItem="r8h-6t-ZNm" secondAttribute="centerX" id="sbD-l6-6kk"/>
<constraint firstItem="ZiS-Gq-TLQ" firstAttribute="centerY" secondItem="r8h-6t-ZNm" secondAttribute="centerY" constant="-10" id="x7S-hb-YV1"/>
</constraints>
</visualEffectView>
</subviews>
<constraints>
<constraint firstItem="p0j-eB-I2i" firstAttribute="leading" secondItem="rIZ-4U-bhj" secondAttribute="leading" id="2Tx-yb-xrv"/>
<constraint firstItem="wcV-ed-8Bv" firstAttribute="centerX" secondItem="rIZ-4U-bhj" secondAttribute="centerX" id="DPz-kQ-aP0"/>
<constraint firstItem="wcV-ed-8Bv" firstAttribute="centerY" secondItem="rIZ-4U-bhj" secondAttribute="centerY" id="HCW-zJ-gSY"/>
<constraint firstItem="r8h-6t-ZNm" firstAttribute="centerX" secondItem="rIZ-4U-bhj" secondAttribute="centerX" id="JF1-EN-aWm"/>
<constraint firstItem="p0j-eB-I2i" firstAttribute="top" secondItem="rIZ-4U-bhj" secondAttribute="top" id="Pst-5A-dI0"/>
<constraint firstAttribute="bottom" secondItem="p0j-eB-I2i" secondAttribute="bottom" id="QEw-5m-u1s"/>
<constraint firstItem="ZiS-Gq-TLQ" firstAttribute="centerY" secondItem="rIZ-4U-bhj" secondAttribute="centerY" constant="-10" id="XqX-Tf-8ck"/>
<constraint firstItem="ZiS-Gq-TLQ" firstAttribute="centerX" secondItem="rIZ-4U-bhj" secondAttribute="centerX" id="eD8-TV-7dF"/>
<constraint firstItem="r8h-6t-ZNm" firstAttribute="centerY" secondItem="rIZ-4U-bhj" secondAttribute="centerY" constant="-10" id="dkm-LB-eCY"/>
<constraint firstAttribute="trailing" secondItem="p0j-eB-I2i" secondAttribute="trailing" id="zWH-TD-RZv"/>
</constraints>
</view>
<connections>
<outlet property="labelProgressIndicator" destination="xoy-5Y-WDT" id="Wfj-oK-Bni"/>
<outlet property="noResultsView" destination="wcV-ed-8Bv" id="K3s-fb-1aN"/>
<outlet property="progressIndicator" destination="ZiS-Gq-TLQ" id="Ylb-Vk-uub"/>
<outlet property="progressIndicatorContainer" destination="r8h-6t-ZNm" id="x0d-1g-Kzw"/>
<outlet property="tableView" destination="cp3-34-pQj" id="sdw-Ac-27X"/>
</connections>
</viewController>
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="323" y="723"/>
<point key="canvasLocation" x="323" y="722.5"/>
</scene>
<!--Add ProxyVC-->
<scene sceneID="g8z-pE-RL9">
@ -1355,7 +1422,7 @@ Gw
<visualEffectView blendingMode="behindWindow" material="toolTip" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="F37-zt-gM3">
<rect key="frame" x="0.0" y="0.0" width="540" height="177"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FhN-AM-SkI">
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FhN-AM-SkI">
<rect key="frame" x="13" y="13" width="114" height="32"/>
<buttonCell key="cell" type="push" title="[i18n] Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="LxP-t4-H2W">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -1374,7 +1441,7 @@ Gw
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pYe-Qu-qnK">
<rect key="frame" x="187" y="20" width="333" height="20"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="L5n-Gw-J27">
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="L5n-Gw-J27">
<rect key="frame" x="-7" y="-7" width="172" height="32"/>
<buttonCell key="cell" type="push" title="[i18n] Create a Link" bezelStyle="rounded" image="IconLinked" imagePosition="left" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="8UP-Sw-TP6">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -1385,7 +1452,7 @@ Gw
<action selector="pressedCreateLink:" target="gOD-Gu-zDG" id="77M-Ip-GMi"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="01Z-IV-hv1">
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="01Z-IV-hv1">
<rect key="frame" x="159" y="-7" width="181" height="32"/>
<buttonCell key="cell" type="push" title="[i18n] Create a Proxy" bezelStyle="rounded" image="IconProxy" imagePosition="left" alignment="center" borderStyle="border" imageScaling="proportionallyUpOrDown" inset="2" id="bJ4-q8-1Ej">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -1503,6 +1570,10 @@ Gw
<image name="Lock" width="30" height="30"/>
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="14" height="13"/>
<image name="star.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="IconColorGreen">
<color red="0.24699999392032623" green="0.69700002670288086" blue="0.50099998712539673" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>

View File

@ -8,6 +8,7 @@
import Foundation
import Cocoa
import NVAlert
class ValetServicesManager: ServicesManager {
override init() {
@ -131,7 +132,7 @@ class ValetServicesManager: ServicesManager {
Log.err("The service '\(named)' is now reporting an error.")
guard let errorLogPath = after.error_log_path else {
return BetterAlert().withInformation(
return NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.no_error_log".localized(named),
description: "alert.service_error.extra".localized
@ -140,7 +141,7 @@ class ValetServicesManager: ServicesManager {
.show()
}
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.service_error.title".localized(named),
subtitle: "alert.service_error.subtitle.error_log".localized(named),
description: "alert.service_error.extra".localized

View File

@ -0,0 +1,74 @@
//
// Startup+Timers.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/07/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
import AppKit
import NVAlert
extension Startup {
@MainActor static var startupTimer: Timer?
@MainActor static var launchTime: Date?
/** Returns a human-readable version to indicate how many seconds elapsed since boot. */
@MainActor static var humanReadableSinceBootTime: String {
return String(format: "%.2f", Date().timeIntervalSince(Self.launchTime!))
}
/** Starts the timeout timer that keeps track of how long the app takes to boot. */
@MainActor func startStartupTimer() {
Self.launchTime = Date()
Self.startupTimer = Timer.scheduledTimer(
timeInterval: Constants.SlowBootThresholdInterval, target: self,
selector: #selector(startupTimeout), userInfo: nil, repeats: false
)
}
/**
Invalidates and stops the startup timer.
This is only called if the slow boot threshold is not exceeded.
*/
@MainActor static func invalidateTimeoutTimer() {
if Self.startupTimer == nil {
return
}
Log.info("PHP Monitor was quick; elapsed time: \(Self.humanReadableSinceBootTime) sec.")
Self.startupTimer?.invalidate()
Self.startupTimer = nil
}
/**
Displays an alert for when the application startup process takes too long.
*/
@MainActor @objc func startupTimeout() {
Log.info("PHP Monitor was slow; elapsed time: \(Self.humanReadableSinceBootTime) sec.")
// Invalidate the timer
Self.startupTimer?.invalidate()
Self.startupTimer = nil
// Present an alert that lets the user know about the slow start
NVAlert()
.withInformation(
title: "startup.timeout.title".localized,
subtitle: "startup.timeout.subtitle".localized,
description: "startup.timeout.description".localized
)
.withPrimary(text: "alert.cannot_start.close".localized, action: { vc in
vc.close(with: .alertFirstButtonReturn)
exit(1)
})
.withSecondary(text: "startup.timeout.ignore".localized, action: { vc in
vc.close(with: .alertSecondButtonReturn)
})
.withTertiary(text: "", action: { _ in
NSWorkspace.shared.open(URL(string: "https://github.com/nicoverbruggen/phpmon/issues/294")!)
})
.show()
}
}

View File

@ -7,9 +7,9 @@
import Foundation
import AppKit
import NVAlert
class Startup {
/**
Checks the user's environment and checks if PHP Monitor can be used properly.
This checks if PHP is installed, Valet is running, the appropriate permissions are set, and more.
@ -21,6 +21,11 @@ class Startup {
// Do the important system setup checks
Log.info("The user is running PHP Monitor with the architecture: \(App.architecture)")
// Set up a "background" timer on the main thread
Task { @MainActor in
startStartupTimer()
}
for group in self.groups {
if group.condition() {
Log.info("Now running \(group.checks.count) \(group.name) checks!")
@ -44,6 +49,7 @@ class Startup {
// If we get here, nothing has gone wrong. That's what we want!
initializeSwitcher()
Log.info("PHP Monitor has determined the application has successfully passed all checks.")
Log.separator(as: .info)
return true
}
@ -55,7 +61,7 @@ class Startup {
*/
@MainActor private func showAlert(for check: EnvironmentCheck) {
if check.requiresAppRestart {
BetterAlert()
NVAlert()
.withInformation(
title: check.titleText,
subtitle: check.subtitleText,
@ -66,7 +72,7 @@ class Startup {
}).show()
}
BetterAlert()
NVAlert()
.withInformation(
title: check.titleText,
subtitle: check.subtitleText,

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
@MainActor class ComposerWindow {
private var shouldNotify: Bool! = nil
@ -109,7 +110,7 @@ import Foundation
// MARK: Alert
private func presentMissingAlert() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.composer_missing.title".localized,
subtitle: "alert.composer_missing.subtitle".localized,

View File

@ -15,7 +15,8 @@ struct ProjectTypeDetection {
public static let CommonDependencyList = [
"laravel/framework": "Laravel",
"symfony/symfony": "Symfony",
"laravel/lumen": "Lumen"
"laravel/lumen": "Lumen",
"tempest/framework": "Tempest"
]
/**

View File

@ -35,10 +35,11 @@ class Brew {
/// Each formula for each PHP version that can be installed.
public static let phpVersionFormulae = [
"8.5": "shivammathur/php/php@8.5",
"8.4": "shivammathur/php/php@8.4",
"8.3": "php@8.3",
"8.2": "php@8.2",
"8.1": "php@8.1",
"8.3": "shivammathur/php/php@8.3",
"8.2": "shivammathur/php/php@8.2",
"8.1": "shivammathur/php/php@8.1",
"8.0": "shivammathur/php/php@8.0",
"7.4": "shivammathur/php/php@7.4",
"7.3": "shivammathur/php/php@7.3",

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
class BrewDiagnostics {
/**
@ -183,7 +184,7 @@ class BrewDiagnostics {
*/
private static func presentAlertAboutConflict() {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_alias_conflict.title".localized,
subtitle: "alert.php_alias_conflict.info".localized

View File

@ -21,11 +21,12 @@ struct BrewPhpFormula: Equatable {
/// The upgrade that is currently available, if it exists.
let upgradeVersion: String?
// TODO: A rebuild attribute could be checked, to check if a Tap update exists for a pre-release version
/// Whether this formula is a stable version of PHP.
let prerelease: Bool
/// Whether this formula's associated Homebrew file exists.
var hasFormulaFile: Bool = false
/// Whether the formula is currently installed.
var isInstalled: Bool {
return installedVersion != nil
@ -43,6 +44,7 @@ struct BrewPhpFormula: Equatable {
self.installedVersion = installedVersion
self.upgradeVersion = upgradeVersion
self.prerelease = prerelease
self.hasFormulaFile = checkFormulaFile()
}
/// Whether the formula can be upgraded.
@ -93,6 +95,21 @@ struct BrewPhpFormula: Equatable {
return isHealthy() ?? true
}
/**
Verify whether the formula file exists (sourced via `shivammathur/homebrew-php`).
If it does not exist, the formula cannot be installed.
*/
private func checkFormulaFile() -> Bool {
guard let version = shortVersion else {
return false
}
return FileSystem.fileExists(
"\(Paths.tapPath)/shivammathur/homebrew-php/Formula/php@\(version).rb"
.replacingOccurrences(of: "php@" + PhpEnvironments.brewPhpAlias, with: "php")
)
}
/**
* Determines if this PHP installation is healthy.
* Uses the cached installation health check as basis.

View File

@ -39,19 +39,25 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
OutdatedFormulae.self,
from: rawJsonText
).formulae.filter({ formula in
formula.name.starts(with: "php")
formula.name.starts(with: "shivammathur/php/php") || formula.name.starts(with: "php")
})
}
return Brew.phpVersionFormulae.map { (version, formula) in
var fullVersion: String?
var upgradeVersion: String?
var isPrerelease: Bool = Constants.ExperimentalPhpVersions.contains(version)
if let install = PhpEnvironments.shared.cachedPhpInstallations[version] {
fullVersion = install.versionNumber.text
fullVersion = install.isPreRelease ? "\(fullVersion!)-dev" : fullVersion
upgradeVersion = outdated?.first(where: { formula in
return formula.name == install.formulaName
return formula.name.replacingOccurrences(of: "shivammathur/php/", with: "")
== install.formulaName.replacingOccurrences(of: "shivammathur/php/", with: "")
})?.current_version
isPrerelease = install.isPreRelease
}
return BrewPhpFormula(
@ -59,7 +65,7 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
displayName: "PHP \(version)",
installedVersion: fullVersion,
upgradeVersion: upgradeVersion,
prerelease: Constants.ExperimentalPhpVersions.contains(version)
prerelease: isPrerelease
)
}.sorted { $0.displayName > $1.displayName }
}

View File

@ -16,21 +16,65 @@ protocol BrewCommand {
extension BrewCommand {
internal func reportInstallationProgress(_ text: String) -> (Double, String)? {
if text.contains("Fetching") {
// Special cases: downloading a manifest is effectively fetching metadata
if text.contains("==> Downloading") && text.contains("/manifests/") {
return (0.1, "phpman.steps.fetching".localized)
}
if text.contains("Downloading") {
return (0.25, "phpman.steps.downloading".localized)
}
if text.contains("Installing") {
return (0.60, "phpman.steps.installing".localized)
}
if text.contains("Pouring") {
return (0.80, "phpman.steps.pouring".localized)
}
if text.contains("Summary") {
// Logical progress evaluation (reverse order for accuracy)
if text.contains("==> Summary") {
return (0.90, "phpman.steps.summary".localized)
}
if text.contains("==> Pouring") {
if let subject = extractContext(from: text) {
return (0.80, "phpman.steps.pouring".localized + "\n(\(subject))")
}
return (0.80, "phpman.steps.pouring".localized)
}
if text.contains("==> Installing") {
if let subject = extractContext(from: text) {
return (0.60, "phpman.steps.installing".localized + "\n(\(subject))")
}
return (0.60, "phpman.steps.installing".localized)
}
if text.contains("==> Downloading") {
if let subject = extractContext(from: text) {
return (0.25, "phpman.steps.downloading".localized + "\n(\(subject))")
}
return (0.25, "phpman.steps.downloading".localized)
}
if text.contains("==> Fetching") {
return (0.1, "phpman.steps.fetching".localized)
}
return nil
}
internal func extractContext(from text: String) -> String? {
var pattern = #""#
if text.contains("==> Fetching") {
pattern = #"==> Fetching (\S+)"#
}
if text.contains("==> Downloading") {
pattern = #"==> Downloading (\S+)"#
}
if text.contains("==> Installing") {
pattern = #"==> Installing (\S+)"#
}
if text.contains("==> Pouring") {
pattern = #"==> Pouring (\S+)"#
}
guard let regex = try? NSRegularExpression(pattern: pattern) else {
return nil
}
let range = NSRange(text.startIndex..<text.endIndex, in: text)
if let match = regex.firstMatch(in: text, options: [], range: range) {
if let formulaRange = Range(match.range(at: 1), in: text) {
return String(text[formulaRange])
}
}
return nil
}

View File

@ -51,7 +51,15 @@ class ValetInteractor {
// Keep track of the command we wish to run
let action = site.secured ? "unsecure" : "secure"
let command = "cd '\(site.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
// Use modernized version of command using domain name
// This will allow us to secure multiple domains that use the same path
var command = "sudo \(Paths.valet) \(action) '\(site.name)' && exit;"
// For Valet 2, use the old syntax; this has a known issue so Valet 3+ is preferred
if !Valet.enabled(feature: .isolatedSites) {
command = "cd '\(site.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
}
// Run the command
await Shell.quiet(command)

View File

@ -24,4 +24,6 @@ protocol ValetListable {
func getListableUrl() -> URL?
func getListableFavorited() -> Bool
}

View File

@ -14,6 +14,11 @@ class ValetProxy: ValetListable {
var target: String
var secured: Bool = false
var favorited: Bool = false
var favoriteSignature: String {
"proxy:domain:\(domain).\(tld)|target:\(target)"
}
init(domain: String, target: String, secure: Bool, tld: String) {
self.domain = domain
self.tld = tld
@ -28,6 +33,8 @@ class ValetProxy: ValetListable {
secure: false,
tld: configuration.tld
)
self.favorited = Favorites.shared.contains(domain: self.domain)
self.determineSecured()
}
@ -61,12 +68,21 @@ class ValetProxy: ValetListable {
return URL(string: "\(self.secured ? "https://" : "http://")\(self.domain).\(self.tld)")
}
func getListableFavorited() -> Bool {
return self.favorited
}
// MARK: - Interactions
func determineSecured() {
self.secured = FileSystem.fileExists("~/.config/valet/Certificates/\(self.domain).\(self.tld).key")
}
func toggleFavorite() {
self.favorited.toggle()
Favorites.shared.toggle(domain: self.favoriteSignature)
}
func toggleSecure() async throws {
try await ValetInteractor.shared.toggleSecure(proxy: self)
}

View File

@ -61,6 +61,11 @@ class ValetSite: ValetListable {
?? "???"
}
var favorited: Bool = false
var favoriteSignature: String {
"site:domain:\(name).\(tld)|path:\(absolutePath)"
}
init(
name: String,
tld: String,
@ -75,6 +80,7 @@ class ValetSite: ValetListable {
self.secured = false
if makeDeterminations {
self.favorited = Favorites.shared.contains(domain: favoriteSignature)
determineSecured()
determineIsolated()
determineComposerPhpVersion()
@ -305,12 +311,21 @@ class ValetSite: ValetListable {
return URL(string: "\(self.secured ? "https://" : "http://")\(self.name).\(Valet.shared.config.tld)")
}
func getListableFavorited() -> Bool {
return self.favorited
}
// MARK: - Interactions
func toggleSecure() async throws {
try await ValetInteractor.shared.toggleSecure(site: self)
}
func toggleFavorite() {
self.favorited.toggle()
Favorites.shared.toggle(domain: self.favoriteSignature)
}
func isolate(version: String) async throws {
try await ValetInteractor.shared.isolate(site: self, version: version)
}

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
extension Valet {
@ -16,7 +17,7 @@ extension Valet {
public func notifyAboutUnsupportedTLD() {
if Valet.shared.config.tld != "test" && Preferences.isEnabled(.warnAboutNonStandardTLD) {
Task { @MainActor in
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.warnings.tld_issue.title".localized,
subtitle: "alert.warnings.tld_issue.subtitle".localized,
description: "alert.warnings.tld_issue.description".localized
@ -33,7 +34,7 @@ extension Valet {
public func notifyAboutOutdatedValetVersion(_ version: VersionNumber) {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.min_valet_version.title".localized,
subtitle: "alert.min_valet_version.info".localized(
@ -60,7 +61,7 @@ extension Valet {
}
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_fpm_broken.title".localized,
subtitle: "alert.php_fpm_broken.info".localized,

View File

@ -7,6 +7,7 @@
//
import Cocoa
import NVAlert
extension MainMenu {
@ -20,7 +21,7 @@ extension MainMenu {
@MainActor @objc func displayUnlinkedInfo() {
Task { @MainActor in
BetterAlert()
NVAlert()
.withInformation(
title: "phpman.unlinked.title".localized,
subtitle: "phpman.unlinked.desc".localized,
@ -32,7 +33,7 @@ extension MainMenu {
}
@MainActor @objc func fixHomebrewPermissions() {
if !BetterAlert()
if !NVAlert()
.withInformation(
title: "alert.fix_homebrew_permissions.title".localized,
subtitle: "alert.fix_homebrew_permissions.subtitle".localized,
@ -47,7 +48,7 @@ extension MainMenu {
asyncExecution {
try Actions.fixHomebrewPermissions()
} success: {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_homebrew_permissions_done.title".localized,
subtitle: "alert.fix_homebrew_permissions_done.subtitle".localized,
@ -56,7 +57,7 @@ extension MainMenu {
.withPrimary(text: "generic.ok".localized)
.show()
} failure: { error in
BetterAlert.show(for: error as! HomebrewPermissionError)
NVAlert.show(for: error as! HomebrewPermissionError)
}
}
@ -175,7 +176,7 @@ extension MainMenu {
return
}
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.revert_description.title".localized,
subtitle: "alert.revert_description.subtitle".localized(
preset.textDescription
@ -196,7 +197,7 @@ extension MainMenu {
}
@MainActor @objc func showPresetHelp() {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "preset_help_title".localized,
subtitle: "preset_help_info".localized,
description: "preset_help_desc".localized
@ -263,7 +264,7 @@ extension MainMenu {
Task { MainMenu.shared.switchToPhpVersion(version) }
} else {
Task {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.php_switch_unavailable.title".localized,
subtitle: "alert.php_switch_unavailable.subtitle".localized(version)
).withPrimary(

View File

@ -8,6 +8,7 @@
import Foundation
import AppKit
import NVAlert
extension MainMenu {
@ -19,7 +20,7 @@ extension MainMenu {
return
}
if !BetterAlert()
if !NVAlert()
.withInformation(
title: "alert.fix_my_valet.title".localized,
subtitle: "alert.fix_my_valet.info".localized(PhpEnvironments.brewPhpAlias)
@ -43,7 +44,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForMissingFormula() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.php_formula_missing.title".localized,
subtitle: "alert.php_formula_missing.info".localized
@ -53,7 +54,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForSameVersion() {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,
subtitle: "alert.fix_my_valet_done.subtitle".localized,
@ -64,7 +65,7 @@ extension MainMenu {
}
@MainActor private func presentAlertForDifferentVersion(version: String) {
BetterAlert()
NVAlert()
.withInformation(
title: "alert.fix_my_valet_done.title".localized,
subtitle: "alert.fix_my_valet_done.subtitle".localized,

View File

@ -7,6 +7,7 @@
//
import Cocoa
import NVAlert
extension MainMenu {
/**
@ -102,20 +103,20 @@ extension MainMenu {
// Find out which services are active
Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.")
// Post-launch stats and update check, but only if not running tests
await performPostLaunchActions()
// Check if the linked version has changed between launches of phpmon
PhpGuard().compareToLastGlobalVersion()
// We are ready!
PhpEnvironments.shared.isBusy = false
// Finally!
Log.info("PHP Monitor is ready to serve!")
// Avoid showing the "startup timeout" alert
Startup.invalidateTimeoutTimer()
// Check if we upgraded from a previous version
AppUpdater.checkIfUpdateWasPerformed()
// Post-launch stats and update check, but only if not running tests
await performPostLaunchActions()
}
/**
@ -123,18 +124,24 @@ extension MainMenu {
(This code is skipped when running SwiftUI previews.)
*/
private func performPostLaunchActions() async {
if !isRunningSwiftUIPreview {
Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
if isRunningSwiftUIPreview {
return
}
if Stats.successfulLaunchCount == 1 {
Log.info("Should present the first launch screen!")
Task { @MainActor in
OnboardingWindowController.show()
}
} else {
await AppUpdater().checkForUpdates(userInitiated: false)
Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
if Stats.successfulLaunchCount == 1 {
Log.info("Should present the first launch screen!")
Task { @MainActor in
OnboardingWindowController.show()
}
} else {
// Check for updates
await AppUpdater().checkForUpdates(userInitiated: false)
// Check if the linked version has changed between launches of phpmon
await PhpGuard().compareToLastGlobalVersion()
}
}
@ -143,7 +150,7 @@ extension MainMenu {
*/
private func onEnvironmentFail() async {
Task { @MainActor [self] in
BetterAlert()
NVAlert()
.withInformation(
title: "alert.cannot_start.title".localized,
subtitle: "alert.cannot_start.subtitle".localized,

View File

@ -7,6 +7,7 @@
//
import Foundation
import NVAlert
extension MainMenu {
@ -75,7 +76,7 @@ extension MainMenu {
}
@MainActor private func suggestFixMyValet(failed version: String) {
let outcome = BetterAlert()
let outcome = NVAlert()
.withInformation(
title: "alert.php_switch_failed.title".localized(version),
subtitle: "alert.php_switch_failed.info".localized(version),
@ -90,7 +91,7 @@ extension MainMenu {
}
@MainActor private func suggestFixMyComposer() {
BetterAlert().withInformation(
NVAlert().withInformation(
title: "alert.global_composer_platform_issues.title".localized,
subtitle: "alert.global_composer_platform_issues.subtitle".localized,
description: "alert.global_composer_platform_issues.desc".localized

Some files were not shown because too many files have changed in this diff Show More