1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-12-21 03:10:06 +01:00

🚀 Version 25.12

This commit is contained in:
2025-12-06 12:54:34 +01:00
246 changed files with 2048 additions and 1050 deletions

View File

@@ -1,22 +1,16 @@
# Contribution Guidelines
Thank you for your interest in contributing to PHP Monitor.
Thank you for your interest in contributing to PHP Monitor!
I consider this project a bit of a nice side-project to my daily gig, so it is very much a personal affair where I love to tinker around.
While the code of the latest PHP Monitor release is public, many things are constantly in flux that may not be pushed to this repository yet.
**While the code of the latest PHP Monitor release is public, many things are constantly in flux that may not be pushed to this repository yet.**
In particular, certain changes may only be available to [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) via the [EAP repository](https://github.com/phpmon/early-access).
I don't mean to be rude, but I don't want other people involved with the project beyond simply contributing a few small things here and there, as has been the case in the past.
Please consider creating an issue before working on anything related to the project, so that I can confirm you are not just wasting your time.
The extra mental overhead of having additional contributors to report to, whose code will need to be reviewed... it's a lot and it makes working on PHP Monitor less enjoyable for me.
**Making any changes in a fork and opening a pull request WITHOUT properly documenting your changes and referencing an issue may require me to close your PR.**
Plus, at this point, the majority of PHP Monitor's main functionality is also done.
As a result, I may refer you to this file at some point. Again, I don't wish to be rude, but this general rule stands:
**Making any changes in a fork and opening a pull request without opening an issue first will most likely result in your PR being closed without mercy.**
To repeat, I am **not opposed** to small contributions and fixes, if they are **meaningful or insightful**.
To repeat, I am not opposed to small contributions and fixes, if they are meaningful or insightful, but low effort changes are generally not accepted.
To learn more, please check out the [pull request template](/.github/pull_request_template.md) which contains more information about my contribution requirements. (This will also show up when you open a new PR.)

View File

@@ -2,25 +2,26 @@ Hello there! Thank you for considering a pull request for PHP Monitor.
Please read the text below first before you submit your PR.
## Do not PR unless...
## Keep this in mind!
In order to make development and maintenance of PHP Monitor easier, I ask that you _avoid_ making a pull request in the following situations:
* No issue has been associated with the changes youd like to merge
* You have not announced you will be addressing a particular issue
* The PR is a low effort change: e.g. commits that only fix typos or phrasing may not be accepted
(If you believe the phrasing of particular text in the app is unclear or incorrect, please open an issue first.)
In short: It is usually best to *get in touch first* if you are making substantial changes.
- Some code changes available to the sponsor repository may not be pushed to the public repository yet, so it's common that the public repository is a little behind.
- Because of this, it is usually best to *get in touch first* if you are making substantial changes.
- Low effort changes may not be accepted.
- When in doubt, open an issue or discussion and ask me if it's worth doing something.
## About destination branches
Please keep in mind that `main` is reserved for the current code state of the latest release and should *never* be the destination branch unless a new release is happening. **Pull requests that target `main` will be closed without mercy.**
Please keep in mind that `main` is reserved for the current code state of the latest release and should generally *not* be the destination branch unless a new release is happening.
Usually, the best target is the stable `dev/x.x` branch that corresponds with the latest major version that is released.
**Pull requests that target `main` will usually be retargeted.**
There may be a newer branch available, which is an appropriate place for bigger changes, but please keep in mind that it is usually best to announce youll be working on such a change before you spend the time, since as the lead contributor I might not even want said change in the app. Thank you.
Usually, the best target is the stable `dev/x.x` branch that corresponds with the latest major version that is released, although that branch may not be available or up-to-date at all times.
There may be a newer branch available, which is an appropriate place for bigger changes, but please keep in mind that it is usually best to announce youll be working on such a change before you spend the time, since as the lead contributor I might not even want said change in the app.
Thank you.
---
## Your changes
@@ -29,7 +30,7 @@ There may be a newer branch available, which is an appropriate place for bigger
* Affected parts of the app: shared code / UI code / CLI (remove what does not apply)
* Estimated impact on performance: none / low / high (remove what does not apply)
* Made a new build with Xcode and tested this: yes / no (remove what does not apply)
* Tested on macOS version + architecture: (e.g. "Monterey on M1" or "Big Sur on Intel")
* Tested on macOS version + architecture: (e.g. "Tahoe on M4" or "Ventura on Intel")
* References issue(s): (please reference the issue here, using # and the number of the issue)
(please describe what you have changed here)

View File

@@ -16,6 +16,12 @@ included:
excluded:
- phpmon/Vendor
type_body_length:
warning: 300
function_body_length:
warning: 100
line_length:
ignores_function_declarations: true
ignores_comments: true

View File

@@ -11,6 +11,9 @@
0310B17A2EB8F3FF00A8B140 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 0310B1792EB8F3FF00A8B140 /* CrashReporter */; };
0310B17C2EB8F40100A8B140 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 0310B17B2EB8F40100A8B140 /* CrashReporter */; };
0310B17E2EB8F40400A8B140 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 0310B17D2EB8F40400A8B140 /* CrashReporter */; };
0317C17E2ED87CAB005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C17D2ED87CAB005479D2 /* NVAppUpdater */; };
0317C1812ED87CE1005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C1802ED87CE1005479D2 /* NVAppUpdater */; };
0317C1832ED87CEA005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C1822ED87CEA005479D2 /* NVAppUpdater */; };
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
031E2B6A2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031E2B682B1525A7007C29E1 /* BrewPhpExtension.swift */; };
@@ -28,6 +31,10 @@
0329A9A42E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A52E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A62E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
032C7A022EE43B7600758D98 /* Suspendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C7A012EE43B7400758D98 /* Suspendable.swift */; };
032C7A032EE43B7600758D98 /* Suspendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C7A012EE43B7400758D98 /* Suspendable.swift */; };
032C7A042EE43B7600758D98 /* Suspendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C7A012EE43B7400758D98 /* Suspendable.swift */; };
032C7A052EE43B7600758D98 /* Suspendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032C7A012EE43B7400758D98 /* Suspendable.swift */; };
032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC292E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
@@ -63,6 +70,36 @@
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39132E5CB820008DAEDF /* TestBundle.swift */; };
036C3A212E5CBBAA008DAEDF /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; };
0379C49F2ED71D050035D7EA /* Startup+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */; };
0379C4A02ED71D050035D7EA /* Startup+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */; };
0379C4A12ED71D050035D7EA /* Startup+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */; };
0379C4A22ED71D050035D7EA /* Startup+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */; };
0379C4A42ED720220035D7EA /* App+DetectApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */; };
0379C4A52ED720220035D7EA /* App+DetectApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */; };
0379C4A62ED720220035D7EA /* App+DetectApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */; };
0379C4A72ED720220035D7EA /* App+DetectApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */; };
037F44162EDB0AAA002EBF75 /* FSNotifierTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44152EDB0AA8002EBF75 /* FSNotifierTest.swift */; };
037F44182EDB27BA002EBF75 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44172EDB27B7002EBF75 /* Debouncer.swift */; };
037F44192EDB27BA002EBF75 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44172EDB27B7002EBF75 /* Debouncer.swift */; };
037F441A2EDB27BA002EBF75 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44172EDB27B7002EBF75 /* Debouncer.swift */; };
037F441B2EDB27BA002EBF75 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44172EDB27B7002EBF75 /* Debouncer.swift */; };
037F441D2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */; };
037F441E2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */; };
037F441F2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */; };
037F44202EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */; };
037F44222EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */; };
037F44232EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */; };
037F44242EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */; };
037F44252EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */; };
0386B0B42ED36C3D00CA6795 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386B0B32ED36C3D00CA6795 /* Locked.swift */; };
0386B0B52ED36C3D00CA6795 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386B0B32ED36C3D00CA6795 /* Locked.swift */; };
0386B0B62ED36C3D00CA6795 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386B0B32ED36C3D00CA6795 /* Locked.swift */; };
0386B0B72ED36C3D00CA6795 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386B0B32ED36C3D00CA6795 /* Locked.swift */; };
0386B0BC2ED36DF800CA6795 /* LockedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386B0B82ED36DF800CA6795 /* LockedTests.swift */; };
038A2B7E2EDDB24C00173ACF /* App+UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038A2B7D2EDDB24400173ACF /* App+UUID.swift */; };
038A2B7F2EDDB24C00173ACF /* App+UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038A2B7D2EDDB24400173ACF /* App+UUID.swift */; };
038A2B802EDDB24C00173ACF /* App+UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038A2B7D2EDDB24400173ACF /* App+UUID.swift */; };
038A2B812EDDB24C00173ACF /* App+UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038A2B7D2EDDB24400173ACF /* App+UUID.swift */; };
0392CDE62EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
0392CDE72EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
0392CDE82EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
@@ -71,10 +108,6 @@
0392CDEC2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
0392CDED2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
0392CDEE2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039616102E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29192E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
@@ -297,10 +330,6 @@
C4415E8E2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */; };
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */; };
C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */; };
C441CC562AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; };
C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; };
@@ -414,7 +443,6 @@
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 */; };
@@ -545,7 +573,6 @@
C471E84B28F9BB650021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; };
C471E84D28F9BB650021E251 /* Valet+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* Valet+Alerts.swift */; };
C471E84E28F9BB650021E251 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C471E84F28F9BB650021E251 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C471E85028F9BB650021E251 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C471E85128F9BB650021E251 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
C471E85228F9BB650021E251 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
@@ -585,8 +612,6 @@
C471E87728F9BB650021E251 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; };
C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C471E87C28F9BB650021E251 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C471E87D28F9BB650021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; };
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@@ -632,7 +657,6 @@
C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; };
C471E8B028F9BB8F0021E251 /* Valet+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* Valet+Alerts.swift */; };
C471E8B128F9BB8F0021E251 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C471E8B228F9BB8F0021E251 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C471E8B328F9BB8F0021E251 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
@@ -672,8 +696,6 @@
C471E8DA28F9BB8F0021E251 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; };
C471E8DB28F9BB8F0021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C471E8DF28F9BB8F0021E251 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C471E8E028F9BB8F0021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; };
C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@@ -737,10 +759,6 @@
C48DDD0E29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C48DDD0F29C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C48DDD1029C75C9E00D032D9 /* BlockingOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */; };
C490E3B629BCA367006D2DE6 /* App+BrewWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */; };
C490E3B829BCA367006D2DE6 /* App+BrewWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */; };
C490E3B929BCA368006D2DE6 /* App+BrewWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */; };
C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */; };
C490E3BB29BCA375006D2DE6 /* Measurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5129B12A5A00AB28FC /* Measurements.swift */; };
C490E3BC29BCA375006D2DE6 /* Measurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5129B12A5A00AB28FC /* Measurements.swift */; };
C490E3BD29BCA375006D2DE6 /* Measurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAA5129B12A5A00AB28FC /* Measurements.swift */; };
@@ -824,14 +842,9 @@
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; };
C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
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 */; };
C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */; };
C4C8900528F0E3D100CE5E97 /* RealFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */; };
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C4C8E81C276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB250429B28BB800CA4492 /* MainMenuTest.swift */; };
C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; };
C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; };
@@ -942,7 +955,6 @@
C4F30B08278E195800755FCE /* brew-services.json in Resources */ = {isa = PBXBuildFile; fileRef = C4F30B06278E195800755FCE /* brew-services.json */; };
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4F319C927B034A500AFF46F /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DEB7D327A5D60B00834718 /* Stats.swift */; };
C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; };
C4F520672AF03791006787F2 /* ExtensionEnumeratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F520662AF03791006787F2 /* ExtensionEnumeratorTest.swift */; };
@@ -1020,6 +1032,7 @@
03263A372E86D5E800BD0415 /* UpdateScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateScheduler.swift; sourceTree = "<group>"; };
0329A9A02E92A2A800A62A12 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WarningManager+Evaluations.swift"; sourceTree = "<group>"; };
032C7A012EE43B7400758D98 /* Suspendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Suspendable.swift; sourceTree = "<group>"; };
032DAC272E8BEB590018E01C /* RealWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealWebApi.swift; sourceTree = "<group>"; };
032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebApiProtocol.swift; sourceTree = "<group>"; };
0336CAAF2B0D0CDA009A1034 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1037,9 +1050,17 @@
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistP2Response.swift; sourceTree = "<group>"; };
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Launch.swift"; sourceTree = "<group>"; };
0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+DetectApps.swift"; sourceTree = "<group>"; };
037F44152EDB0AA8002EBF75 /* FSNotifierTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSNotifierTest.swift; sourceTree = "<group>"; };
037F44172EDB27B7002EBF75 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = "<group>"; };
037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewWatchManager.swift; sourceTree = "<group>"; };
0386B0B32ED36C3D00CA6795 /* Locked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locked.swift; sourceTree = "<group>"; };
0386B0B82ED36DF800CA6795 /* LockedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockedTests.swift; sourceTree = "<group>"; };
038A2B7D2EDDB24400173ACF /* App+UUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+UUID.swift"; sourceTree = "<group>"; };
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
0392CDEA2EB25371009176DA /* SecurePopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurePopoverView.swift; sourceTree = "<group>"; };
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
039C29172E8AA311007F5FAB /* TestableWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApi.swift; sourceTree = "<group>"; };
039C291C2E8AA399007F5FAB /* TestableWebApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApiTest.swift; sourceTree = "<group>"; };
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
@@ -1153,7 +1174,6 @@
C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTypeCell.swift; sourceTree = "<group>"; };
C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = "<group>"; };
C4415E8C2B0287E90035F520 /* BrewFormulaeObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewFormulaeObservable.swift; sourceTree = "<group>"; };
C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigFSNotifier.swift; sourceTree = "<group>"; };
C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = "<group>"; };
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = "<group>"; };
C4463FCB29804BCB007B93D5 /* RCFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RCFile.swift; sourceTree = "<group>"; };
@@ -1224,7 +1244,6 @@
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>"; };
C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = "<group>"; };
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
@@ -1257,12 +1276,9 @@
C4C0E8E627F88B41002D32A9 /* DomainScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainScanner.swift; sourceTree = "<group>"; };
C4C1019A27C65C6F001FACC2 /* Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = "<group>"; };
C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = "<group>"; };
C4CB6E64292C362C002E9027 /* Homebrew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Homebrew.swift; sourceTree = "<group>"; };
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
@@ -1333,7 +1349,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C47014FC2C46D31B0069AAE7 /* NVAppUpdater in Frameworks */,
0317C1832ED87CEA005479D2 /* NVAppUpdater in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1342,6 +1358,8 @@
buildActionMask = 2147483647;
files = (
C47014FF2C46D57C0069AAE7 /* NVAlert in Frameworks */,
0317C17E2ED87CAB005479D2 /* NVAppUpdater in Frameworks */,
0317C1812ED87CE1005479D2 /* NVAppUpdater in Frameworks */,
03D8462B2EB6418F006EFE3C /* CrashReporter in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1441,12 +1459,20 @@
path = Parsers;
sourceTree = "<group>";
};
0396160B2E74A617002DD7F6 /* Analytics */ = {
037F44142EDB0A9F002EBF75 /* Watchers */ = {
isa = PBXGroup;
children = (
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */,
037F44152EDB0AA8002EBF75 /* FSNotifierTest.swift */,
);
path = Analytics;
path = Watchers;
sourceTree = "<group>";
};
0386B0BD2ED36E2500CA6795 /* Helpers */ = {
isa = PBXGroup;
children = (
0386B0B82ED36DF800CA6795 /* LockedTests.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
039C29112E8AA159007F5FAB /* Http */ = {
@@ -1459,13 +1485,6 @@
path = Http;
sourceTree = "<group>";
};
039C291E2E8AA39B007F5FAB /* Api */ = {
isa = PBXGroup;
children = (
);
path = Api;
sourceTree = "<group>";
};
03ACC6432ECCAAF70070D4CD /* WebApi */ = {
isa = PBXGroup;
children = (
@@ -1527,6 +1546,7 @@
5489625628312F95004F647A /* Protocols */ = {
isa = PBXGroup;
children = (
032C7A012EE43B7400758D98 /* Suspendable.swift */,
5489625728312FAD004F647A /* CreatedFromFile.swift */,
);
path = Protocols;
@@ -2083,7 +2103,6 @@
isa = PBXGroup;
children = (
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */,
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */,
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */,
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */,
@@ -2099,6 +2118,7 @@
C4811D2822D70D9C00B5F6B3 /* Helpers */ = {
isa = PBXGroup;
children = (
0386B0B32ED36C3D00CA6795 /* Locked.swift */,
C476FF9722B0DD830098105B /* Alert.swift */,
54B48B5E275F66AE006D90C5 /* Application.swift */,
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
@@ -2139,7 +2159,6 @@
C4AF9F6B275445D300D44ED0 /* Integrations */ = {
isa = PBXGroup;
children = (
0396160B2E74A617002DD7F6 /* Analytics */,
036C38FB2E5C8827008DAEDF /* Packagist */,
C4463FD029804C13007B93D5 /* Common */,
C4C0E8DA27F887CC002D32A9 /* Nginx */,
@@ -2179,10 +2198,13 @@
C415D3E72770F692005EF286 /* AppDelegate+InterApp.swift */,
03D846312EB64E35006EFE3C /* CrashReporter.swift */,
C4811D2322D70A4700B5F6B3 /* App.swift */,
038A2B7D2EDDB24400173ACF /* App+UUID.swift */,
0379C4A32ED7201D0035D7EA /* App+DetectApps.swift */,
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */,
C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */,
C4EED88827A48778006D7272 /* InterAppHandler.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
0379C49E2ED71CFC0035D7EA /* Startup+Launch.swift */,
03BFF5262E312C39007F96FA /* Startup+Timers.swift */,
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */,
C40FE736282ABA4F00A302C2 /* AppVersion.swift */,
@@ -2302,11 +2324,10 @@
C4C8E81D276F5686003AC782 /* Watcher */ = {
isa = PBXGroup;
children = (
C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */,
C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */,
C41ADCE72970CCC700120423 /* FSNotifier.swift */,
C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */,
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */,
037F44172EDB27B7002EBF75 /* Debouncer.swift */,
037F441C2EDB9195002EBF75 /* ConfigWatchManager.swift */,
037F44212EDB92EC002EBF75 /* HomebrewWatchManager.swift */,
);
path = Watcher;
sourceTree = "<group>";
@@ -2410,12 +2431,13 @@
isa = PBXGroup;
children = (
C40C7F1C27720E1400DDDCDC /* Test Files */,
039C291E2E8AA39B007F5FAB /* Api */,
C4C1019927C65A4D001FACC2 /* Commands */,
036C39062E5C8890008DAEDF /* Integration */,
036C3A232E5CBC57008DAEDF /* Parsers */,
03D53E902E8AE089001B1671 /* Testables */,
036575C62EA12E2200BA41BF /* Versions */,
0386B0BD2ED36E2500CA6795 /* Helpers */,
037F44142EDB0A9F002EBF75 /* Watchers */,
);
path = unit;
sourceTree = "<group>";
@@ -2474,7 +2496,7 @@
);
name = "PHP Monitor Self-Updater";
packageProductDependencies = (
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */,
0317C1822ED87CEA005479D2 /* NVAppUpdater */,
);
productName = "PHP Monitor Updater";
productReference = C406A5F0298AD2CE00B5B85A /* PHP Monitor Self-Updater.app */;
@@ -2498,6 +2520,8 @@
packageProductDependencies = (
C47014FE2C46D57C0069AAE7 /* NVAlert */,
03D8462A2EB6418F006EFE3C /* CrashReporter */,
0317C17D2ED87CAB005479D2 /* NVAppUpdater */,
0317C1802ED87CE1005479D2 /* NVAppUpdater */,
);
productName = phpmon;
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
@@ -2622,9 +2646,9 @@
);
mainGroup = C41C1B2A22B0097F00E7CF16;
packageReferences = (
C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */,
C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */,
03D846292EB6418F006EFE3C /* XCRemoteSwiftPackageReference "plcrashreporter" */,
0317C17F2ED87CE1005479D2 /* XCRemoteSwiftPackageReference "NVAppUpdater" */,
);
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
projectDirPath = "";
@@ -2799,7 +2823,7 @@
0309E6672B0D4B2F002AC007 /* BrewExtensionsObservable.swift in Sources */,
C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
037F441F2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */,
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */,
032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
@@ -2812,12 +2836,13 @@
C4D5576429C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C4E49DED28F764A00026AC4E /* TestableCommand.swift in Sources */,
C41E871A2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */,
037F44222EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */,
C40C7F2827721FF600DDDCDC /* Valet+Alerts.swift in Sources */,
C463E380284930EE00422731 /* PresetHelper.swift in Sources */,
C41C02A927E61A65009F26CB /* FakeValetSite.swift in Sources */,
C4E2E85C28FC282B003B070C /* TestableConfiguration.swift in Sources */,
039616102E74A61E002DD7F6 /* LoggableEvent.swift in Sources */,
C4C0E8DF27F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */,
037F44192EDB27BA002EBF75 /* Debouncer.swift in Sources */,
C44B3A4628E5C70100718CB1 /* TimeIntervalExtension.swift in Sources */,
03C099452EA15C8E00B76D43 /* Container+Real.swift in Sources */,
C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */,
@@ -2841,6 +2866,7 @@
C40C5C9C2846A40600E28255 /* Preset.swift in Sources */,
C4B79EBC29CA38DB00A483EE /* BrewCommand.swift in Sources */,
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
0379C4A42ED720220035D7EA /* App+DetectApps.swift in Sources */,
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */,
C436B39D29F3C42500B6A64E /* PreferencesTabs.swift in Sources */,
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */,
@@ -2857,6 +2883,7 @@
C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */,
C46EBC4A28DB966A007ACC74 /* TestableShell.swift in Sources */,
C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
0379C49F2ED71D050035D7EA /* Startup+Launch.swift in Sources */,
54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
C4C0E8E727F88B41002D32A9 /* DomainScanner.swift in Sources */,
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
@@ -2889,6 +2916,7 @@
C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
03B675EA2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewDecodable.swift in Sources */,
032C7A042EE43B7600758D98 /* Suspendable.swift in Sources */,
03BFF52E2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */,
@@ -2897,9 +2925,7 @@
03DAD3A72EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */,
C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */,
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
C441CC562AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */,
C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */,
C417DC74277614690015E6EE /* Helpers.swift in Sources */,
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
@@ -2907,7 +2933,6 @@
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C422DDAA28A2C49900CEAC97 /* PhpDoctorView.swift in Sources */,
C469E6FE294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C490E3B629BCA367006D2DE6 /* App+BrewWatch.swift in Sources */,
C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */,
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */,
@@ -2932,6 +2957,7 @@
C4D3660B29113F20006BD146 /* System.swift in Sources */,
C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */,
03ACC6472ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
0386B0B52ED36C3D00CA6795 /* Locked.swift in Sources */,
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
C40C7F1E2772136000DDDCDC /* PhpEnvironments.swift in Sources */,
C4B79EB629CA387F00A483EE /* BrewPhpFormulaeHandler.swift in Sources */,
@@ -2959,7 +2985,6 @@
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */,
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
C4821C5A2C2DEDE200357A68 /* AppMenu.swift in Sources */,
C42106662AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C4B79ECB29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
@@ -2967,6 +2992,7 @@
03D846282EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */,
03CC1FE62E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
038A2B7E2EDDB24C00173ACF /* App+UUID.swift in Sources */,
C43BCD4429FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */,
C4E2E84A28FC1E70003B070C /* DataExtension.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
@@ -3029,6 +3055,7 @@
035983A22E97FA9100218DC7 /* Container.swift in Sources */,
C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */,
C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
0379C4A22ED71D050035D7EA /* Startup+Launch.swift in Sources */,
C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */,
032DAC2B2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E84728F9BB650021E251 /* Startup.swift in Sources */,
@@ -3039,7 +3066,6 @@
C471E84D28F9BB650021E251 /* Valet+Alerts.swift in Sources */,
C471E84E28F9BB650021E251 /* MainMenu.swift in Sources */,
C40934A4298EEB2C00D25014 /* CaskFile.swift in Sources */,
C471E84F28F9BB650021E251 /* MainMenu+Startup.swift in Sources */,
C471E85028F9BB650021E251 /* MainMenu+Async.swift in Sources */,
C471E85128F9BB650021E251 /* MainMenu+Switcher.swift in Sources */,
C471E85228F9BB650021E251 /* MainMenu+FixMyValet.swift in Sources */,
@@ -3055,6 +3081,7 @@
C4611E5B2AEAD2E30010BE24 /* ConfigManagerWindowController.swift in Sources */,
C471E85A28F9BB650021E251 /* DomainListTypeCell.swift in Sources */,
C471E85B28F9BB650021E251 /* DomainListKindCell.swift in Sources */,
032C7A052EE43B7600758D98 /* Suspendable.swift in Sources */,
C4611E5E2AEAD2FB0010BE24 /* ConfigManagerView.swift in Sources */,
031F24822EA1071A00CFB8D9 /* Container+Fake.swift in Sources */,
C4BF56AD2949381100379603 /* FakeValetInteractor.swift in Sources */,
@@ -3072,8 +3099,10 @@
C471E86328F9BB650021E251 /* PMTableView.swift in Sources */,
C471E86428F9BB650021E251 /* Warning.swift in Sources */,
03C29A772EC88E3100FBA25E /* ValetServicesDataManager.swift in Sources */,
037F441B2EDB27BA002EBF75 /* Debouncer.swift in Sources */,
C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */,
C43931C729C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
0379C4A52ED720220035D7EA /* App+DetectApps.swift in Sources */,
036C390A2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */,
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
@@ -3104,8 +3133,6 @@
C471E87728F9BB650021E251 /* Keys.swift in Sources */,
C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */,
C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */,
C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */,
C471E87C28F9BB650021E251 /* ConfigWatchManager.swift in Sources */,
C471E87D28F9BB650021E251 /* Preset.swift in Sources */,
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */,
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */,
@@ -3136,8 +3163,10 @@
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */,
03BFF52F2E313244007F96FA /* StatusMenu+Driver.swift in Sources */,
037F44242EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */,
C471E7F328F9BAC70021E251 /* PhpHelper.swift in Sources */,
C46DC7A62C7B5BC900F19D17 /* Favorites.swift in Sources */,
038A2B812EDDB24C00173ACF /* App+UUID.swift in Sources */,
C471E7E728F9BAC20021E251 /* Constants.swift in Sources */,
C471E81628F9BAE80021E251 /* DateExtension.swift in Sources */,
03CC1FF72E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
@@ -3157,8 +3186,6 @@
C471E7E828F9BAC20021E251 /* Actions.swift in Sources */,
C40D72612A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C471E82528F9BB2E0021E251 /* ComposerWindow.swift in Sources */,
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */,
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */,
C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
03ACC6482ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
@@ -3166,8 +3193,8 @@
031E2B6B2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C471E82728F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
C471E7DB28F9BA8F0021E251 /* RealShell.swift in Sources */,
C490E3B929BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
C471E7FF28F9BAD10021E251 /* Xdebug.swift in Sources */,
037F441D2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */,
C409349F298EE8E900D25014 /* AppUpdater.swift in Sources */,
03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C471E7F228F9BAC70021E251 /* PhpEnvironments.swift in Sources */,
@@ -3205,6 +3232,7 @@
C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */,
03DAD3A92EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */,
C4821C5C2C2DEDE200357A68 /* AppMenu.swift in Sources */,
0386B0B42ED36C3D00CA6795 /* Locked.swift in Sources */,
0392CDED2EB25371009176DA /* SecurePopoverView.swift in Sources */,
C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */,
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */,
@@ -3226,13 +3254,14 @@
033D45A12B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C471E89328F9BB8F0021E251 /* Application.swift in Sources */,
036C39022E5C883B008DAEDF /* Packagist.swift in Sources */,
0379C4A72ED720220035D7EA /* App+DetectApps.swift in Sources */,
C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */,
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C40934A5298EEB2C00D25014 /* CaskFile.swift in Sources */,
C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */,
C40D725D2A018ACC0054A067 /* BusyStatus.swift in Sources */,
C471E89628F9BB8F0021E251 /* PMWindowController.swift in Sources */,
C471E89728F9BB8F0021E251 /* VersionExtractor.swift in Sources */,
0386B0B72ED36C3D00CA6795 /* Locked.swift in Sources */,
C47DF1B2299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4E2E86728FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C471E89828F9BB8F0021E251 /* ValetProxy.swift in Sources */,
@@ -3259,16 +3288,17 @@
03DAD3A82EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */,
C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */,
C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */,
0379C4A02ED71D050035D7EA /* Startup+Launch.swift in Sources */,
C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */,
C471E8AB28F9BB8F0021E251 /* EnvironmentCheck.swift in Sources */,
C471E8AD28F9BB8F0021E251 /* AppVersion.swift in Sources */,
C471E8AE28F9BB8F0021E251 /* ServicesManager.swift in Sources */,
C471E8B028F9BB8F0021E251 /* Valet+Alerts.swift in Sources */,
C471E8B128F9BB8F0021E251 /* MainMenu.swift in Sources */,
C471E8B228F9BB8F0021E251 /* MainMenu+Startup.swift in Sources */,
C471E8B328F9BB8F0021E251 /* MainMenu+Async.swift in Sources */,
C471E8B428F9BB8F0021E251 /* MainMenu+Switcher.swift in Sources */,
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */,
038A2B7F2EDDB24C00173ACF /* App+UUID.swift in Sources */,
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
03ACC64A2ECCBA130070D4CD /* CaskFile+API.swift in Sources */,
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
@@ -3329,8 +3359,6 @@
C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */,
03D846262EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */,
C490E3BF29BCA376006D2DE6 /* Measurements.swift in Sources */,
C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */,
C471E8DF28F9BB8F0021E251 /* ConfigWatchManager.swift in Sources */,
C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */,
C40D72622A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C4B79ECE29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
@@ -3352,6 +3380,7 @@
C456A0CE2AA6166F0080144F /* BytePhpPreference.swift in Sources */,
C4FD87A829AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C45B9151295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
037F44202EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */,
C47015032C46D7F00069AAE7 /* NVAlertExtension.swift in Sources */,
C471E8EC28F9BB8F0021E251 /* SwiftUIHelper.swift in Sources */,
C471E8EE28F9BB8F0021E251 /* HotKey.swift in Sources */,
@@ -3360,7 +3389,6 @@
C471E8F128F9BB8F0021E251 /* KeyCombo.swift in Sources */,
C471E8F228F9BB8F0021E251 /* ModifierFlagsExtension.swift in Sources */,
C471E7F028F9BAC30021E251 /* Paths.swift in Sources */,
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */,
03CC1FE72E3D22120050FC18 /* InstallHomebrew.swift in Sources */,
C4CE7F9929683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C471E7FC28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
@@ -3373,7 +3401,6 @@
C4611E5A2AEAD2E20010BE24 /* ConfigManagerWindowController.swift in Sources */,
C471E80E28F9BAE80021E251 /* DateExtension.swift in Sources */,
036C390F2E5C8D42008DAEDF /* PackagistError.swift in Sources */,
C490E3BA29BCA368006D2DE6 /* App+BrewWatch.swift in Sources */,
03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */,
C471E7D028F9BA630021E251 /* FileSystemProtocol.swift in Sources */,
C471E81228F9BAE80021E251 /* TimeIntervalExtension.swift in Sources */,
@@ -3389,6 +3416,8 @@
C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */,
C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */,
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */,
037F44232EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */,
037F441A2EDB27BA002EBF75 /* Debouncer.swift in Sources */,
032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */,
C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */,
@@ -3433,6 +3462,7 @@
C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */,
035983A32E97FA9100218DC7 /* Container.swift in Sources */,
032C7A032EE43B7600758D98 /* Suspendable.swift in Sources */,
C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */,
C471E7EC28F9BAC30021E251 /* Events.swift in Sources */,
C471E7CE28F9BA600021E251 /* RealShell.swift in Sources */,
@@ -3470,15 +3500,18 @@
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */,
038A2B802EDDB24C00173ACF /* App+UUID.swift in Sources */,
03C099472EA15C8E00B76D43 /* Container+Real.swift in Sources */,
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C45D654D29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
0379C4A62ED720220035D7EA /* App+DetectApps.swift in Sources */,
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C485707528BF454F00539B36 /* StatsView.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
0386B0B62ED36C3D00CA6795 /* Locked.swift in Sources */,
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
03D846332EB64E39006EFE3C /* CrashReporter.swift in Sources */,
C485707328BF454300539B36 /* OnboardingView.swift in Sources */,
@@ -3494,17 +3527,18 @@
C485707A28BF457800539B36 /* PhpDoctorView.swift in Sources */,
C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */,
C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */,
037F44162EDB0AAA002EBF75 /* FSNotifierTest.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewDecodable.swift in Sources */,
C4C8E81C276F54E5003AC782 /* ConfigWatchManager.swift in Sources */,
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */,
54D9E0B527E4F51E003B9AD9 /* Key.swift in Sources */,
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */,
03263A3A2E86D5EC00BD0415 /* UpdateScheduler.swift in Sources */,
0379C4A12ED71D050035D7EA /* Startup+Launch.swift in Sources */,
C451AFF72969E40F0078E617 /* HelpButton.swift in Sources */,
C47DF1B0299D5A3B0007055D /* LoginItemManager.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
@@ -3516,7 +3550,6 @@
0392CDE92EB23B8F009176DA /* CertificateValidator.swift in Sources */,
C4821C5B2C2DEDE200357A68 /* AppMenu.swift in Sources */,
C463E381284930EE00422731 /* PresetHelper.swift in Sources */,
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C4F520672AF03791006787F2 /* ExtensionEnumeratorTest.swift in Sources */,
C46FA98C2822F08F00D78807 /* PhpConfigurationFileTest.swift in Sources */,
C4D5576529C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
@@ -3531,7 +3564,6 @@
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */,
032DAC2F2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
@@ -3546,12 +3578,14 @@
C4E2E85D28FC282B003B070C /* TestableConfiguration.swift in Sources */,
C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */,
C4BB393A2981AFC700F8E797 /* PhpVersionSource.swift in Sources */,
037F44252EDB92EC002EBF75 /* HomebrewWatchManager.swift in Sources */,
C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */,
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 */,
037F441E2EDB9195002EBF75 /* ConfigWatchManager.swift in Sources */,
C4E2E84828FC1D93003B070C /* TestableConfigurationTest.swift in Sources */,
C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */,
C4513F962B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */,
@@ -3588,7 +3622,6 @@
5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C4513F932B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */,
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */,
C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
@@ -3620,6 +3653,7 @@
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */,
03C29A792EC88E3100FBA25E /* ValetServicesDataManager.swift in Sources */,
C4EA3C482BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
0386B0BC2ED36DF800CA6795 /* LockedTests.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
036C39042E5C883B008DAEDF /* Packagist.swift in Sources */,
@@ -3628,21 +3662,21 @@
C485707828BF456300539B36 /* Warning.swift in Sources */,
033D459F2B0D513900070080 /* RemovePhpExtensionCommand.swift in Sources */,
C4513F8F2B13E2E5001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
037F44182EDB27BA002EBF75 /* Debouncer.swift in Sources */,
C415938027A1B54F00D2E1B7 /* ProjectTypeDetection.swift in Sources */,
C40F505628ECA64E004AD45B /* TestableConfigurations.swift in Sources */,
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */,
032C7A022EE43B7600758D98 /* Suspendable.swift in Sources */,
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */,
C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */,
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */,
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */,
03D846252EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */,
0329A9A42E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */,
033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */,
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
C490E3B829BCA367006D2DE6 /* App+BrewWatch.swift in Sources */,
C44AD3F72912EF7100997FF4 /* RealFileSystemTest.swift in Sources */,
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
C4C0E8E027F88AEB002D32A9 /* FakeDomainScanner.swift in Sources */,
@@ -3960,7 +3994,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1735;
CURRENT_PROJECT_VERSION = 1810;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
ENABLE_APP_SANDBOX = NO;
@@ -3979,7 +4013,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.11.1;
MARKETING_VERSION = 25.12;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4004,7 +4038,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1735;
CURRENT_PROJECT_VERSION = 1810;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
ENABLE_APP_SANDBOX = NO;
@@ -4023,7 +4057,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.11.1;
MARKETING_VERSION = 25.12;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4186,7 +4220,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1735;
CURRENT_PROJECT_VERSION = 1810;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
ENABLE_APP_SANDBOX = NO;
@@ -4205,7 +4239,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.11.1;
MARKETING_VERSION = 25.12;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@@ -4379,7 +4413,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1735;
CURRENT_PROJECT_VERSION = 1810;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
ENABLE_APP_SANDBOX = NO;
@@ -4398,7 +4432,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 25.11.1;
MARKETING_VERSION = 25.12;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@@ -4615,6 +4649,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
0317C17F2ED87CE1005479D2 /* XCRemoteSwiftPackageReference "NVAppUpdater" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicoverbruggen/NVAppUpdater";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
03D846292EB6418F006EFE3C /* XCRemoteSwiftPackageReference "plcrashreporter" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/microsoft/plcrashreporter.git";
@@ -4623,20 +4665,12 @@
minimumVersion = 1.12.0;
};
};
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 = {
kind = exactVersion;
version = 1.1.0;
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
@@ -4657,16 +4691,25 @@
package = 03D846292EB6418F006EFE3C /* XCRemoteSwiftPackageReference "plcrashreporter" */;
productName = CrashReporter;
};
0317C17D2ED87CAB005479D2 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
productName = NVAppUpdater;
};
0317C1802ED87CE1005479D2 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
package = 0317C17F2ED87CE1005479D2 /* XCRemoteSwiftPackageReference "NVAppUpdater" */;
productName = NVAppUpdater;
};
0317C1822ED87CEA005479D2 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
package = 0317C17F2ED87CE1005479D2 /* XCRemoteSwiftPackageReference "NVAppUpdater" */;
productName = NVAppUpdater;
};
03D8462A2EB6418F006EFE3C /* CrashReporter */ = {
isa = XCSwiftPackageProductDependency;
package = 03D846292EB6418F006EFE3C /* XCRemoteSwiftPackageReference "plcrashreporter" */;
productName = CrashReporter;
};
C47014FB2C46D31B0069AAE7 /* NVAppUpdater */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FA2C46D31B0069AAE7 /* XCRemoteSwiftPackageReference "NVAppUpdater" */;
productName = NVAppUpdater;
};
C47014FE2C46D57C0069AAE7 /* NVAlert */ = {
isa = XCSwiftPackageProductDependency;
package = C47014FD2C46D57C0069AAE7 /* XCRemoteSwiftPackageReference "NVAlert" */;

View File

@@ -29,8 +29,6 @@ PHP Monitor is a universal application that runs natively on Apple Silicon **and
* Homebrew `php` formula is installed
* Optional but recommended: Laravel Valet
_Starting with PHP Monitor 6.0, you do not need to have Laravel Valet installed for PHP Monitor to work. To get access to all features of PHP Monitor however, installing Valet is **recommended**._
For more information, please see [SECURITY.md](./SECURITY.md) to find out which version of the app is currently supported.
## 🚀 How to install
@@ -43,7 +41,7 @@ valet install
valet trust
```
Currently, PHP Monitor is compatible with Laravel Valet v2, v3 and v4. Each of these versions of Valet support slightly different PHP versions, which is why legacy versions remain supported. Please note that some features are not available in older versions of Valet, like site isolation.
Currently, PHP Monitor is compatible with Laravel Valet v2 up to v4. Each of these versions of Valet support slightly different PHP versions, which is why legacy versions remain supported. Please note that some features are not available in older versions of Valet, like site isolation.
#### Manual installation (recommended, first time only)
@@ -51,15 +49,15 @@ Once that's done, you can [download the latest release](https://github.com/nicov
#### Installation via Homebrew
*Prior to version 5.8, this was the recommended way of installing PHP Monitor.*
If you prefer to install the app via Homebrew, you can also run the following:
Alternatively, if you prefer to install the app via Homebrew, you can also run the following:
```sh
brew tap nicoverbruggen/homebrew-cask
brew install --cask phpmon
```
(You may want to disable the built-in update check if you install PHP Monitor this way.)
## ⬆️ How to update
The recommended method of updating the app to the latest version is to use **the built-in updater**.
@@ -88,11 +86,9 @@ Initially, I had an Alfred workflow for this — but it has now been replaced wi
_**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 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.
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 a great alternative to PHP Monitor, since it does not rely on Homebrew. The app also offers 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.
Herd may not be for everyone, which is why other solutions to run PHP locally exist. PHP Monitor preceded Herd and will remain supported. I _highly_ recommend that you try different tools, and use what you like best.
## 🤬 The app won't start?!
@@ -674,3 +670,5 @@ I have done my best to annotate as much as humanly possible, and have avoided us
I also have a few tests for key parts of the application that I found needed to be tested. In the future, I would like to add even more tests for some of the UI stuff, but for now the tests are more unit tests than feature tests.
For more detailed information for developers, please see [the documentation file for developers](./DEVELOPER.md).
You may also find the [privacy policy](https://phpmon.app/privacy-policy) useful to learn more about how PHP Monitor deals with your data. (Spoiler alert: PHP Monitor respects your privacy!)

View File

@@ -6,7 +6,7 @@ Generally speaking, only the latest version of **PHP Monitor** is supported, exc
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Recommended Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 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 |
| 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.6 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
## Legacy versions

View File

@@ -3,7 +3,7 @@
// PHP Monitor Self-Updater
//
// Created by Nico Verbruggen on 01/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 12/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// Command.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa
@@ -15,6 +15,8 @@ public class RealCommand: CommandProtocol {
withStandardError: Bool
) -> String {
let task = Process()
var output = ""
task.launchPath = path
task.arguments = arguments
@@ -26,10 +28,27 @@ public class RealCommand: CommandProtocol {
}
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
defer {
try? pipe.fileHandleForReading.close()
}
// Handle termination
if task.terminationReason == .uncaughtSignal {
Log.err("The command `\(path) w/ args: \(arguments)` likely crashed. Returning UNCAUGHT_SIGNAL.")
return "PHPMON_COMMAND_UNCAUGHT_SIGNAL"
}
// Try reading from file handle and close it
if let data = try? pipe.fileHandleForReading.readToEnd(),
let string = String(data: data, encoding: .utf8) {
output = string
} else {
return "PHPMON_FILE_HANDLE_READ_FAILURE"
}
// Trim newline output if necessary
if trimNewlines {
return output.components(separatedBy: .newlines)
.filter({ !$0.isEmpty })

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 12/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// Services.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// Constants.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
// MARK: Common Shell Commands

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/11/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// Paths.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 06/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 08/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 11/06/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 16/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// Date.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 01/11/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 14/04/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 18/08/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa
@@ -84,8 +84,8 @@ class ExtensionMenuItem: NSMenuItem {
var phpExtension: PhpExtension?
}
class EditorMenuItem: NSMenuItem {
var editor: Application?
class ApplicationMenuItem: NSMenuItem {
var app: Application?
}
class PresetMenuItem: NSMenuItem {

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 17/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 16/07/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -18,6 +18,6 @@ extension NVAlert {
return NVAlert().withInformation(
title: "\(key).title".localized,
subtitle: "\(key).description".localized
).withPrimary(text: "generic.ok".localized).show()
).withPrimary(text: "generic.ok".localized).show(urgency: .bringToFront)
}
}

View File

@@ -2,7 +2,7 @@
// StringExtension.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
import SwiftUI

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 29/09/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -14,6 +14,10 @@ extension TimeInterval {
static func minutes(_ value: Double) -> TimeInterval { value * 60 }
static func hours(_ value: Double) -> TimeInterval { value * 3600 }
static func days(_ value: Double) -> TimeInterval { value * 86400 }
var nanoseconds: UInt64 {
return UInt64(self * 1_000_000_000)
}
}
extension Date {

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 04/02/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 08/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 08/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 04/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -49,7 +49,7 @@ class TestableFileSystem: FileSystemProtocol {
/**
Serial dispatch queue for ensuring thread-safe access to the `files` dictionary.
*/
private let accessQueue = DispatchQueue(label: "com.testablefilesystem.accessQueue")
private let accessQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.fs_access")
// MARK: - Basics

View File

@@ -2,7 +2,7 @@
// Alert.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 07/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -14,7 +14,7 @@ import Foundation
class Application {
enum AppType {
case editor, browser, git_gui, terminal, user_supplied
case editor, ide, browser, git_gui, terminal, user_supplied
}
// MARK: - Container
@@ -29,24 +29,50 @@ class Application {
/// Application type. Depending on the type, a different action might occur.
let type: AppType
/// The full path to the application bundle (if found)
var path: String?
/// Initializer. Used to detect a specific app of a specific type.
init(_ container: Container, _ name: String, _ type: AppType) {
self.container = container
self.name = name
self.type = type
self.path = determinePath()
}
/**
Attempt to open a specific directory in the app of choice.
Attempt to open a specific string (path or URL) in the app of choice.
(This will open the app if it isn't open yet.)
*/
@objc public func openDirectory(file: String) {
Task { await container.shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") }
@objc public func open(arg: String) {
Task { await container.shell.quiet("/usr/bin/open -a \"\(name)\" \"\(arg)\"") }
}
/** Checks if the app is installed. */
func isInstalled() async -> Bool {
/**
Attempt to see if we can locate the app bundle in one of the two default locations:
- - First in `/Applications` (system-wide installed apps)
- - Second in `~/Applications` (user-specific installed apps)
If not in one of these default locations, the path will be `nil` and certain operations
will not be possible (i.e. determining icon via path to application).
*/
func determinePath() -> String? {
// Check global applications
if container.filesystem.directoryExists("/Applications/\(name).app") {
return "/Applications/\(name).app"
}
// Check user applications
if container.filesystem.directoryExists("~/Applications/\(name).app") {
return "~/Applications/\(name).app".replacingTildeWithHomeDirectory
}
return nil
}
/** Checks if the app is installed and stores its path. */
func isInstalled() async -> Bool {
// Then verify it's actually installed using the shell command
let (process, output) = try! await container.shell.attach(
"/usr/bin/open -Ra \"\(name)\"",
didReceiveOutput: { _, _ in },
@@ -71,11 +97,32 @@ class Application {
var detected: [Application] = []
let detectable = [
Application(container, "PhpStorm", .editor),
// Browsers (for future Open In > Browser context menu)
/*
Application(container, "Safari", .browser),
Application(container, "Google Chrome", .browser),
Application(container, "Microsoft Edge", .browser),
Application(container, "Firefox", .browser),
Application(container, "Brave", .browser),
Application(container, "Arc", .browser),
Application(container, "Zen", .browser),
*/
// Editors
Application(container, "PhpStorm", .ide),
Application(container, "WebStorm", .ide),
Application(container, "Visual Studio Code", .editor),
Application(container, "VSCodium", .editor),
Application(container, "Sublime Text", .editor),
// Git
Application(container, "Sublime Merge", .git_gui),
Application(container, "iTerm", .terminal)
Application(container, "Tower", .git_gui),
Application(container, "SourceTree", .git_gui),
// Terminals
Application(container, "iTerm", .terminal),
Application(container, "Ghostty", .terminal)
]
for app in detectable where await app.isInstalled() {

View File

@@ -2,7 +2,7 @@
// LocalNotification.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -0,0 +1,57 @@
//
// Locked.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/11/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
A thread-safe wrapper for a value that can be accessed from multiple threads.
Uses `NSLock` internally to ensure only one thread can read or write at a time,
preventing race conditions where two threads might try to access or modify
the value simultaneously.
## Usage
```swift
private let _counter = Locked<Int>(0)
var counter: Int {
get { _counter.value }
set { _counter.value = newValue }
}
```
Without locking, if Thread A reads a value while Thread B is writing to it,
Thread A might see a partially-written or inconsistent state, leading to crashes
or corrupted data. The lock ensures operations happen one at a time.
Use with care. Using structured concurrency w/ `actor` or delegating to
`MainActor` is generally preferred, but this approach may be necessary in
situations where adopting structured concurrency would otherwise be
too challenging or a huge refactor.
*/
final class Locked<T>: @unchecked Sendable {
private var _value: T
private let lock = NSLock()
init(_ value: T) {
self._value = value
}
var value: T {
get {
lock.lock()
defer { lock.unlock() }
return _value
}
set {
lock.lock()
defer { lock.unlock() }
_value = newValue
}
}
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 15/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import AppKit

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 02/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// ImageGenerator.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 01/11/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 16/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -82,7 +82,11 @@ class RealWebApi: WebApiProtocol {
var defaultHeaders: HttpHeaders {
return [
"User-Agent": "phpmon-nur/2.0",
// Fun fact: NUR stands for "NSURLSession Update Requester"
"User-Agent": "phpmon-nur/3.0",
// Optional randomized API session UUID
"X-phpmon-session-uuid": App.shared.getApiId(),
// Required fields
"X-phpmon-version": "\(App.shortVersion) (\(App.bundleVersion))",
"X-phpmon-os-version": "\(App.macVersion)",
"X-phpmon-bundle-id": "\(App.identifier)"

View File

@@ -2,7 +2,7 @@
// ActivePhpInstallation.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 01/05/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// HomebrewDecodable.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 11/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -106,14 +106,28 @@ class PhpEnvironments {
}
}
// MARK: - Thread-Safe PHP Version Storage
/** All versions of PHP that are currently supported. */
var availablePhpVersions: [String] = []
private let _availablePhpVersions = Locked<[String]>([])
var availablePhpVersions: [String] {
get { _availablePhpVersions.value }
set { _availablePhpVersions.value = newValue }
}
/** All versions of PHP that are currently installed but not compatible. */
var incompatiblePhpVersions: [String] = []
private let _incompatiblePhpVersions = Locked<[String]>([])
var incompatiblePhpVersions: [String] {
get { _incompatiblePhpVersions.value }
set { _incompatiblePhpVersions.value = newValue }
}
/** Cached information about the PHP installations. */
var cachedPhpInstallations: [String: PhpInstallation] = [:]
private let _cachedPhpInstallations = Locked<[String: PhpInstallation]>([:])
var cachedPhpInstallations: [String: PhpInstallation] {
get { _cachedPhpInstallations.value }
set { _cachedPhpInstallations.value = newValue }
}
/** Information about the currently linked PHP installation. */
var currentInstall: ActivePhpInstallation? {

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 17/03/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 06/01/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 04/05/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -83,7 +83,7 @@ class PhpConfigurationFile: CreatedFromFile {
Replaces the value for a specific (existing) key with a new value.
The key must exist for this to work.
*/
public func replace(key: String, value: String) throws {
public func replace(key: String, value: String) async throws {
// Ensure that the key exists
guard let item = getConfig(for: key) else {
throw ReplacementErrors.missingKey
@@ -102,14 +102,11 @@ class PhpConfigurationFile: CreatedFromFile {
self.lines[item.lineIndex] = components.joined(separator: "=")
// Ensure the watchers aren't tripped up by config changes
ConfigWatchManager.ignoresModificationsToConfigValues = true
// Finally, join the string and save the file atomatically again
try self.lines.joined(separator: "\n")
.write(toFile: self.filePath, atomically: true, encoding: .utf8)
// Ensure watcher behaviour is reverted
ConfigWatchManager.ignoresModificationsToConfigValues = false
try await ConfigWatchManager.withSuspended {
// Finally, join the string and save the file atomically again
try self.lines.joined(separator: "\n")
.write(toFile: self.filePath, atomically: true, encoding: .utf8)
}
// Reload the original file
self.reload()

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 31/01/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -94,11 +94,24 @@ class PhpInstallation {
withStandardError: true
).trimmingCharacters(in: .whitespacesAndNewlines)
// The PHP executable did not return any output
if testCommand.isEmpty
|| testCommand.contains("HANDLE_READ_FAILURE") {
Log.err("No output. PHP \(self.versionNumber.short) is not healthy!")
self.isHealthy = false
}
// The PHP executable crashed with an uncaught signal when we tried to run this
if testCommand.contains("UNCAUGHT_SIGNAL") {
Log.err("Uncaught signal, PHP \(self.versionNumber.short) is not healthy!")
self.isHealthy = false
}
// If the "dyld: Library not loaded" issue pops up, we have an unhealthy PHP installation
// and we will need to reinstall this version of PHP via Homebrew.
if testCommand.contains("Library not loaded") && testCommand.contains("dyld") {
Log.err("dyld error, PHP \(self.versionNumber.short) is not healthy!")
self.isHealthy = false
Log.err("The PHP installation of \(self.versionNumber.short) is not healthy!")
}
}
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 14/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 24/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 15/05/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -0,0 +1,58 @@
//
// Suspendable.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 06/12/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
/**
A protocol for actors that manage filesystem watchers and can temporarily
suspend their responses to changes.
This is useful when the application itself makes changes to watched files,
preventing duplicate work or unwanted side effects.
*/
protocol Suspendable: Actor {
/**
Suspends responding to filesystem events.
Events are still observed but handlers won't fire.
*/
func suspend() async
/**
Resumes responding to filesystem events.
Handlers will fire normally for observed events.
*/
func resume() async
/**
Executes an action while suspended, ensuring resume happens
even if the action throws.
- Parameter action: The async throwing closure to execute while suspended
- Returns: The result of the action
- Throws: Rethrows any error from the action
*/
func withSuspended<T>(_ action: () async throws -> T) async rethrows -> T
}
extension Suspendable {
/**
Default implementation of withSuspended that ensures proper
suspend/resume lifecycle even when errors occur.
*/
func withSuspended<T>(_ action: () async throws -> T) async rethrows -> T {
await suspend()
do {
let result = try await action()
await resume()
return result
} catch {
await resume()
throw error
}
}
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/09/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -80,6 +80,23 @@ class RealShell: ShellProtocol {
return task
}
/**
Reads the entire output of a `Pipe` and returns it as a UTF8 string.
Closes the pipe's file handler when done.
*/
private static func getStringOutput(from pipe: Pipe) -> String {
// 1. Read all data (safely).
let rawData = (try? pipe.fileHandleForReading.readToEnd()) ?? Data()
// 2. Convert to string (safely).
let result = String(data: rawData, encoding: .utf8) ?? ""
// 3. Close the handle quietly.
try? pipe.fileHandleForReading.close()
return result
}
// MARK: - Public API
/**
@@ -114,11 +131,8 @@ class RealShell: ShellProtocol {
return .out("", "")
}
let stdOut = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
try? outputPipe.fileHandleForReading.close()
try? errorPipe.fileHandleForReading.close()
let stdOut = RealShell.getStringOutput(from: outputPipe)
let stdErr = RealShell.getStringOutput(from: errorPipe)
if Log.shared.verbosity == .cli {
log(process: process, stdOut: stdOut, stdErr: stdErr)
@@ -145,20 +159,17 @@ class RealShell: ShellProtocol {
process.terminationHandler = { [weak self] _ in
if process.terminationReason == .uncaughtSignal {
Log.err("The command `\(command)` likely crashed. Returning empty output.")
continuation.resume(returning: .out("", ""))
return continuation.resume(returning: .out("", ""))
}
let stdOut = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
try? outputPipe.fileHandleForReading.close()
try? errorPipe.fileHandleForReading.close()
let stdOut = RealShell.getStringOutput(from: outputPipe)
let stdErr = RealShell.getStringOutput(from: errorPipe)
if Log.shared.verbosity == .cli {
self?.log(process: process, stdOut: stdOut, stdErr: stdErr)
}
continuation.resume(returning: .out(stdOut, stdErr))
return continuation.resume(returning: .out(stdOut, stdErr))
}
process.launch()
@@ -208,6 +219,7 @@ class RealShell: ShellProtocol {
process.standardError = errorPipe
let output = ShellOutput.empty()
let serialQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.shell_output")
return try await withCheckedThrowingContinuation({ continuation in
let timeoutTask = Task {
@@ -224,8 +236,10 @@ class RealShell: ShellProtocol {
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if !data.isEmpty, let string = String(data: data, encoding: .utf8) {
output.out += string
didReceiveOutput(string, .stdOut)
serialQueue.async {
output.out += string
didReceiveOutput(string, .stdOut)
}
}
}
@@ -233,8 +247,10 @@ class RealShell: ShellProtocol {
errorPipe.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if !data.isEmpty, let string = String(data: data, encoding: .utf8) {
output.err += string
didReceiveOutput(string, .stdErr)
serialQueue.async {
output.err += string
didReceiveOutput(string, .stdErr)
}
}
}
@@ -249,20 +265,18 @@ class RealShell: ShellProtocol {
let remainingOut = outputPipe.fileHandleForReading.readDataToEndOfFile()
let remainingErr = errorPipe.fileHandleForReading.readDataToEndOfFile()
if !remainingOut.isEmpty, let string = String(data: remainingOut, encoding: .utf8) {
output.out += string
didReceiveOutput(string, .stdOut)
}
serialQueue.async {
if !remainingOut.isEmpty, let string = String(data: remainingOut, encoding: .utf8) {
output.out += string
didReceiveOutput(string, .stdOut)
}
if !remainingErr.isEmpty, let string = String(data: remainingErr, encoding: .utf8) {
output.err += string
didReceiveOutput(string, .stdErr)
}
if !remainingErr.isEmpty, let string = String(data: remainingErr, encoding: .utf8) {
output.err += string
didReceiveOutput(string, .stdErr)
}
if !output.err.isEmpty {
continuation.resume(returning: (process, .err(output.err)))
} else {
continuation.resume(returning: (process, .out(output.out)))
continuation.resume(returning: (process, output))
}
}
@@ -275,9 +289,3 @@ class RealShell: ShellProtocol {
self.PATH = RealShell.getPath()
}
}
extension TimeInterval {
var nanoseconds: UInt64 {
return UInt64(self * 1_000_000_000)
}
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/09/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -67,7 +67,7 @@ enum ShellStream: Codable {
case stdOut, stdErr, stdIn
}
class ShellOutput {
class ShellOutput: @unchecked Sendable {
var out: String
var err: String

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 21/09/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 02/05/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 16/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -55,7 +55,6 @@ public struct TestableConfiguration: Codable {
private var primaryPhpVersion: VersionNumber?
private var secondaryPhpVersions: [VersionNumber] = []
// swiftlint:disable function_body_length
mutating func addPhpVersion(_ version: VersionNumber, primary: Bool) {
if primary {
if primaryPhpVersion != nil {
@@ -125,7 +124,6 @@ public struct TestableConfiguration: Codable {
)
}
}
// swiftlint:enable function_body_length
// MARK: Interactions

View File

@@ -7,9 +7,9 @@
//
extension Container {
public static func real() -> Container {
public static func real(minimal: Bool = false) -> Container {
let container = Container()
container.bind()
container.bind(coreOnly: minimal)
return container
}
}

View File

@@ -51,11 +51,18 @@ class Container {
/// (Swapping instances for specific dependencies can be introduced later with dedicated
/// methods if it ever becomes truly necessary.)
///
public func bind() {
/// - Parameter coreOnly: Only binds `shell`, `filesystem`, `command`, `paths` and `webApi`.
/// Use this to prevent slowing down tests for a minimal container.
///
public func bind(coreOnly: Bool = false) {
if self.bound {
fatalError("You cannot call `bind` on a Container more than once.")
}
defer {
self.bound = true
}
// These are the most basic building blocks. We need these before
// any of the other classes can be initialized!
self.shell = RealShell(container: self)
@@ -64,6 +71,10 @@ class Container {
self.paths = Paths(container: self)
self.webApi = RealWebApi(container: self)
if coreOnly {
return
}
// Please note that the order in which these are initialized, matters!
// For example, preferences leverages the Paths instance, so don't just
// swap these around for no reason... the order is very intentional.
@@ -71,9 +82,6 @@ class Container {
self.phpEnvs = PhpEnvironments(container: self)
self.favorites = Favorites()
self.warningManager = WarningManager(container: self)
// At this point, our container has been bound.
self.bound = true
}
/**

View File

@@ -14,11 +14,141 @@
<body>
<br>
<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>Privacy concerns?</b> Don't worry! Get informed by visiting the <a href="https://phpmon.app/privacy-policy">privacy policy</a>.</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 social media.</b> Follow me on <a href="https://x.com/nicoverbruggen">Twitter (X)</a>, <a href="https://bsky.app/profile/nicoverbruggen.be"> 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, @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>Made possible by these GitHub Sponsors</b>:
@abdusfauzi,
@abicons,
@ace-of-aces,
@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,
@ianlandsman,
@incon,
@intrepidws,
@israaraujo,
@jacksleight,
@JacobBennett,
@jasonvarga,
@jeromegamez,
@jimmyaldape,
@jimmysawczuk,
@joetannenbaum,
@jolora,
@jorisnoo,
@joshuablum,
@jpeinelt,
@jreviews,
@JustSteveKing,
@Kajvdh,
@KFoobar,
@kholisabdullah,
@Laravel-Backpack,
@leganz,
@lucianvacaroiu,
@marianoviola,
@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,
@rastitkac,
@rderimay,
@renecum,
@richardhulbert,
@richardtape,
@rickyjohnston,
@rico,
@RobertBoes,
@runofthemill,
@SahinU88,
@sdebacker,
@sdevore,
@shadracnicholas,
@simonhamp,
@slaFFik,
@spatie,
@SRWieZ,
@stefanbauer,
@stefanzweifel,
@StriveMedia,
@studentiyot,
@swilla,
@Tailcode-Studio,
@theutz,
@ThomasEnssner,
@tillkruss,
@timothyrowan,
@ttnppedr,
@victorsyin,
@vincent-tarrit,
@vintagesucks,
@WheresMarco,
@xPand4B,
@xuandung38,
@yeslandi89,
@zackkatz,
@zacksmash,
@zaherg.
<br/>
<br/>
(This is a historical list of sponsors, these are not all current sponsors. Some names have been omitted due to private sponsorships. Thank you, everyone!)</p>
<p><b>Localization credits:</b></br>
&dash; English, Dutch</b> by @nicoverbruggen</br>
&dash; Vietnamese</b> by @xuandung38</br>
@@ -29,6 +159,6 @@
</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>
</html>

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -0,0 +1,32 @@
//
// App+DetectApps.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 26/11/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
extension App {
/**
Detect which applications are installed that can be used to open a domain's source directory.
*/
public func detectApplications() async {
Log.info("Detecting applications...")
// Start by detecting the default applications
var detected = await Application.detectPresetApplications(container)
// Next up, scan for additional apps
let customApps = Preferences.custom.scanApps?.map { appName in
return Application(container, appName, .user_supplied)
} ?? []
// Append any detected apps
for app in customApps where await app.isInstalled() {
detected.append(app)
}
App.shared.detectedApplications = detected
Log.info("Detected applications: \(detected.map { $0.name })")
}
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -0,0 +1,74 @@
//
// App+UUID.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/12/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
struct Api {
/**
How long a API ID (uuid) is valid.
Currently set to this length to accomodate potential failures
for the update check.
*/
static let uuidValidityDuration: TimeInterval = .hours(36)
static let uuidKey = "api.uuid"
static let uuidTimestampKey = "api.uuid.timestamp"
}
extension App {
/**
Returns a unique UUID that automatically refreshes every 36 hours.
It is used to more accurately throttle API requests for a given IP,
since multiple users could be coming from one residential/business IP.
- Important: This UUID is NOT used for tracking.
It is used for legitimate purposes only.
Read more below to find out how this UUID is used.
How it is used:
- Allows identifying which IP addresses might be throttled too quickly
(example: many requests with the same IP, but different unique UUIDs)
- Allows counting how many unique users checked for updates in 24 hours
(example: previous assumption was: 1 IP = 1 user; not always true!)
The UUID is stored in UserDefaults and regenerated when it has expired.
Because I only use this for user counting, the ID is reset after 36 hours.
*/
func getApiId() -> String {
let defaults = UserDefaults.standard
// Check if we have a stored UUID and timestamp
if let storedUUID = defaults.string(forKey: Api.uuidKey),
let storedTimestamp = defaults.object(forKey: Api.uuidTimestampKey) as? Date {
// Check if the UUID is still valid (less than X hours old)
if Date().timeIntervalSince(storedTimestamp) < Api.uuidValidityDuration {
return storedUUID
}
}
// Generate a new UUID if we don't have one or it's expired
return regenerate()
}
/**
Regenerates a UUID for a given duration.
*/
private func regenerate() -> String {
let newUUID = UUID().uuidString
let defaults = UserDefaults.standard
defaults.set(newUUID, forKey: Api.uuidKey)
defaults.set(Date(), forKey: Api.uuidTimestampKey)
return newUUID
}
}

View File

@@ -2,7 +2,7 @@
// StateManager.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa
@@ -134,14 +134,18 @@ class App {
*/
var openWindows: [String] = []
// MARK: - App Watchers
// MARK: - FS Watchers
/** Individual filesystem watchers, which are, i.e. responsible for watching the Homebrew folders. */
var watchers: [String: FSNotifier] = [:]
/**
/**
The `ConfigWatchManager` is responsible for watching the `.ini` files and the `.conf.d` folder.
This manager object can immediately start or stop all watchers (or pause them) all at once.
*/
var watchManager: ConfigWatchManager!
var configWatchManager: ConfigWatchManager?
/**
The `HomebrewWatchManager` is responsible for watching the Homebrew binaries folder.
This allows PHP Monitor to respond to external `brew` changes executed by the user.
*/
var homebrewWatchManager: HomebrewWatchManager?
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 20/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 06/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -2,7 +2,7 @@
// AppDelegate.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa
@@ -53,6 +53,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
#if DEBUG
logger.verbosity = .performance
Log.info("Extra verbose mode is enabled by default on DEBUG builds.")
if let profile = CommandLine.arguments.first(where: { $0.matches(pattern: "--configuration:*") }) {
AppDelegate.initializeTestingProfile(profile.replacing("--configuration:", with: ""))
}
@@ -113,8 +115,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
// Make sure notifications will work
setupNotifications()
// Start with the regular busy icon
MainMenu.shared.setStatusBar(image: NSImage.statusBarIcon)
Task { // Make sure the menu performs its initial checks
await MainMenu.shared.startup()
await Startup.check(App.shared.container)
}
}

View File

@@ -3,140 +3,145 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 04/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
import NVAlert
/**
The potential different outcomes of a check for updates.
*/
enum UpdateCheckResult {
case success
case networkError
case parseError
}
/**
Instead of using `UpdateCheck` which is a more simplified update checking process
included in `NVAppUpdater`, we have a slightly more complex setup here.
*/
class AppUpdater {
var caskFile: CaskFile!
var latestVersionOnline: AppVersion!
var interactive: Bool = false
public func checkForUpdates(userInitiated: Bool) async -> UpdateCheckResult {
// If user initiated, we always expect to see an alert
self.interactive = userInitiated
// Log that we're looking for updates
Log.info("The app will search for updates...")
let caskUrl = Constants.Urls.UpdateCheckEndpoint
guard let caskFile = try? await CaskFile.fromUrl(App.shared.container, caskUrl) else {
presentCouldNotRetrieveUpdateIfInteractive()
// Attempt to get the latest CaskFile from the API
guard let caskFile = try? await CaskFile.fromUrl(
App.shared.container,
Constants.Urls.UpdateCheckEndpoint
) else {
// ERROR #1: The endpoint is unreachable or the response is invalid.
Log.err("Could not get a valid CaskFile from the endpoint.")
if interactive {
await presentCouldNotRetrieveUpdate()
}
return .networkError
}
// We will now persist the CaskFile so we can reference it later
self.caskFile = caskFile
let currentVersion = AppVersion.fromCurrentVersion()
// Let's parse the latest online version if we can
guard let onlineVersion = AppVersion.from(caskFile.version) else {
// ERROR #2: The CaskFile's version string is invalid.
Log.err("The version string from the CaskFile could not be read.")
presentCouldNotRetrieveUpdateIfInteractive()
if interactive {
await presentCouldNotRetrieveUpdate()
}
return .parseError
}
// We will now persist the version number so we can reference it later
latestVersionOnline = onlineVersion
Log.info("The latest version read from '\(caskUrl.lastPathComponent)' is: v\(onlineVersion.computerReadable).")
Log.info("The latest version read from the endpoint is: v\(onlineVersion.computerReadable).")
if latestVersionOnline > currentVersion {
presentNewerVersionAvailableAlert()
} else if interactive {
presentNoNewerVersionAvailableAlert()
Task { // Present this concurrently w/ returning the .success value
if latestVersionOnline > AppVersion.fromCurrentVersion() {
await presentNewerVersionAvailableAlert()
} else if interactive {
await presentNoNewerVersionAvailableAlert()
}
}
return .success
}
private func presentCouldNotRetrieveUpdateIfInteractive() {
if interactive {
return presentCouldNotRetrieveUpdate()
} else {
return
}
}
// MARK: - Alerts
public func presentNewerVersionAvailableAlert() {
let command = "brew upgrade phpmon"
Task { @MainActor in
NVAlert().withInformation(
title: "updater.alerts.newer_version_available.title"
.localized(latestVersionOnline.humanReadable),
subtitle: "updater.alerts.newer_version_available.subtitle"
.localized,
description: BrewDiagnostics.shared.customCaskInstalled
? "updater.installation_source.brew".localized(command)
: "updater.installation_source.direct".localized
)
.withPrimary(
text: "updater.alerts.buttons.install".localized,
action: { vc in
self.cleanupCaskroom()
self.prepareForDownload()
vc.close(with: .OK)
}
)
.withSecondary(
text: "updater.alerts.buttons.release_notes".localized,
action: { _ in
NSWorkspace.shared.open({
if App.identifier.contains(".eap") {
return Constants.Urls.EarlyAccessChangelog
} else {
let urlSegments = self.caskFile.url.split(separator: "/")
let tag = urlSegments[urlSegments.count - 2] // ../download/{tag}/{file.zip}
return Constants.Urls.GitHubReleases.appendingPathComponent("/tag/\(tag)")
}
}())
}
)
.withTertiary(text: "updater.alerts.buttons.dismiss".localized, action: { vc in
@MainActor public func presentNewerVersionAvailableAlert() {
NVAlert().withInformation(
title: "updater.alerts.newer_version_available.title"
.localized(latestVersionOnline.humanReadable),
subtitle: "updater.alerts.newer_version_available.subtitle"
.localized,
description: BrewDiagnostics.shared.customCaskInstalled
? "updater.installation_source.brew".localized("brew upgrade phpmon")
: "updater.installation_source.direct".localized
)
.withPrimary(
text: "updater.alerts.buttons.install".localized,
action: { vc in
self.cleanupCaskroom()
self.prepareForDownload()
vc.close(with: .OK)
})
.show()
}
}
)
.withSecondary(
text: "updater.alerts.buttons.release_notes".localized,
action: { _ in
NSWorkspace.shared.open({
if App.identifier.contains(".eap") {
return Constants.Urls.EarlyAccessChangelog
} else {
let urlSegments = self.caskFile.url.split(separator: "/")
let tag = urlSegments[urlSegments.count - 2] // ../download/{tag}/{file.zip}
return Constants.Urls.GitHubReleases.appendingPathComponent("/tag/\(tag)")
}
}())
}
)
.withTertiary(text: "updater.alerts.buttons.dismiss".localized, action: { vc in
vc.close(with: .OK)
})
.show(urgency: interactive ? .bringToFront : .urgentRequestAttention)
}
public func presentNoNewerVersionAvailableAlert() {
Task { @MainActor in
NVAlert().withInformation(
title: "updater.alerts.is_latest_version.title".localized,
subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion),
description: ""
)
.withPrimary(text: "generic.ok".localized)
.show()
}
@MainActor public func presentNoNewerVersionAvailableAlert() {
NVAlert().withInformation(
title: "updater.alerts.is_latest_version.title".localized,
subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion),
description: ""
)
.withPrimary(text: "generic.ok".localized)
.show(urgency: .bringToFront)
}
public func presentCouldNotRetrieveUpdate() {
Task { @MainActor in
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(
App.version
)
@MainActor public func presentCouldNotRetrieveUpdate() {
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(
App.version
)
.withTertiary(
text: "updater.alerts.buttons.releases_on_github".localized,
action: { _ in
NSWorkspace.shared.open(Constants.Urls.GitHubReleases)
}
)
.withPrimary(text: "generic.ok".localized)
.show()
}
)
.withTertiary(
text: "updater.alerts.buttons.releases_on_github".localized,
action: { _ in
NSWorkspace.shared.open(Constants.Urls.GitHubReleases)
}
)
.withPrimary(text: "generic.ok".localized)
.show(urgency: .bringToFront)
}
// MARK: - Preparing for Self-Updater

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 10/05/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -68,7 +68,7 @@ class CrashReporter {
})
.withPrimary(text: "crash_reporter.send_report".localized, action: { alert in
alert.close(with: .OK)
}).runModal()
}).runModal(urgency: .urgentRequestAttention)
// Check the outcome of what the user chose
if response == .abort {
@@ -100,6 +100,7 @@ class CrashReporter {
request.httpMethod = "POST"
request.setValue("text/crash", forHTTPHeaderField: "Content-Type")
request.setValue("phpmon-crashrep/1.0", forHTTPHeaderField: "User-Agent")
request.setValue(App.shared.getApiId(), forHTTPHeaderField: "X-phpmon-session-uuid")
request.httpBody = text.data(using: .utf8)
request.timeoutInterval = timeout

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 10/08/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 28/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/12/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/12/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 11/06/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/12/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -91,7 +91,7 @@ class ValetServicesManager: ServicesManager {
description: "alert.service_error.extra".localized
)
.withPrimary(text: "alert.service_error.button.close".localized)
.show()
.show(urgency: .bringToFront)
}
// If we do have a path to a log file, show a more complex alert w/ Show Log button
@@ -112,6 +112,6 @@ class ValetServicesManager: ServicesManager {
alert.close(with: .OK)
})
.show()
.show(urgency: .bringToFront)
}
}

View File

@@ -1,25 +1,32 @@
//
// MainMenu+Startup.swift
// Startup+Launch.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 03/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Created by Nico Verbruggen on 26/11/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
import NVAlert
extension MainMenu {
extension Startup {
/**
Kick off the startup of the rendering of the main menu.
*/
func startup() async {
// Start with the icon
await MainActor.run {
self.setStatusBar(image: NSImage.statusBarIcon)
}
static func check(_ container: Container) async {
// Create a new instance of Startup w/ the container
let startup = Startup(container)
if await Startup().checkEnvironment() {
// Perform the startup checks
await startup.check()
}
/**
Perform all checks and execute pass or fail results.
*/
private func check() async {
if await self.checkEnvironment() {
await self.onEnvironmentPass()
} else {
await self.onEnvironmentFail()
@@ -29,6 +36,7 @@ extension MainMenu {
/**
When the environment is all clear and the app can run, let's go.
*/
@MainActor
private func onEnvironmentPass() async {
// Load additional preferences
await container.preferences.loadCustomPreferences()
@@ -60,16 +68,16 @@ extension MainMenu {
await container.phpEnvs.reloadPhpVersions()
// Set up the filesystem watcher for the Homebrew binaries
App.shared.prepareHomebrewWatchers()
await HomebrewWatchManager.prepare()
// Check for other problems
container.warningManager.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching)
App.shared.handlePhpConfigWatcher()
await ConfigWatchManager.handleWatcher()
// Detect built-in and custom applications
await detectApplications()
await App.shared.detectApplications()
// Load the rollback preset
PresetHelper.loadRollbackPresetFromFile()
@@ -150,52 +158,21 @@ extension MainMenu {
/**
When the environment is not OK, present an alert to inform the user.
*/
@MainActor
private func onEnvironmentFail() async {
Task { @MainActor [self] in
NVAlert()
.withInformation(
title: "alert.cannot_start.title".localized,
subtitle: "alert.cannot_start.subtitle".localized,
description: "alert.cannot_start.description".localized
)
.withPrimary(text: "alert.cannot_start.retry".localized)
.withSecondary(text: "alert.cannot_start.close".localized, action: { vc in
vc.close(with: .alertSecondButtonReturn)
exit(1)
})
.show()
NVAlert()
.withInformation(
title: "alert.cannot_start.title".localized,
subtitle: "alert.cannot_start.subtitle".localized,
description: "alert.cannot_start.description".localized
)
.withPrimary(text: "alert.cannot_start.retry".localized)
.withSecondary(text: "alert.cannot_start.close".localized, action: { vc in
vc.close(with: .alertSecondButtonReturn)
exit(1)
})
.show(urgency: .bringToFront)
Task { // An issue occurred, fire startup checks again after dismissal
await startup()
}
}
}
/**
Detect which applications are installed that can be used to open a domain's source directory.
*/
private func detectApplications() async {
Log.info("Detecting applications...")
App.shared.detectedApplications = await Application.detectPresetApplications(container)
let customApps = Preferences.custom.scanApps?.map { appName in
return Application(container, appName, .user_supplied)
} ?? []
var detectedCustomApps: [Application] = []
for app in customApps where await app.isInstalled() {
detectedCustomApps.append(app)
}
App.shared.detectedApplications
.append(contentsOf: detectedCustomApps)
let appNames = App.shared.detectedApplications.map { app in
return app.name
}
Log.info("Detected applications: \(appNames)")
await self.check()
}
}

View File

@@ -69,6 +69,6 @@ extension Startup {
.withTertiary(text: "", action: { _ in
NSWorkspace.shared.open(URL(string: "https://github.com/nicoverbruggen/phpmon/issues/294")!)
})
.show()
.show(urgency: .urgentRequestAttention)
}
}

View File

@@ -2,7 +2,7 @@
// Environment.swift
// PHP Monitor
//
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -10,6 +10,12 @@ import AppKit
import NVAlert
class Startup {
var container: Container
init(_ container: Container) {
self.container = container
}
/**
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.
@@ -68,7 +74,7 @@ class Startup {
)
.withPrimary(text: check.buttonText, action: { _ in
exit(1)
}).show()
}).show(urgency: .bringToFront)
}
NVAlert()
@@ -78,7 +84,7 @@ class Startup {
description: check.descriptionText
)
.withPrimary(text: "generic.ok".localized)
.show()
.show(urgency: .bringToFront)
}
// MARK: - Check (List)

View File

@@ -1,27 +0,0 @@
//
// LoggableEvent.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 12/09/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
// TODO: Add anonymous analytics system
// Batch events and dispatch them every hour.
// Reset the counts when send successfully.
// That's the plan. Currently not implemented!
// Also, there should be an opt-out.
enum LoggableEvent: String {
case menuOpened = "menu_opened"
case phpVersionSwitched = "php_version_switched"
case openedDomainManagement = "opened_domain_management"
case openedPhpInstallations = "opened_php_installations"
case openedPhpExtensions = "opened_php_extensions"
case openedSettings = "opened_settings"
// TODO: Add more tracked things.
}

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 24/01/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 04/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 08/02/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
@@ -77,7 +77,7 @@ import NVAlert
withTimeout: .minutes(5)
)
if process.terminationStatus <= 0 {
if process.terminationStatus == 0 {
composerUpdateSucceeded()
} else {
composerUpdateFailed()
@@ -137,7 +137,7 @@ import NVAlert
description: "alert.composer_missing.desc".localized
)
.withPrimary(text: "generic.ok".localized)
.show()
.show(urgency: .bringToFront)
}
deinit {

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 26/01/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 23/04/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 17/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 27/11/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// PHP Monitor
//
// Created by Nico Verbruggen on 17/03/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation

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