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 # 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. 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.
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 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.) 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. 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: - 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.
* No issue has been associated with the changes youd like to merge - Low effort changes may not be accepted.
* You have not announced you will be addressing a particular issue - When in doubt, open an issue or discussion and ask me if it's worth doing something.
* 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.
## About destination branches ## 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 ## 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) * 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) * 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) * 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) * References issue(s): (please reference the issue here, using # and the number of the issue)
(please describe what you have changed here) (please describe what you have changed here)

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
// Command.swift // Command.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa import Cocoa
@@ -15,6 +15,8 @@ public class RealCommand: CommandProtocol {
withStandardError: Bool withStandardError: Bool
) -> String { ) -> String {
let task = Process() let task = Process()
var output = ""
task.launchPath = path task.launchPath = path
task.arguments = arguments task.arguments = arguments
@@ -26,10 +28,27 @@ public class RealCommand: CommandProtocol {
} }
task.launch() task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile() defer {
let output: String = String.init(data: data, encoding: String.Encoding.utf8)! 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 { if trimNewlines {
return output.components(separatedBy: .newlines) return output.components(separatedBy: .newlines)
.filter({ !$0.isEmpty }) .filter({ !$0.isEmpty })

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 24/12/2021. // 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 // MARK: Common Shell Commands

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 16/07/2024. // Created by Nico Verbruggen on 16/07/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -18,6 +18,6 @@ extension NVAlert {
return NVAlert().withInformation( return NVAlert().withInformation(
title: "\(key).title".localized, title: "\(key).title".localized,
subtitle: "\(key).description".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 // StringExtension.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
import SwiftUI import SwiftUI

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 04/10/2022. // Created by Nico Verbruggen on 04/10/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -49,7 +49,7 @@ class TestableFileSystem: FileSystemProtocol {
/** /**
Serial dispatch queue for ensuring thread-safe access to the `files` dictionary. 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 // MARK: - Basics

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 07/12/2021. // Created by Nico Verbruggen on 07/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -14,7 +14,7 @@ import Foundation
class Application { class Application {
enum AppType { enum AppType {
case editor, browser, git_gui, terminal, user_supplied case editor, ide, browser, git_gui, terminal, user_supplied
} }
// MARK: - Container // MARK: - Container
@@ -29,24 +29,50 @@ class Application {
/// Application type. Depending on the type, a different action might occur. /// Application type. Depending on the type, a different action might occur.
let type: AppType 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. /// Initializer. Used to detect a specific app of a specific type.
init(_ container: Container, _ name: String, _ type: AppType) { init(_ container: Container, _ name: String, _ type: AppType) {
self.container = container self.container = container
self.name = name self.name = name
self.type = type 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.) (This will open the app if it isn't open yet.)
*/ */
@objc public func openDirectory(file: String) { @objc public func open(arg: String) {
Task { await container.shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") } 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( let (process, output) = try! await container.shell.attach(
"/usr/bin/open -Ra \"\(name)\"", "/usr/bin/open -Ra \"\(name)\"",
didReceiveOutput: { _, _ in }, didReceiveOutput: { _, _ in },
@@ -71,11 +97,32 @@ class Application {
var detected: [Application] = [] var detected: [Application] = []
let detectable = [ 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, "Visual Studio Code", .editor),
Application(container, "VSCodium", .editor),
Application(container, "Sublime Text", .editor), Application(container, "Sublime Text", .editor),
// Git
Application(container, "Sublime Merge", .git_gui), 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() { for app in detectable where await app.isInstalled() {

View File

@@ -2,7 +2,7 @@
// LocalNotification.swift // LocalNotification.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation 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 // PHP Monitor
// //
// Created by Nico Verbruggen on 15/02/2023. // Created by Nico Verbruggen on 15/02/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import AppKit import AppKit

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,11 @@ class RealWebApi: WebApiProtocol {
var defaultHeaders: HttpHeaders { var defaultHeaders: HttpHeaders {
return [ 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-version": "\(App.shortVersion) (\(App.bundleVersion))",
"X-phpmon-os-version": "\(App.macVersion)", "X-phpmon-os-version": "\(App.macVersion)",
"X-phpmon-bundle-id": "\(App.identifier)" "X-phpmon-bundle-id": "\(App.identifier)"

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 21/12/2021. // Created by Nico Verbruggen on 21/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -106,14 +106,28 @@ class PhpEnvironments {
} }
} }
// MARK: - Thread-Safe PHP Version Storage
/** All versions of PHP that are currently supported. */ /** 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. */ /** 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. */ /** 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. */ /** Information about the currently linked PHP installation. */
var currentInstall: ActivePhpInstallation? { var currentInstall: ActivePhpInstallation? {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 28/11/2021. // Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -94,11 +94,24 @@ class PhpInstallation {
withStandardError: true withStandardError: true
).trimmingCharacters(in: .whitespacesAndNewlines) ).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 // 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. // and we will need to reinstall this version of PHP via Homebrew.
if testCommand.contains("Library not loaded") && testCommand.contains("dyld") { if testCommand.contains("Library not loaded") && testCommand.contains("dyld") {
Log.err("dyld error, PHP \(self.versionNumber.short) is not healthy!")
self.isHealthy = false self.isHealthy = false
Log.err("The PHP installation of \(self.versionNumber.short) is not healthy!")
} }
} }
} }

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 15/05/2022. // Created by Nico Verbruggen on 15/05/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation 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 // PHP Monitor
// //
// Created by Nico Verbruggen on 21/09/2022. // Created by Nico Verbruggen on 21/09/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -80,6 +80,23 @@ class RealShell: ShellProtocol {
return task 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 // MARK: - Public API
/** /**
@@ -114,11 +131,8 @@ class RealShell: ShellProtocol {
return .out("", "") return .out("", "")
} }
let stdOut = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" let stdOut = RealShell.getStringOutput(from: outputPipe)
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" let stdErr = RealShell.getStringOutput(from: errorPipe)
try? outputPipe.fileHandleForReading.close()
try? errorPipe.fileHandleForReading.close()
if Log.shared.verbosity == .cli { if Log.shared.verbosity == .cli {
log(process: process, stdOut: stdOut, stdErr: stdErr) log(process: process, stdOut: stdOut, stdErr: stdErr)
@@ -145,20 +159,17 @@ class RealShell: ShellProtocol {
process.terminationHandler = { [weak self] _ in process.terminationHandler = { [weak self] _ in
if process.terminationReason == .uncaughtSignal { if process.terminationReason == .uncaughtSignal {
Log.err("The command `\(command)` likely crashed. Returning empty output.") 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 stdOut = RealShell.getStringOutput(from: outputPipe)
let stdErr = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" let stdErr = RealShell.getStringOutput(from: errorPipe)
try? outputPipe.fileHandleForReading.close()
try? errorPipe.fileHandleForReading.close()
if Log.shared.verbosity == .cli { if Log.shared.verbosity == .cli {
self?.log(process: process, stdOut: stdOut, stdErr: stdErr) self?.log(process: process, stdOut: stdOut, stdErr: stdErr)
} }
continuation.resume(returning: .out(stdOut, stdErr)) return continuation.resume(returning: .out(stdOut, stdErr))
} }
process.launch() process.launch()
@@ -208,6 +219,7 @@ class RealShell: ShellProtocol {
process.standardError = errorPipe process.standardError = errorPipe
let output = ShellOutput.empty() let output = ShellOutput.empty()
let serialQueue = DispatchQueue(label: "com.nicoverbruggen.phpmon.shell_output")
return try await withCheckedThrowingContinuation({ continuation in return try await withCheckedThrowingContinuation({ continuation in
let timeoutTask = Task { let timeoutTask = Task {
@@ -224,8 +236,10 @@ class RealShell: ShellProtocol {
outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputPipe.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData let data = fileHandle.availableData
if !data.isEmpty, let string = String(data: data, encoding: .utf8) { if !data.isEmpty, let string = String(data: data, encoding: .utf8) {
output.out += string serialQueue.async {
didReceiveOutput(string, .stdOut) output.out += string
didReceiveOutput(string, .stdOut)
}
} }
} }
@@ -233,8 +247,10 @@ class RealShell: ShellProtocol {
errorPipe.fileHandleForReading.readabilityHandler = { fileHandle in errorPipe.fileHandleForReading.readabilityHandler = { fileHandle in
let data = fileHandle.availableData let data = fileHandle.availableData
if !data.isEmpty, let string = String(data: data, encoding: .utf8) { if !data.isEmpty, let string = String(data: data, encoding: .utf8) {
output.err += string serialQueue.async {
didReceiveOutput(string, .stdErr) output.err += string
didReceiveOutput(string, .stdErr)
}
} }
} }
@@ -249,20 +265,18 @@ class RealShell: ShellProtocol {
let remainingOut = outputPipe.fileHandleForReading.readDataToEndOfFile() let remainingOut = outputPipe.fileHandleForReading.readDataToEndOfFile()
let remainingErr = errorPipe.fileHandleForReading.readDataToEndOfFile() let remainingErr = errorPipe.fileHandleForReading.readDataToEndOfFile()
if !remainingOut.isEmpty, let string = String(data: remainingOut, encoding: .utf8) { serialQueue.async {
output.out += string if !remainingOut.isEmpty, let string = String(data: remainingOut, encoding: .utf8) {
didReceiveOutput(string, .stdOut) output.out += string
} didReceiveOutput(string, .stdOut)
}
if !remainingErr.isEmpty, let string = String(data: remainingErr, encoding: .utf8) { if !remainingErr.isEmpty, let string = String(data: remainingErr, encoding: .utf8) {
output.err += string output.err += string
didReceiveOutput(string, .stdErr) didReceiveOutput(string, .stdErr)
} }
if !output.err.isEmpty { continuation.resume(returning: (process, output))
continuation.resume(returning: (process, .err(output.err)))
} else {
continuation.resume(returning: (process, .out(output.out)))
} }
} }
@@ -275,9 +289,3 @@ class RealShell: ShellProtocol {
self.PATH = RealShell.getPath() self.PATH = RealShell.getPath()
} }
} }
extension TimeInterval {
var nanoseconds: UInt64 {
return UInt64(self * 1_000_000_000)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,11 +14,141 @@
<body> <body>
<br> <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>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>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>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>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>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> <p><b>Localization credits:</b></br>
&dash; English, Dutch</b> by @nicoverbruggen</br> &dash; English, Dutch</b> by @nicoverbruggen</br>
&dash; Vietnamese</b> by @xuandung38</br> &dash; Vietnamese</b> by @xuandung38</br>
@@ -29,6 +159,6 @@
</br> </br>
Other languages are considered experimental, and were generated via a local LLM. If you have feedback or concerns, please don't hesitate to get in touch. 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> </p>
<br/>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 05/12/2021. // Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa 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 // PHP Monitor
// //
// Created by Nico Verbruggen on 05/12/2021. // Created by Nico Verbruggen on 05/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa 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 // StateManager.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa import Cocoa
@@ -134,14 +134,18 @@ class App {
*/ */
var openWindows: [String] = [] 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. 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. 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 // PHP Monitor
// //
// Created by Nico Verbruggen on 20/12/2021. // Created by Nico Verbruggen on 20/12/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa import Cocoa

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
// AppDelegate.swift // AppDelegate.swift
// PHP Monitor // PHP Monitor
// //
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Cocoa import Cocoa
@@ -53,6 +53,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
#if DEBUG #if DEBUG
logger.verbosity = .performance 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:*") }) { if let profile = CommandLine.arguments.first(where: { $0.matches(pattern: "--configuration:*") }) {
AppDelegate.initializeTestingProfile(profile.replacing("--configuration:", with: "")) AppDelegate.initializeTestingProfile(profile.replacing("--configuration:", with: ""))
} }
@@ -113,8 +115,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
// Make sure notifications will work // Make sure notifications will work
setupNotifications() setupNotifications()
// Start with the regular busy icon
MainMenu.shared.setStatusBar(image: NSImage.statusBarIcon)
Task { // Make sure the menu performs its initial checks 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 // PHP Monitor
// //
// Created by Nico Verbruggen on 04/02/2023. // 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 Foundation
import Cocoa import Cocoa
import NVAlert import NVAlert
/**
The potential different outcomes of a check for updates.
*/
enum UpdateCheckResult { enum UpdateCheckResult {
case success case success
case networkError case networkError
case parseError 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 { class AppUpdater {
var caskFile: CaskFile! var caskFile: CaskFile!
var latestVersionOnline: AppVersion! var latestVersionOnline: AppVersion!
var interactive: Bool = false var interactive: Bool = false
public func checkForUpdates(userInitiated: Bool) async -> UpdateCheckResult { public func checkForUpdates(userInitiated: Bool) async -> UpdateCheckResult {
// If user initiated, we always expect to see an alert
self.interactive = userInitiated self.interactive = userInitiated
// Log that we're looking for updates
Log.info("The app will search for updates...") Log.info("The app will search for updates...")
let caskUrl = Constants.Urls.UpdateCheckEndpoint // Attempt to get the latest CaskFile from the API
guard let caskFile = try? await CaskFile.fromUrl(
guard let caskFile = try? await CaskFile.fromUrl(App.shared.container, caskUrl) else { App.shared.container,
presentCouldNotRetrieveUpdateIfInteractive() 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 return .networkError
} }
// We will now persist the CaskFile so we can reference it later
self.caskFile = caskFile 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 { 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.") Log.err("The version string from the CaskFile could not be read.")
presentCouldNotRetrieveUpdateIfInteractive() if interactive {
await presentCouldNotRetrieveUpdate()
}
return .parseError return .parseError
} }
// We will now persist the version number so we can reference it later
latestVersionOnline = onlineVersion 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 { Task { // Present this concurrently w/ returning the .success value
presentNewerVersionAvailableAlert() if latestVersionOnline > AppVersion.fromCurrentVersion() {
} else if interactive { await presentNewerVersionAvailableAlert()
presentNoNewerVersionAvailableAlert() } else if interactive {
await presentNoNewerVersionAvailableAlert()
}
} }
return .success return .success
} }
private func presentCouldNotRetrieveUpdateIfInteractive() {
if interactive {
return presentCouldNotRetrieveUpdate()
} else {
return
}
}
// MARK: - Alerts // MARK: - Alerts
public func presentNewerVersionAvailableAlert() { @MainActor public func presentNewerVersionAvailableAlert() {
let command = "brew upgrade phpmon" NVAlert().withInformation(
title: "updater.alerts.newer_version_available.title"
Task { @MainActor in .localized(latestVersionOnline.humanReadable),
NVAlert().withInformation( subtitle: "updater.alerts.newer_version_available.subtitle"
title: "updater.alerts.newer_version_available.title" .localized,
.localized(latestVersionOnline.humanReadable), description: BrewDiagnostics.shared.customCaskInstalled
subtitle: "updater.alerts.newer_version_available.subtitle" ? "updater.installation_source.brew".localized("brew upgrade phpmon")
.localized, : "updater.installation_source.direct".localized
description: BrewDiagnostics.shared.customCaskInstalled )
? "updater.installation_source.brew".localized(command) .withPrimary(
: "updater.installation_source.direct".localized text: "updater.alerts.buttons.install".localized,
) action: { vc in
.withPrimary( self.cleanupCaskroom()
text: "updater.alerts.buttons.install".localized, self.prepareForDownload()
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
vc.close(with: .OK) 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() { @MainActor public func presentNoNewerVersionAvailableAlert() {
Task { @MainActor in NVAlert().withInformation(
NVAlert().withInformation( title: "updater.alerts.is_latest_version.title".localized,
title: "updater.alerts.is_latest_version.title".localized, subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion),
subtitle: "updater.alerts.is_latest_version.subtitle".localized(App.shortVersion), description: ""
description: "" )
) .withPrimary(text: "generic.ok".localized)
.withPrimary(text: "generic.ok".localized) .show(urgency: .bringToFront)
.show()
}
} }
public func presentCouldNotRetrieveUpdate() { @MainActor public func presentCouldNotRetrieveUpdate() {
Task { @MainActor in NVAlert().withInformation(
NVAlert().withInformation( title: "updater.alerts.cannot_check_for_update.title".localized,
title: "updater.alerts.cannot_check_for_update.title".localized, subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized,
subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized, description: "updater.alerts.cannot_check_for_update.description".localized(
description: "updater.alerts.cannot_check_for_update.description".localized( App.version
App.version
)
) )
.withTertiary( )
text: "updater.alerts.buttons.releases_on_github".localized, .withTertiary(
action: { _ in text: "updater.alerts.buttons.releases_on_github".localized,
NSWorkspace.shared.open(Constants.Urls.GitHubReleases) action: { _ in
} NSWorkspace.shared.open(Constants.Urls.GitHubReleases)
) }
.withPrimary(text: "generic.ok".localized) )
.show() .withPrimary(text: "generic.ok".localized)
} .show(urgency: .bringToFront)
} }
// MARK: - Preparing for Self-Updater // MARK: - Preparing for Self-Updater

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 23/12/2022. // Created by Nico Verbruggen on 23/12/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation import Foundation
@@ -91,7 +91,7 @@ class ValetServicesManager: ServicesManager {
description: "alert.service_error.extra".localized description: "alert.service_error.extra".localized
) )
.withPrimary(text: "alert.service_error.button.close".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 // 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) alert.close(with: .OK)
}) })
.show() .show(urgency: .bringToFront)
} }
} }

View File

@@ -1,25 +1,32 @@
// //
// MainMenu+Startup.swift // Startup+Launch.swift
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 03/01/2022. // Created by Nico Verbruggen on 26/11/2025.
// Copyright © 2023 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation
import Cocoa import Cocoa
import NVAlert import NVAlert
extension MainMenu { extension Startup {
/** /**
Kick off the startup of the rendering of the main menu. Kick off the startup of the rendering of the main menu.
*/ */
func startup() async { static func check(_ container: Container) async {
// Start with the icon // Create a new instance of Startup w/ the container
await MainActor.run { let startup = Startup(container)
self.setStatusBar(image: NSImage.statusBarIcon)
}
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() await self.onEnvironmentPass()
} else { } else {
await self.onEnvironmentFail() await self.onEnvironmentFail()
@@ -29,6 +36,7 @@ extension MainMenu {
/** /**
When the environment is all clear and the app can run, let's go. When the environment is all clear and the app can run, let's go.
*/ */
@MainActor
private func onEnvironmentPass() async { private func onEnvironmentPass() async {
// Load additional preferences // Load additional preferences
await container.preferences.loadCustomPreferences() await container.preferences.loadCustomPreferences()
@@ -60,16 +68,16 @@ extension MainMenu {
await container.phpEnvs.reloadPhpVersions() await container.phpEnvs.reloadPhpVersions()
// Set up the filesystem watcher for the Homebrew binaries // Set up the filesystem watcher for the Homebrew binaries
App.shared.prepareHomebrewWatchers() await HomebrewWatchManager.prepare()
// Check for other problems // Check for other problems
container.warningManager.evaluateWarnings() container.warningManager.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching) // Set up the config watchers on launch (updated automatically when switching)
App.shared.handlePhpConfigWatcher() await ConfigWatchManager.handleWatcher()
// Detect built-in and custom applications // Detect built-in and custom applications
await detectApplications() await App.shared.detectApplications()
// Load the rollback preset // Load the rollback preset
PresetHelper.loadRollbackPresetFromFile() PresetHelper.loadRollbackPresetFromFile()
@@ -150,52 +158,21 @@ extension MainMenu {
/** /**
When the environment is not OK, present an alert to inform the user. When the environment is not OK, present an alert to inform the user.
*/ */
@MainActor
private func onEnvironmentFail() async { private func onEnvironmentFail() async {
Task { @MainActor [self] in NVAlert()
NVAlert() .withInformation(
.withInformation( title: "alert.cannot_start.title".localized,
title: "alert.cannot_start.title".localized, subtitle: "alert.cannot_start.subtitle".localized,
subtitle: "alert.cannot_start.subtitle".localized, description: "alert.cannot_start.description".localized
description: "alert.cannot_start.description".localized )
) .withPrimary(text: "alert.cannot_start.retry".localized)
.withPrimary(text: "alert.cannot_start.retry".localized) .withSecondary(text: "alert.cannot_start.close".localized, action: { vc in
.withSecondary(text: "alert.cannot_start.close".localized, action: { vc in vc.close(with: .alertSecondButtonReturn)
vc.close(with: .alertSecondButtonReturn) exit(1)
exit(1) })
}) .show(urgency: .bringToFront)
.show()
Task { // An issue occurred, fire startup checks again after dismissal await self.check()
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)")
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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